import { useState, useEffect, useRef, useMemo, type ReactNode } from 'react'
import { createPortal } from 'react-dom'
import { Search, X, ChevronUp, ChevronDown } from 'lucide-react'
import styles from './CommandPalette.module.css'
import { SectionHeader } from '../../primitives/SectionHeader/SectionHeader'
import { CodeBlock } from '../../primitives/CodeBlock/CodeBlock'
import { KeyboardHint } from '../../primitives/KeyboardHint/KeyboardHint'
import type { SearchResult, SearchCategory, ScopeFilter } from './types'

interface CommandPaletteProps {
  open: boolean
  onClose: () => void
  onSelect: (result: SearchResult) => void
  data: SearchResult[]
  onOpen?: () => void
  onQueryChange?: (query: string) => void
  /** Called when Enter is pressed without the user explicitly selecting a result (arrow keys/click).
   *  Useful for applying the query as a full-text search filter. */
  onSubmit?: (query: string) => void
}

const KNOWN_CATEGORY_LABELS: Record<string, string> = {
  application: 'Applications',
  exchange: 'Exchanges',
  attribute: 'Attributes',
  route: 'Routes',
  agent: 'Agents',
}

/** Preferred display order for known categories */
const KNOWN_CATEGORY_ORDER: string[] = [
  'application',
  'exchange',
  'attribute',
  'route',
  'agent',
]

function categoryLabel(cat: string): string {
  if (cat === 'all') return 'All'
  if (KNOWN_CATEGORY_LABELS[cat]) return KNOWN_CATEGORY_LABELS[cat]
  // Title-case unknown categories: "my-thing" → "My Thing", "foo_bar" → "Foo Bar"
  return cat.replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())
}

function highlightText(text: string, query: string, matchRanges?: [number, number][]): ReactNode {
  if (!query && (!matchRanges || matchRanges.length === 0)) return text

  // Use matchRanges if provided, otherwise compute from query
  let ranges: [number, number][] = matchRanges ?? []
  if (!matchRanges && query) {
    const lowerText = text.toLowerCase()
    const lowerQuery = query.toLowerCase()
    let idx = 0
    while (idx < text.length) {
      const found = lowerText.indexOf(lowerQuery, idx)
      if (found === -1) break
      ranges.push([found, found + lowerQuery.length])
      idx = found + lowerQuery.length
    }
  }

  if (ranges.length === 0) return text

  const parts: ReactNode[] = []
  let last = 0
  for (const [start, end] of ranges) {
    if (start > last) parts.push(text.slice(last, start))
    parts.push(<mark key={start} className={styles.mark}>{text.slice(start, end)}</mark>)
    last = end
  }
  if (last < text.length) parts.push(text.slice(last))
  return <>{parts}</>
}

export function CommandPalette({ open, onClose, onSelect, data, onOpen, onQueryChange, onSubmit }: CommandPaletteProps) {
  const [query, setQuery] = useState('')
  const [activeCategory, setActiveCategory] = useState<string>('all')
  const [scopeFilters, setScopeFilters] = useState<ScopeFilter[]>([])
  const [focusedIdx, setFocusedIdx] = useState(0)
  const [expandedId, setExpandedId] = useState<string | null>(null)
  const userNavigated = useRef(false)
  const inputRef = useRef<HTMLInputElement>(null)
  const listRef = useRef<HTMLDivElement>(null)

  // Register global Ctrl+K / Cmd+K
  useEffect(() => {
    function handleKeyDown(e: KeyboardEvent) {
      if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
        e.preventDefault()
        onOpen?.()
      }
    }
    window.addEventListener('keydown', handleKeyDown)
    return () => window.removeEventListener('keydown', handleKeyDown)
  }, [onOpen])

  // Focus input when opened
  useEffect(() => {
    if (open) {
      setTimeout(() => inputRef.current?.focus(), 10)
      setQuery('')
      setFocusedIdx(0)
      setExpandedId(null)
      userNavigated.current = false
    }
  }, [open])

  // Stage 1: apply text query + scope filters (used for counts)
  const queryFiltered = useMemo(() => {
    let results = data

    if (query.trim()) {
      const q = query.toLowerCase()
      results = results.filter(
        (r) => r.serverFiltered || r.title.toLowerCase().includes(q) || r.meta.toLowerCase().includes(q),
      )
    }

    for (const sf of scopeFilters) {
      results = results.filter((r) =>
        r.category === sf.field || r.title.toLowerCase().includes(sf.value.toLowerCase()),
      )
    }

    return results
  }, [data, query, scopeFilters])

  // Stage 2: apply category filter (used for display)
  const filtered = useMemo(() => {
    if (activeCategory === 'all') return queryFiltered
    return queryFiltered.filter((r) => r.category === activeCategory)
  }, [queryFiltered, activeCategory])

  // Group results by category
  const grouped = useMemo(() => {
    const map = new Map<string, SearchResult[]>()
    for (const r of filtered) {
      if (!map.has(r.category)) map.set(r.category, [])
      map.get(r.category)!.push(r)
    }
    return map
  }, [filtered])

  // Flatten for keyboard nav
  const flatResults = useMemo(() => filtered, [filtered])

  // Counts per category (from query-filtered, before category filter)
  const categoryCounts = useMemo(() => {
    const counts: Record<string, number> = { all: queryFiltered.length }
    for (const r of queryFiltered) {
      counts[r.category] = (counts[r.category] ?? 0) + 1
    }
    return counts
  }, [queryFiltered])

  // Build tab list dynamically: 'all' + known categories (in order) + any unknown categories found in data
  const visibleCategories = useMemo(() => {
    const dataCategories = new Set(data.map((r) => r.category))
    const tabs: string[] = ['all']
    for (const cat of KNOWN_CATEGORY_ORDER) {
      if (dataCategories.has(cat)) tabs.push(cat)
    }
    for (const cat of dataCategories) {
      if (!tabs.includes(cat)) tabs.push(cat)
    }
    return tabs
  }, [data])

  function handleKeyDown(e: React.KeyboardEvent) {
    switch (e.key) {
      case 'Escape':
        onClose()
        break
      case 'ArrowDown':
        e.preventDefault()
        userNavigated.current = true
        setFocusedIdx((i) => Math.min(i + 1, flatResults.length - 1))
        break
      case 'ArrowUp':
        e.preventDefault()
        userNavigated.current = true
        setFocusedIdx((i) => Math.max(i - 1, 0))
        break
      case 'Enter':
        e.preventDefault()
        if (!userNavigated.current && onSubmit && query.trim()) {
          onSubmit(query.trim())
          onClose()
        } else if (flatResults[focusedIdx]) {
          onSelect(flatResults[focusedIdx])
          onClose()
        }
        break
    }
  }

  // Scroll focused item into view
  useEffect(() => {
    const el = listRef.current?.querySelector(`[data-idx="${focusedIdx}"]`) as HTMLElement | null
    el?.scrollIntoView({ block: 'nearest' })
  }, [focusedIdx])

  function removeScopeFilter(idx: number) {
    setScopeFilters((prev) => prev.filter((_, i) => i !== idx))
  }

  function toggleExpanded(e: React.MouseEvent, id: string) {
    e.stopPropagation()
    setExpandedId((prev) => (prev === id ? null : id))
  }

  if (!open) return null

  return createPortal(
    <div
      className={styles.overlay}
      onClick={onClose}
      onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onClose() }}
      role="button"
      tabIndex={0}
      aria-label="Close command palette"
      data-testid="command-palette-overlay"
    >
      <div
        className={styles.panel}
        onClick={(e) => e.stopPropagation()}
        onKeyDown={handleKeyDown}
        role="dialog"
        aria-modal="true"
        aria-label="Command palette"
      >
        {/* Search input area */}
        <div className={styles.searchArea}>
          <span className={styles.searchIcon} aria-hidden="true"><Search size={14} /></span>
          {scopeFilters.map((sf, i) => (
            <span key={i} className={styles.scopeTag}>
              <span className={styles.scopeField}>{sf.field}:</span>
              <span className={styles.scopeValue}>{sf.value}</span>
              <button
                className={styles.scopeRemove}
                onClick={() => removeScopeFilter(i)}
                aria-label={`Remove filter ${sf.field}:${sf.value}`}
              >
                <X size={10} />
              </button>
            </span>
          ))}
          <input
            ref={inputRef}
            type="text"
            className={styles.input}
            placeholder="Search exchanges, routes, agents…"
            value={query}
            onChange={(e) => {
              setQuery(e.target.value)
              setFocusedIdx(0)
              userNavigated.current = false
              onQueryChange?.(e.target.value)
            }}
            aria-label="Search"
          />
          <KeyboardHint keys="Esc" />
        </div>

        {/* Category tabs */}
        <div className={styles.tabs} role="tablist">
          {visibleCategories.map((cat) => (
            <button
              key={cat}
              role="tab"
              aria-selected={activeCategory === cat}
              className={[
                styles.tab,
                activeCategory === cat ? styles.tabActive : '',
              ]
                .filter(Boolean)
                .join(' ')}
              onClick={() => {
                setActiveCategory(cat)
                setFocusedIdx(0)
              }}
            >
              {categoryLabel(cat)}
              {categoryCounts[cat] != null && (
                <span className={styles.tabCount}>{categoryCounts[cat]}</span>
              )}
            </button>
          ))}
        </div>

        {/* Results */}
        <div
          className={styles.results}
          ref={listRef}
          role="listbox"
          aria-label="Search results"
        >
          {filtered.length === 0 ? (
            <div className={styles.empty}>No results for "{query}"</div>
          ) : (
            Array.from(grouped.entries()).map(([category, items]) => (
              <div key={category} className={styles.group}>
                <div className={styles.groupHeader}>
                  <SectionHeader>{categoryLabel(category)}</SectionHeader>
                </div>
                {items.map((result) => {
                  const flatIdx = flatResults.indexOf(result)
                  const isFocused = flatIdx === focusedIdx
                  const isExpanded = expandedId === result.id

                  return (
                    <div
                      key={result.id}
                      data-idx={flatIdx}
                      className={[
                        styles.item,
                        isFocused ? styles.focused : '',
                      ]
                        .filter(Boolean)
                        .join(' ')}
                      role="option"
                      aria-selected={isFocused}
                      onClick={() => {
                        onSelect(result)
                        onClose()
                      }}
                      onKeyDown={(e) => {
                        if (e.key === 'Enter' || e.key === ' ') {
                          onSelect(result)
                          onClose()
                        }
                      }}
                      onMouseEnter={() => { userNavigated.current = true; setFocusedIdx(flatIdx) }}
                    >
                      <div className={styles.itemMain}>
                        {result.icon && (
                          <span className={styles.itemIcon}>{result.icon}</span>
                        )}
                        <div className={styles.itemContent}>
                          <div className={styles.itemTop}>
                            <span className={styles.itemTitle}>
                              {highlightText(result.title, query, result.matchRanges)}
                            </span>
                            <div className={styles.itemBadges}>
                              {result.badges?.map((b, bi) => (
                                <span key={bi} className={styles.badge}>
                                  {b.label}
                                </span>
                              ))}
                            </div>
                            {result.timestamp && (
                              <span className={styles.itemTime}>{result.timestamp}</span>
                            )}
                          </div>
                          <div className={styles.itemMeta}>
                            {highlightText(result.meta, query)}
                          </div>
                          {result.matchContext && (
                            <div
                              className={styles.matchContext}
                              dangerouslySetInnerHTML={{ __html: result.matchContext }}
                            />
                          )}
                        </div>
                        {result.expandedContent && (
                          <button
                            className={styles.expandBtn}
                            onClick={(e) => toggleExpanded(e, result.id)}
                            aria-expanded={isExpanded}
                            aria-label="Toggle detail"
                          >
                            {isExpanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
                          </button>
                        )}
                      </div>
                      {isExpanded && result.expandedContent && (
                        <div className={styles.expanded} onClick={(e) => e.stopPropagation()}>
                          <CodeBlock
                            content={result.expandedContent}
                            language="json"
                            copyable
                          />
                        </div>
                      )}
                    </div>
                  )
                })}
              </div>
            ))
          )}
        </div>

        {/* Shortcuts bar */}
        <div className={styles.shortcutsBar}>
          <div className={styles.shortcut}>
            <KeyboardHint keys="↑↓" />
            <span>Navigate</span>
          </div>
          <div className={styles.shortcut}>
            <KeyboardHint keys="Enter" />
            <span>Search</span>
          </div>
          <div className={styles.shortcut}>
            <KeyboardHint keys="Esc" />
            <span>Close</span>
          </div>
          <div className={styles.shortcut}>
            <KeyboardHint keys="Tab" />
            <span>Filter</span>
          </div>
        </div>
      </div>
    </div>,
    document.body,
  )
}
