import {
  useState,
  useEffect,
  useRef,
  useCallback,
  useMemo,
} from 'preact/hooks';
import type { PaletteEntry, ContentType } from '@lib/types/content';
import styles from './CMDPalette.module.css';

/* CONSTANTS */
const MAX_DEFAULT = 20;
const MAX_SEARCH = 30;

const TYPE_LABELS: Record<ContentType, string> = {
  article: 'Article',
  element: 'Element',
  page: 'Page',
  section: 'Section',
};

/* INDEX ENTRY */
interface IndexedEntry extends PaletteEntry {
  _label: string;
  _parent: string;
  _type: string;
  _path: string;
}

const normalize = (str: string): string =>
  str.toLowerCase().replace(/[^a-z0-9]/g, '');

const indexEntry = (entry: PaletteEntry): IndexedEntry => ({
  ...entry,
  _label: normalize(entry.label),
  _parent: normalize(entry.parent ?? ''),
  _type: normalize(entry.type),
  _path: normalize(entry.path),
});

/* SCORING */
const scoreEntry = (entry: IndexedEntry, q: string): number => {
  let score = 0;

  if (entry._label === q) score += 100;
  else if (entry._label.startsWith(q)) score += 80;
  else if (entry._label.includes(q)) score += 60;
  if (entry._parent.includes(q)) score += 30;
  if (entry._type.includes(q)) score += 20;
  if (entry._path.includes(q)) score += 10;

  return score;
};

/* RENDERABLE ROW – single-pass from filtered entries */
interface RenderRow {
  kind: 'label' | 'result';
  key: string;
  group?: string;
  entry?: PaletteEntry;
  index?: number;
}

const buildRows = (entries: PaletteEntry[]): RenderRow[] => {
  const rows: RenderRow[] = [];
  let currentGroup = '';
  let idx = 0;

  for (const entry of entries) {
    const group = entry.parent ?? TYPE_LABELS[entry.type] ?? 'Other';

    if (group !== currentGroup) {
      currentGroup = group;
      rows.push({ kind: 'label', key: `label-${group}`, group });
    }
    rows.push({
      kind: 'result',
      key: entry.path,
      entry,
      index: idx++,
    });
  }
  return rows;
};

interface Props {
  currentPath?: string;
}

/* COMPONENT */
export default function CommandPalette({ currentPath = '/' }) {
  const [isOpen, setIsOpen] = useState(false);
  const [query, setQuery] = useState('');
  const [index, setIndex] = useState<IndexedEntry[]>([]);
  const [selectedIndex, setSelectedIndex] = useState(-1);

  const selectedRef = useRef(-1);
  const loadingRef = useRef(false);
  const inputRef = useRef<HTMLInputElement>(null);
  const resultsRef = useRef<HTMLDivElement>(null);

  /* Keep ref in sync for stable access in callbacks */
  useEffect(() => {
    selectedRef.current = selectedIndex;
  }, [selectedIndex]);

  /* Load index (ref-guarded, no race conditions) */
  const loadIndex = useCallback(async () => {
    if (index.length > 0 || loadingRef.current) return;
    loadingRef.current = true;

    try {
      const res = await fetch('/palette-index.json');
      const data: PaletteEntry[] = await res.json();
      setIndex(data.map(indexEntry));
    } catch (err) {
      console.error('Failed to load palette index:', err);
      loadingRef.current = false;
    }
  }, [index.length]);

  /* OPEN / CLOSE */
  const open = useCallback(async () => {
    await loadIndex();
    setIsOpen(true);
    setQuery('');
    setSelectedIndex(-1);
  }, [loadIndex]);

  const close = useCallback(() => {
    setIsOpen(false);
    setSelectedIndex(-1);
  }, []);

  /* GLOBAL KEYBOARD SHORTCUTS */
  useEffect(() => {
    const onKeydown = (e: KeyboardEvent) => {
      if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
        e.preventDefault();
        isOpen ? close() : open();
      }
      if (e.key === 'Escape' && isOpen) {
        e.preventDefault();
        close();
      }
    };
    document.addEventListener('keydown', onKeydown);
    return () => document.removeEventListener('keydown', onKeydown);
  }, [isOpen, open, close]);

  /* FOCUS INPUT WHEN OPENING*/
  useEffect(() => {
    if (isOpen) requestAnimationFrame(() => inputRef.current?.focus());
  }, [isOpen]);

  /* FILTERED ENTRIES -> RENDER ROWS */
  const filtered = useMemo(() => {
    if (query.trim()) {
      const q = normalize(query);
      return index
        .map((entry) => ({ entry, score: scoreEntry(entry, q) }))
        .filter((r) => r.score > 0)
        .sort((a, b) => b.score - a.score)
        .slice(0, MAX_SEARCH)
        .map((r) => r.entry);
    }

    if (currentPath === '/') {
      return index.filter((e) => e.depth === 1);
    }

    const segments = currentPath.replace(/\/$/, '').split('/').filter(Boolean);
    const parentPath = '/' + segments.slice(0, -1).join('/');
    const normalizedCurrent = currentPath.replace(/\/$/, '');

    const siblings = index.filter((e) => {
      const ePath = e.path.replace(/\/$/, '');
      return (
        ePath !== normalizedCurrent &&
        ePath.startsWith(parentPath) &&
        e.depth === segments.length
      );
    });

    const children = index.filter((e) => {
      const ePath = e.path.replace(/\/$/, '');
      return (
        ePath.startsWith(normalizedCurrent) && e.depth === segments.length + 1
      );
    });

    const parent = index.filter((e) => {
      const ePath = e.path.replace(/\/$/, '');
      return ePath === parentPath;
    });

    const contextual = [...parent, ...siblings, ...children];

    return contextual;
  }, [index, query, currentPath]);

  const rows = useMemo(() => buildRows(filtered), [filtered]);

  /* COUNT OF NAVIGABLE RESULTS (excludes labels) */
  const resultCount = useMemo(
    () => rows.filter((r) => r.kind === 'result').length,
    [rows],
  );

  /* RESET SELECTION ON QUERY CHANGE */
  useEffect(() => {
    setSelectedIndex(-1);
  }, [query]);

  /*SCROLL SELECTED INTO VIEW */
  useEffect(() => {
    if (selectedIndex < 0) return;
    resultsRef.current
      ?.querySelector(`[data-index="${selectedIndex}"]`)
      ?.scrollIntoView({ block: 'nearest' });
  }, [selectedIndex]);

  /* INPUT KEYBOARD NAVIGATION */
  const onInputKeydown = useCallback(
    (e: KeyboardEvent) => {
      if (!resultCount) return;
      if (e.key === 'ArrowDown') {
        e.preventDefault();
        setSelectedIndex((i) => (i + 1) % resultCount);
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        setSelectedIndex((i) => (i <= 0 ? resultCount - 1 : i - 1));
      } else if (e.key === 'Enter') {
        e.preventDefault();
        const idx = selectedRef.current;
        const target = rows.find((r) => r.kind === 'result' && r.index === idx);
        if (target?.entry) {
          window.location.href = target.entry.path;
        }
      }
    },
    [resultCount, rows],
  );

  /* RENDER */
  return (
    <>
      <button
        class={styles.trigger}
        onClick={open}
        aria-label="Open Navigation"
      >
        <span>Navigate…</span>
        <kbd class={styles.kbd}>⌘K</kbd>
      </button>
      {isOpen && <div class={styles.backdrop} onClick={close} />}
      {isOpen && (
        <div
          class={styles.palette}
          role="dialog"
          aria-label="Navigation palette"
        >
          <div class={styles.header}>
            <span class={styles.icon}>⟩</span>
            <input
              ref={inputRef}
              class={styles.input}
              type="text"
              placeholder="Where do you want to go?"
              autocomplete="off"
              spellcheck={false}
              value={query}
              onInput={(e) => setQuery((e.target as HTMLInputElement).value)}
              onKeyDown={onInputKeydown}
            />
            <kbd class={styles.esc}>esc</kbd>
          </div>
          <div class={styles.results} ref={resultsRef} role="listbox">
            {rows.map((row) =>
              row.kind === 'label' ? (
                <div key={row.key} class={styles.groupLabel}>
                  {row.group}
                </div>
              ) : (
                <a
                  key={row.key}
                  class={`${styles.result}${row.index === selectedIndex ? ` ${styles.selected}` : ''}`}
                  href={row.entry!.path}
                  role="option"
                  data-index={row.index}
                  aria-selected={row.index === selectedIndex}
                >
                  <span class={styles.type}>
                    {TYPE_LABELS[row.entry!.type] ?? row.entry!.type}
                  </span>
                  <span class={styles.label}>{row.entry!.label}</span>
                  <span class={styles.path}>{row.entry!.path}</span>
                  <span class={styles.arrow}>→</span>
                </a>
              ),
            )}
            {resultCount === 0 && (
              <div class={styles.empty}>No results found</div>
            )}
          </div>
          <div class={styles.footer}>
            <div class={styles.group}>
              <span>
                <kbd>↑</kbd>
                <kbd>↓</kbd> navigate
              </span>
              <span>
                <kbd>↵</kbd> open
              </span>
            </div>
            <span>
              <kbd>esc</kbd> close
            </span>
          </div>
        </div>
      )}
    </>
  );
}
