import { useState, useEffect, useMemo } from 'react'
import { Combobox, Listbox } from '@headlessui/react'
import { api, getProjectionDefs, getObjects } from '../api'
import { Spinner, HashBadge } from './Common'

interface ProjectionDef {
  name: string
  hash: string
  params: Record<string, { type: string; object_type?: string }>
  value_schema: { type: string }
  initial_value: any
  sources: Array<{ event_def_hash: string; bindings: Record<string, string>; expression: string }>
}

export default function Projections() {
  const [defs, setDefs] = useState<ProjectionDef[]>([])
  const [selectedDef, setSelectedDef] = useState<ProjectionDef | null>(null)
  const [params, setParams] = useState<Record<string, string>>({})
  const [objects, setObjects] = useState<Array<{ id: string; type: string }>>([])
  const [result, setResult] = useState<any>(null)
  const [error, setError] = useState('')
  const [loading, setLoading] = useState(false)
  const [initialLoading, setInitialLoading] = useState(true)

  useEffect(() => {
    Promise.all([getProjectionDefs(), getObjects()])
      .then(([defsRes, objsRes]) => {
        setDefs(defsRes.projection_defs)
        setObjects(objsRes.objects.map((o: any) => ({ ...o, id: String(o.id) })))
      })
      .catch(() => {})
      .finally(() => setInitialLoading(false))
  }, [])

  const handleSelectDef = (def: ProjectionDef | null) => {
    setSelectedDef(def)
    setResult(null)
    setError('')
    if (def) {
      const initial: Record<string, string> = {}
      for (const key of Object.keys(def.params)) {
        initial[key] = ''
      }
      setParams(initial)
    } else {
      setParams({})
    }
  }

  const handleQuery = async () => {
    if (!selectedDef) return
    setLoading(true)
    setError('')
    setResult(null)

    const queryParams = new URLSearchParams()
    for (const [k, v] of Object.entries(params)) {
      if (v.trim()) queryParams.set(k, v.trim())
    }

    try {
      const res = await api<{ value: any }>(`/projections/${selectedDef.name}?${queryParams.toString()}`)
      setResult(res.value)
    } catch (e: any) {
      setError(e.message)
    } finally {
      setLoading(false)
    }
  }

  const objectsByType = useMemo(() => {
    const map: Record<string, string[]> = {}
    for (const obj of objects) {
      if (!map[obj.type]) map[obj.type] = []
      map[obj.type].push(String(obj.id))
    }
    return map
  }, [objects])

  if (initialLoading) return <Spinner />

  return (
    <div className="max-w-4xl mx-auto">
      <h2 className="text-lg font-semibold text-white tracking-tight mb-5">Query Projections</h2>
      <div className="bg-surface-1 rounded-xl p-6 space-y-6 border border-white/[0.06]">
        {/* Projection selector with Listbox */}
        <div>
          <label className="block text-sm font-medium text-gray-400 mb-2">Projection</label>
          <Listbox value={selectedDef} onChange={handleSelectDef}>
            <div className="relative">
              <Listbox.Button className="w-full px-4 py-2.5 bg-surface-3 border border-gray-700 rounded-lg text-left focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 transition-all">
                <span className={selectedDef ? 'text-gray-100' : 'text-gray-500'}>
                  {selectedDef?.name || 'Select a projection...'}
                </span>
                <span className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
                  <svg className="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
                    <path
                      fillRule="evenodd"
                      d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
                      clipRule="evenodd"
                    />
                  </svg>
                </span>
              </Listbox.Button>
              <Listbox.Options className="absolute z-10 mt-1 w-full bg-surface-3 border border-gray-700 rounded-lg shadow-lg max-h-60 overflow-auto focus:outline-none">
                {defs.map((d) => (
                  <Listbox.Option
                    key={d.name}
                    value={d}
                    className={({ active }) =>
                      `cursor-pointer select-none px-4 py-2.5 transition-colors ${
                        active ? 'bg-blue-600 text-white' : 'text-gray-100'
                      }`
                    }
                  >
                    {({ selected }) => <span className={selected ? 'font-semibold' : 'font-normal'}>{d.name}</span>}
                  </Listbox.Option>
                ))}
              </Listbox.Options>
            </div>
          </Listbox>
        </div>

        {/* Auto-generated params form */}
        {selectedDef && (
          <div className="space-y-4">
            <div className="flex items-baseline gap-2">
              <label className="block text-sm font-medium text-gray-400">Parameters</label>
              <span className="text-xs text-gray-600">
                → {selectedDef.value_schema?.type || 'any'}
                {selectedDef.initial_value !== undefined && (
                  <span className="ml-1">(initial: {JSON.stringify(selectedDef.initial_value)})</span>
                )}
              </span>
            </div>
            <div className="space-y-3 bg-surface-3/30 rounded-lg p-4 border border-gray-700/50">
              {Object.entries(selectedDef.params).map(([key, schema]) => (
                <div key={key}>
                  <label className="block text-xs font-medium text-gray-500 mb-1.5">
                    {key}
                    <span className="ml-2 px-1.5 py-0.5 bg-gray-700 rounded text-xs text-gray-400">
                      {schema.type}
                      {schema.object_type ? ` → ${schema.object_type}` : ''}
                    </span>
                  </label>
                  {schema.type === 'ref' ? (
                    <RefCombobox
                      value={params[key] || ''}
                      onChange={(v) => setParams({ ...params, [key]: v })}
                      objects={objects}
                      objectsByType={objectsByType}
                      objectType={schema.object_type}
                      paramName={key}
                    />
                  ) : (
                    <input
                      type={schema.type === 'number' ? 'number' : 'text'}
                      className="w-full px-3 py-2 bg-surface-3 border border-white/[0.08] rounded-lg focus:outline-none focus:ring-2 focus:ring-accent/30 focus:border-accent/30 transition-all"
                      placeholder={`Enter ${schema.type} value...`}
                      value={params[key] || ''}
                      onChange={(e) => setParams({ ...params, [key]: e.target.value })}
                    />
                  )}
                </div>
              ))}
            </div>
          </div>
        )}

        {/* Projection info */}
        {selectedDef && (
          <div className="text-xs space-y-2 bg-surface-3/20 rounded-lg p-4 border border-gray-700/30">
            <div>
              <span className="text-gray-500 font-medium">sources:</span>
              <div className="mt-1 space-y-2">
                {selectedDef.sources?.map((s, i) => (
                  <div key={i} className="pl-3 border-l border-gray-700">
                    <div className="flex items-center gap-2">
                      <HashBadge hash={s.event_def_hash} short={false} />
                    </div>
                    <div className="text-xs mt-1">
                      <span className="text-gray-500">bindings:</span>{' '}
                      {Object.keys(s.bindings).length === 0 ? (
                        <span className="text-gray-500 italic">none</span>
                      ) : (
                        <span className="text-yellow-400">
                          {Object.entries(s.bindings)
                            .map(([k, v]) => `${k}=${v}`)
                            .join(', ')}
                        </span>
                      )}
                    </div>
                    <div className="text-xs mt-0.5">
                      <span className="text-gray-500">expression:</span>{' '}
                      <code className="text-green-400">{s.expression}</code>
                    </div>
                  </div>
                ))}
              </div>
            </div>
          </div>
        )}

        <button
          onClick={handleQuery}
          disabled={!selectedDef || loading}
          className="w-full px-4 py-3 bg-blue-600 hover:bg-blue-700 rounded-lg font-semibold disabled:opacity-50 disabled:cursor-not-allowed transition-all shadow-lg shadow-blue-500/20 hover:shadow-blue-500/40"
        >
          {loading ? 'Querying...' : 'Query Projection'}
        </button>

        {error && (
          <div className="p-4 bg-red-900/20 border border-red-800 rounded-lg text-red-400 text-sm">{error}</div>
        )}

        {result !== null && (
          <div>
            <h3 className="text-sm font-medium text-gray-400 mb-2">Result</h3>
            <pre className="p-4 bg-gray-950 rounded-lg overflow-x-auto text-sm text-green-300 border border-white/[0.06]">
              {JSON.stringify(result, null, 2)}
            </pre>
          </div>
        )}
      </div>
    </div>
  )
}

// Headless UI Combobox for object ref params
function RefCombobox({
  value,
  onChange,
  objects,
  objectsByType,
  objectType,
  paramName,
}: {
  value: string
  onChange: (v: string) => void
  objects: Array<{ id: string; type: string }>
  objectsByType: Record<string, string[]>
  objectType?: string
  paramName?: string
}) {
  const [query, setQuery] = useState('')

  // Filter objects by object_type if specified, or infer from param name
  // Convention: param name often matches the object type (e.g. param "agent" → type "agent")
  const effectiveObjectType = useMemo(() => {
    if (objectType) return objectType
    if (paramName) {
      const types = new Set(objects.map(o => o.type))
      if (types.has(paramName)) return paramName
    }
    return undefined
  }, [objectType, paramName, objects])

  const relevantObjects = useMemo(() => {
    if (!effectiveObjectType) return objects
    return objects.filter((o) => o.type === effectiveObjectType)
  }, [objects, effectiveObjectType])

  const relevantByType = useMemo(() => {
    if (!effectiveObjectType) return objectsByType
    const filtered: Record<string, string[]> = {}
    if (objectsByType[effectiveObjectType]) {
      filtered[effectiveObjectType] = objectsByType[effectiveObjectType]
    }
    return filtered
  }, [objectsByType, effectiveObjectType])

  const filtered = useMemo(() => {
    if (!query) return relevantObjects
    const q = query.toLowerCase()
    return relevantObjects.filter((o) => String(o.id).toLowerCase().includes(q) || o.type.toLowerCase().includes(q))
  }, [relevantObjects, query])

  return (
    <Combobox value={value} onChange={(v) => onChange(v || '')}>
      <div className="relative">
        <div className="relative">
          <Combobox.Input
            className="w-full px-3 py-2 pr-10 bg-surface-3 border border-white/[0.08] rounded-lg focus:outline-none focus:ring-2 focus:ring-accent/30 focus:border-accent/30 transition-all"
            placeholder="Type object ID or select..."
            onChange={(e) => setQuery(e.target.value)}
            displayValue={(val: string) => val}
          />
          <Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-3">
            <svg className="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
              <path
                fillRule="evenodd"
                d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
                clipRule="evenodd"
              />
            </svg>
          </Combobox.Button>
        </div>
        <Combobox.Options className="absolute z-10 mt-1 w-full bg-surface-3 border border-gray-700 rounded-lg shadow-lg max-h-60 overflow-auto focus:outline-none">
          {Object.entries(relevantByType).map(([type, ids]) => {
            const filteredIds = ids.filter((id) => !query || id.toLowerCase().includes(query.toLowerCase()))
            if (filteredIds.length === 0) return null
            return (
              <div key={type}>
                <div className="sticky top-0 px-3 py-1.5 text-xs font-medium text-gray-500 bg-surface-3 border-b border-white/[0.06]">
                  {type}
                </div>
                {filteredIds.map((id) => (
                  <Combobox.Option
                    key={id}
                    value={id}
                    className={({ active }) =>
                      `cursor-pointer select-none px-3 py-2 text-sm transition-colors ${
                        active ? 'bg-blue-600 text-white' : 'text-gray-100'
                      }`
                    }
                  >
                    {({ selected }) => <span className={selected ? 'font-semibold' : 'font-normal'}>{id}</span>}
                  </Combobox.Option>
                ))}
              </div>
            )
          })}
          {filtered.length === 0 && <div className="px-3 py-2 text-sm text-gray-500">No objects found</div>}
        </Combobox.Options>
      </div>
    </Combobox>
  )
}
