import { useEffect, useRef, useState } from 'react'
import { makeStyles, mergeClasses, tokens, shorthands } from '@fluentui/react-components'
import { useFocusStyles } from './ui/focusRing'
import { Z, SCRIM, ELEVATION, RADIUS } from './ui/tokens'

export interface PaletteAction {
  id: string
  label: string
  /** short right-aligned hint, e.g. a shortcut or category */
  hint?: string
  run: () => void
}

const useStyles = makeStyles({
  // A plain fixed overlay -- NOT a Fluent Dialog, since this app avoids Fluent's
  // portal-based overlays (GPU/driver blank-overlay issue, see Select.tsx).
  backdrop: {
    position: 'fixed',
    inset: 0,
    zIndex: Z.overlay,
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'flex-start',
    paddingTop: '14vh',
    backgroundColor: SCRIM
  },
  panel: {
    width: 'min(560px, 92vw)',
    display: 'flex',
    flexDirection: 'column',
    backgroundColor: tokens.colorNeutralBackground1,
    ...shorthands.borderRadius(RADIUS.card),
    border: `1px solid ${tokens.colorNeutralStroke2}`,
    boxShadow: ELEVATION.overlay,
    overflow: 'hidden'
  },
  input: {
    appearance: 'none',
    border: 'none',
    padding: '14px 16px',
    fontSize: tokens.fontSizeBase400,
    fontFamily: tokens.fontFamilyBase,
    backgroundColor: 'transparent',
    color: tokens.colorNeutralForeground1,
    borderBottom: `1px solid ${tokens.colorNeutralStroke2}`
  },
  list: {
    maxHeight: '50vh',
    overflowY: 'auto',
    padding: '6px'
  },
  item: {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'space-between',
    gap: '12px',
    width: '100%',
    padding: '9px 12px',
    border: 'none',
    backgroundColor: 'transparent',
    color: tokens.colorNeutralForeground1,
    fontSize: tokens.fontSizeBase300,
    fontFamily: tokens.fontFamilyBase,
    textAlign: 'left',
    cursor: 'pointer',
    ...shorthands.borderRadius(tokens.borderRadiusMedium)
  },
  itemActive: {
    backgroundColor: tokens.colorBrandBackground2,
    color: tokens.colorBrandForeground2
  },
  itemHint: {
    flexShrink: 0,
    color: tokens.colorNeutralForeground3,
    fontSize: tokens.fontSizeBase200
  },
  empty: {
    padding: '14px 12px',
    color: tokens.colorNeutralForeground3
  }
})

/**
 * A Ctrl/Cmd+K command palette (Phase N). Filter by typing, ↑/↓ to move, Enter to
 * run, Esc or a backdrop click to dismiss. Actions are supplied by App.
 */
export function CommandPalette({
  actions,
  onClose
}: {
  actions: PaletteAction[]
  onClose: () => void
}): React.JSX.Element {
  const styles = useStyles()
  const focus = useFocusStyles()
  const [q, setQ] = useState('')
  const [sel, setSel] = useState(0)
  const inputRef = useRef<HTMLInputElement>(null)
  const listRef = useRef<HTMLDivElement>(null)

  const filtered = actions.filter((a) => a.label.toLowerCase().includes(q.trim().toLowerCase()))
  const listId = 'cmdpalette-list'
  const optionId = (id: string): string => `cmdpalette-option-${id}`
  const activeId = filtered[sel] ? optionId(filtered[sel].id) : undefined

  useEffect(() => {
    inputRef.current?.focus()
  }, [])
  useEffect(() => {
    setSel(0)
  }, [q])
  useEffect(() => {
    const row = listRef.current?.querySelector<HTMLElement>(`[data-sel="true"]`)
    row?.scrollIntoView({ block: 'nearest' })
  }, [sel])

  function onKeyDown(e: React.KeyboardEvent): void {
    if (e.key === 'Escape') {
      onClose()
    } else if (e.key === 'ArrowDown') {
      e.preventDefault()
      setSel((s) => Math.min(filtered.length - 1, s + 1))
    } else if (e.key === 'ArrowUp') {
      e.preventDefault()
      setSel((s) => Math.max(0, s - 1))
    } else if (e.key === 'Enter') {
      e.preventDefault()
      const a = filtered[sel]
      if (a) {
        a.run()
        onClose()
      }
    }
  }

  return (
    <div className={styles.backdrop} onClick={onClose}>
      <div
        className={styles.panel}
        onClick={(e) => e.stopPropagation()}
        role="dialog"
        aria-label="Command palette"
      >
        <input
          ref={inputRef}
          className={mergeClasses(styles.input, focus.focusRing)}
          value={q}
          onChange={(e) => setQ(e.target.value)}
          onKeyDown={onKeyDown}
          placeholder="Type a command…"
          role="combobox"
          aria-expanded={filtered.length > 0}
          aria-controls={listId}
          aria-activedescendant={activeId}
          aria-autocomplete="list"
          aria-label="Command palette search"
        />
        <div className={styles.list} ref={listRef} id={listId} role="listbox" aria-label="Commands">
          {filtered.length === 0 ? (
            <div className={styles.empty}>No matching commands</div>
          ) : (
            filtered.map((a, i) => (
              // A non-focusable option: focus stays on the combobox input and the
              // active row is announced via aria-activedescendant (UI27), the
              // standard combobox+listbox pattern. onMouseEnter keeps the pointer
              // and keyboard highlight unified on one `sel` state.
              <div
                key={a.id}
                id={optionId(a.id)}
                role="option"
                aria-selected={i === sel}
                data-sel={i === sel ? 'true' : undefined}
                className={mergeClasses(styles.item, i === sel && styles.itemActive)}
                onClick={() => {
                  a.run()
                  onClose()
                }}
                onMouseEnter={() => setSel(i)}
              >
                <span>{a.label}</span>
                {a.hint && <span className={styles.itemHint}>{a.hint}</span>}
              </div>
            ))
          )}
        </div>
      </div>
    </div>
  )
}
