'use client'

import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useRouter } from 'next/navigation'

import { useI18n } from '@/components/providers/i18n-provider'
import { useAppMode } from '@/components/providers/app-mode-provider'
import { api } from '@/lib/api/client'
import { cn } from '@/lib/cn'

/**
 * Command palette (§59) and global search (§60), in one surface.
 *
 * They belong together: an operator pressing ⌘K is looking for *something* — a page, an
 * asset, an incident — and making them choose which kind of thing before they type is
 * the sort of friction that stops people using the feature at all. Commands match
 * locally and instantly; entity results arrive from the server, debounced, and are
 * permission-scoped there.
 */

interface Command {
  id: string
  labelKey: string
  hint?: string
  run: () => void
  permission?: string
  group: 'navigate' | 'action' | 'appearance'
}

interface SearchHit {
  kind: string
  code: string
  label: string
  labelAr: string
  detail: string
  href: string
}

const SEARCH_DEBOUNCE_MS = 220

export function CommandPalette({ permissions }: { permissions: string[] }) {
  const { t, locale } = useI18n()
  const router = useRouter()
  const { theme, setTheme, contrast, setContrast, motion, setMotion } = useAppMode()

  const [open, setOpen] = useState(false)
  const [query, setQuery] = useState('')
  const [hits, setHits] = useState<SearchHit[]>([])
  const [active, setActive] = useState(0)
  const [searching, setSearching] = useState(false)
  const inputRef = useRef<HTMLInputElement>(null)
  const held = useRef(new Set(permissions))

  useEffect(() => {
    held.current = new Set(permissions)
  }, [permissions])

  const go = useCallback(
    (href: string) => {
      setOpen(false)
      router.push(href)
    },
    [router],
  )

  const commands = useMemo<Command[]>(
    () => [
      { id: 'command-center', labelKey: 'nav.commandCenter', group: 'navigate', permission: 'grid.read', run: () => go('/command-center') },
      { id: 'digital-twin', labelKey: 'nav.digitalTwin', group: 'navigate', permission: 'twin.read', run: () => go('/digital-twin') },
      { id: 'live-grid', labelKey: 'nav.liveGrid', group: 'navigate', permission: 'twin.read', run: () => go('/live-grid') },
      { id: 'grid-physics', labelKey: 'nav.physics', group: 'navigate', permission: 'twin.read', run: () => go('/grid-physics') },
      { id: 'compare-futures', labelKey: 'nav.compareFutures', group: 'navigate', permission: 'twin.simulate', run: () => go('/compare-futures') },
      { id: 'sensors', labelKey: 'nav.sensors', group: 'navigate', permission: 'twin.read', run: () => go('/sensors') },
      { id: 'live-grid-flow', labelKey: 'liveGrid.layer.power_flow', group: 'action', permission: 'twin.read', run: () => go('/live-grid?layer=power_flow') },
      { id: 'live-grid-risk', labelKey: 'liveGrid.layer.risk', group: 'action', permission: 'twin.read', run: () => go('/live-grid?layer=risk') },
      { id: 'map', labelKey: 'nav.map', group: 'navigate', permission: 'grid.map.read', run: () => go('/map') },
      { id: 'war-room', labelKey: 'nav.warRoom', group: 'navigate', permission: 'warroom.access', run: () => go('/war-room') },
      { id: 'ai-operations', labelKey: 'nav.aiOperations', group: 'navigate', permission: 'agents.read', run: () => go('/ai-operations') },
      { id: 'risk-radar', labelKey: 'nav.riskRadar', group: 'navigate', permission: 'grid.read', run: () => go('/risk-radar') },
      { id: 'resilience', labelKey: 'nav.resilience', group: 'navigate', permission: 'grid.read', run: () => go('/resilience') },
      { id: 'cascade', labelKey: 'nav.cascade', group: 'navigate', permission: 'simulations.read', run: () => go('/cascade') },
      { id: 'counterfactual', labelKey: 'nav.counterfactual', group: 'navigate', permission: 'counterfactual.run', run: () => go('/counterfactual') },
      { id: 'trust', labelKey: 'nav.trust', group: 'navigate', permission: 'models.read', run: () => go('/trust') },
      { id: 'integrations', labelKey: 'integrations.hub.title', group: 'navigate', permission: 'integrations.read', run: () => go('/integrations') },
      { id: 'risk-hunter', labelKey: 'nav.riskHunter', group: 'navigate', permission: 'riskhunter.read', run: () => go('/risk-hunter') },
      { id: 'proofs', labelKey: 'nav.proofs', group: 'navigate', permission: 'proof.read', run: () => go('/proofs') },
      { id: 'copilot', labelKey: 'nav.copilot', group: 'navigate', permission: 'copilot.use', run: () => go('/copilot') },
      { id: 'hackathon', labelKey: 'nav.hackathon', group: 'navigate', permission: 'presentation.use', run: () => go('/hackathon') },
      { id: 'immersive', labelKey: 'palette.immersive', group: 'action', permission: 'presentation.use', run: () => go('/demo/immersive') },
      { id: 'run-scenario', labelKey: 'palette.runScenario', group: 'action', permission: 'twin.simulate', run: () => go('/digital-twin?panel=scenario') },
      {
        id: 'theme',
        labelKey: theme === 'dark' ? 'palette.lightMode' : 'palette.darkMode',
        group: 'appearance',
        run: () => {
          setTheme(theme === 'dark' ? 'light' : 'dark')
          setOpen(false)
        },
      },
      {
        id: 'contrast',
        labelKey: contrast === 'high' ? 'palette.normalContrast' : 'palette.highContrast',
        group: 'appearance',
        run: () => {
          setContrast(contrast === 'high' ? 'normal' : 'high')
          setOpen(false)
        },
      },
      {
        id: 'motion',
        labelKey: motion === 'reduced' ? 'palette.fullMotion' : 'palette.reduceMotion',
        group: 'appearance',
        run: () => {
          setMotion(motion === 'reduced' ? 'full' : 'reduced')
          setOpen(false)
        },
      },
      {
        id: 'language',
        labelKey: 'palette.switchLanguage',
        group: 'appearance',
        run: () => {
          setOpen(false)
          // The switcher writes the cookie and reloads; going through it keeps one code
          // path responsible for locale rather than two that can disagree.
          document.querySelector<HTMLButtonElement>('[data-language-switch]')?.click()
        },
      },
    ],
    [go, theme, contrast, motion, setTheme, setContrast, setMotion],
  )

  const visibleCommands = useMemo(() => {
    const needle = query.trim().toLowerCase()
    return commands
      .filter((command) => !command.permission || held.current.has(command.permission))
      .filter((command) => !needle || t(command.labelKey).toLowerCase().includes(needle))
  }, [commands, query, t])

  const options = useMemo(
    () => [
      ...visibleCommands.map((command) => ({ type: 'command' as const, command })),
      ...hits.map((hit) => ({ type: 'hit' as const, hit })),
    ],
    [visibleCommands, hits],
  )

  // ── Open / close ─────────────────────────────────────────────────────────
  useEffect(() => {
    const onKeyDown = (event: KeyboardEvent) => {
      if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {
        event.preventDefault()
        setOpen((current) => !current)
        return
      }
      if (event.key === 'Escape') setOpen(false)
    }
    window.addEventListener('keydown', onKeyDown)
    return () => window.removeEventListener('keydown', onKeyDown)
  }, [])

  useEffect(() => {
    if (open) {
      setActive(0)
      // The input is focused on the next frame: focusing it in the same tick as the
      // dialog mounting loses the caret in Safari.
      requestAnimationFrame(() => inputRef.current?.focus())
    } else {
      setQuery('')
      setHits([])
    }
  }, [open])

  // ── Entity search ────────────────────────────────────────────────────────
  useEffect(() => {
    const needle = query.trim()
    if (needle.length < 2) {
      setHits([])
      return
    }
    setSearching(true)
    const timer = setTimeout(async () => {
      try {
        const data = await api.get<{ hits: SearchHit[] }>(`/api/search?q=${encodeURIComponent(needle)}`)
        setHits(data.hits)
      } catch {
        setHits([])
      } finally {
        setSearching(false)
      }
    }, SEARCH_DEBOUNCE_MS)
    return () => clearTimeout(timer)
  }, [query])

  const choose = (index: number) => {
    const option = options[index]
    if (!option) return
    if (option.type === 'command') option.command.run()
    else go(option.hit.href)
  }

  if (!open) {
    return (
      <button
        type="button"
        onClick={() => setOpen(true)}
        className="hidden items-center gap-2 rounded-lg border border-border bg-surface-2/70 px-2.5 py-1.5 text-[11px] text-text-faint transition-colors hover:border-border-strong hover:text-text-muted sm:flex"
      >
        <svg viewBox="0 0 24 24" className="size-3.5" fill="none" stroke="currentColor" strokeWidth="1.8" aria-hidden>
          <circle cx="11" cy="11" r="6.5" />
          <path d="m16 16 4 4" strokeLinecap="round" />
        </svg>
        {t('palette.open')}
        <kbd className="rounded border border-border px-1 font-mono text-[9px]">⌘K</kbd>
      </button>
    )
  }

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-label={t('palette.title')}
      className="fixed inset-0 z-[60] flex items-start justify-center bg-base/70 px-4 pt-[12vh] backdrop-blur-sm"
      onClick={(event) => {
        if (event.target === event.currentTarget) setOpen(false)
      }}
    >
      <div className="nabdh-enter nabdh-glass w-full max-w-xl overflow-hidden rounded-[--radius-panel] border shadow-2xl">
        <div className="flex items-center gap-2 border-b border-border px-4 py-3">
          <svg viewBox="0 0 24 24" className="size-4 shrink-0 text-text-faint" fill="none" stroke="currentColor" strokeWidth="1.8" aria-hidden>
            <circle cx="11" cy="11" r="6.5" />
            <path d="m16 16 4 4" strokeLinecap="round" />
          </svg>
          <input
            ref={inputRef}
            value={query}
            onChange={(event) => {
              setQuery(event.target.value)
              setActive(0)
            }}
            onKeyDown={(event) => {
              if (event.key === 'ArrowDown') {
                event.preventDefault()
                setActive((current) => Math.min(current + 1, options.length - 1))
              } else if (event.key === 'ArrowUp') {
                event.preventDefault()
                setActive((current) => Math.max(current - 1, 0))
              } else if (event.key === 'Enter') {
                event.preventDefault()
                choose(active)
              }
            }}
            placeholder={t('palette.placeholder')}
            aria-label={t('palette.placeholder')}
            className="w-full bg-transparent text-sm text-text placeholder:text-text-faint focus:outline-none"
          />
          {searching ? (
            <span aria-hidden className="size-3 animate-spin rounded-full border-2 border-text-faint border-t-transparent" />
          ) : null}
        </div>

        <ul className="max-h-[52vh] overflow-y-auto py-1" role="listbox" aria-label={t('palette.title')}>
          {options.length === 0 ? (
            <li className="px-4 py-6 text-center text-xs text-text-muted">{t('palette.noResults')}</li>
          ) : (
            options.map((option, index) => {
              const selected = index === active
              const label =
                option.type === 'command'
                  ? t(option.command.labelKey)
                  : locale === 'ar'
                    ? option.hit.labelAr
                    : option.hit.label
              const detail =
                option.type === 'command' ? t(`palette.group.${option.command.group}`) : option.hit.detail
              const code = option.type === 'hit' ? option.hit.code : null

              return (
                <li key={option.type === 'command' ? option.command.id : `${option.hit.kind}-${option.hit.code}`}>
                  <button
                    type="button"
                    role="option"
                    aria-selected={selected}
                    onMouseEnter={() => setActive(index)}
                    onClick={() => choose(index)}
                    className={cn(
                      'flex w-full items-baseline justify-between gap-3 px-4 py-2 text-start transition-colors',
                      selected ? 'bg-brand/12 text-text' : 'text-text-muted hover:bg-surface-2',
                    )}
                  >
                    <span className="min-w-0 truncate text-xs font-medium">
                      {code ? <span className="me-2 font-mono text-[10px] text-brand">{code}</span> : null}
                      {label}
                    </span>
                    <span className="shrink-0 text-[10px] text-text-faint">{detail}</span>
                  </button>
                </li>
              )
            })
          )}
        </ul>

        <div className="flex items-center justify-between border-t border-border px-4 py-2 text-[10px] text-text-faint">
          <span>{t('palette.hint')}</span>
          <span className="flex items-center gap-2">
            <kbd className="rounded border border-border px-1 font-mono">↑↓</kbd>
            <kbd className="rounded border border-border px-1 font-mono">↵</kbd>
            <kbd className="rounded border border-border px-1 font-mono">esc</kbd>
          </span>
        </div>
      </div>
    </div>
  )
}
