import { getLatexEditorCommandDefinition } from '@learnitive/latex-source';
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandItem,
  CommandList,
  CommandMenuRow,
  CommandSeparator,
} from '@learnitive/ui-components';
import {
  BookOpen,
  Braces,
  Code,
  FileText,
  Heading1,
  Heading2,
  Heading3,
  Image,
  Link,
  List,
  ListOrdered,
  Pilcrow,
  Sigma,
  Table,
} from 'lucide-react';
import React from 'react';

export type LatexVisualInsertCommand =
  | 'paragraph'
  | 'section'
  | 'subsection'
  | 'subsubsection'
  | 'bullet-list'
  | 'numbered-list'
  | 'inline-math'
  | 'display-math'
  | 'equation'
  | 'table'
  | 'figure'
  | 'citation'
  | 'reference'
  | 'label'
  | 'footnote'
  | 'code-block'
  | 'raw-latex'
  | 'page-break'
  | 'newline';

type Item = {
  id: LatexVisualInsertCommand;
  semanticId?: string;
  value: string;
  title: string;
  description: string;
  icon: typeof Pilcrow;
};

type Group = { heading: string; items: Item[] };

const GROUPS: Group[] = [
  {
    heading: 'BASIC',
    items: [
      { id: 'paragraph', value: 'paragraph text plain body', title: 'Text', description: 'Insert an editable paragraph', icon: Pilcrow },
      { id: 'newline', semanticId: 'newline', value: 'newline line break hard break shift enter', title: 'Line break', description: 'Insert a source-safe \\newline{}', icon: Pilcrow },
      { id: 'section', semanticId: 'section', value: 'section heading h1 title', title: 'Section', description: 'Insert \\section{...}', icon: Heading1 },
      { id: 'subsection', semanticId: 'subsection', value: 'subsection heading h2', title: 'Subsection', description: 'Insert \\subsection{...}', icon: Heading2 },
      { id: 'subsubsection', semanticId: 'subsubsection', value: 'subsubsection heading h3', title: 'Subsubsection', description: 'Insert \\subsubsection{...}', icon: Heading3 },
    ],
  },
  {
    heading: 'STRUCTURE',
    items: [
      { id: 'bullet-list', semanticId: 'itemize', value: 'bullet list itemize unordered', title: 'Bullet list', description: 'Insert an itemize environment', icon: List },
      { id: 'numbered-list', semanticId: 'enumerate', value: 'numbered list enumerate ordered', title: 'Numbered list', description: 'Insert an enumerate environment', icon: ListOrdered },
      { id: 'inline-math', semanticId: 'inline-math', value: 'inline math latex formula', title: 'Inline Math', description: 'Insert inline math and open MathLive', icon: Sigma },
      { id: 'equation', semanticId: 'equation', value: 'equation math latex display formula', title: 'Equation', description: 'Insert display math and open MathLive', icon: Sigma },
      { id: 'table', semanticId: 'table', value: 'table tabular grid', title: 'Table', description: 'Insert a simple LaTeX table', icon: Table },
      { id: 'figure', semanticId: 'figure', value: 'figure image upload graphic includegraphics', title: 'Figure / image', description: 'Import an image and insert a figure', icon: Image },
      { id: 'code-block', semanticId: 'verbatim', value: 'code block verbatim source', title: 'Code block', description: 'Insert a verbatim environment', icon: Code },
    ],
  },
  {
    heading: 'ACADEMIC',
    items: [
      { id: 'citation', semanticId: 'citation', value: 'citation cite bibliography reference paper', title: 'Citation', description: 'Choose entries from the project bibliography', icon: BookOpen },
      { id: 'reference', semanticId: 'reference', value: 'reference ref label link cross reference', title: 'Reference', description: 'Choose an existing project label', icon: Link },
      { id: 'label', semanticId: 'label', value: 'label anchor cross reference target', title: 'Label', description: 'Insert \\label{...}', icon: Braces },
      { id: 'footnote', semanticId: 'footnote', value: 'footnote note academic', title: 'Footnote', description: 'Insert \\footnote{...}', icon: FileText },
    ],
  },
  {
    heading: 'ADVANCED',
    items: [
      { id: 'raw-latex', value: 'raw latex tex advanced command', title: 'Raw LaTeX', description: 'Insert a protected LaTeX placeholder', icon: Braces },
      { id: 'page-break', value: 'page break newpage', title: 'Page break', description: 'Insert \\newpage', icon: FileText },
    ],
  },
];

function hydratedItem(item: Item): Item {
  if (!item.semanticId) return item;
  const semantic = getLatexEditorCommandDefinition(item.semanticId);
  if (!semantic) return item;
  return {
    ...item,
    title: semantic.label,
    value: `${item.value} ${semantic.id} ${semantic.slash.join(' ')}`,
  };
}

function matches(item: Item, query: string) {
  const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
  if (terms.length === 0) return true;
  const haystack = `${item.title} ${item.value} ${item.description}`.toLowerCase();
  return terms.every((term) => haystack.includes(term));
}

export function getLatexVisualSlashGroups(query = ''): Group[] {
  return GROUPS.map((group) => ({
    ...group,
    items: group.items.map(hydratedItem).filter((item) => matches(item, query)),
  })).filter((group) => group.items.length > 0);
}

export function getLatexVisualSlashCommands(query = ''): LatexVisualInsertCommand[] {
  return getLatexVisualSlashGroups(query).flatMap((group) => group.items.map((item) => item.id));
}

export function getLatexVisualSlashOptionCount(query = '') {
  return getLatexVisualSlashCommands(query).length;
}

export function getLatexVisualSlashCommandAt(query: string, index: number) {
  const commands = getLatexVisualSlashCommands(query);
  if (commands.length === 0) return undefined;
  return commands[Math.max(0, Math.min(index, commands.length - 1))];
}

export function LatexVisualSlashMenu({
  anchor,
  query = '',
  selectedIndex = 0,
  onSelect,
  onClose,
}: {
  anchor: { left: number; top: number; bottom: number; right: number };
  query?: string;
  selectedIndex?: number;
  onSelect: (command: LatexVisualInsertCommand) => void;
  onClose: () => void;
}) {
  const rootRef = React.useRef<HTMLDivElement | null>(null);
  const groups = React.useMemo(() => getLatexVisualSlashGroups(query), [query]);
  const flat = groups.flatMap((group) => group.items);
  const safeSelectedIndex = flat.length
    ? Math.max(0, Math.min(selectedIndex, flat.length - 1))
    : 0;

  React.useEffect(() => {
    const onPointer = (event: PointerEvent) => {
      if (rootRef.current?.contains(event.target as Node)) return;
      onClose();
    };
    document.addEventListener('pointerdown', onPointer, true);
    return () => document.removeEventListener('pointerdown', onPointer, true);
  }, [onClose]);

  React.useEffect(() => {
    // Query changes can replace the result set even when the selected index
    // remains numerically identical, so keep it as an intentional trigger.
    void query;
    rootRef.current
      ?.querySelector<HTMLElement>(`[data-slash-index="${safeSelectedIndex}"]`)
      ?.scrollIntoView({ block: 'nearest' });
  }, [safeSelectedIndex, query]);

  const left = Math.min(Math.max(8, anchor.left), Math.max(8, window.innerWidth - 304));
  const belowTop = anchor.bottom + 6;
  const top = belowTop + 390 < window.innerHeight ? belowTop : Math.max(8, anchor.top - 390);
  let itemIndex = 0;

  return (
    <div
      ref={rootRef}
      className="fixed z-[110] w-72"
      style={{ left, top }}
      data-testid="latex-visual-slash-menu"
      role="dialog"
      aria-label="LaTeX insert menu"
      onMouseDown={(event) => event.preventDefault()}
    >
      <Command className="overflow-hidden rounded-lg border bg-popover text-popover-foreground shadow-lg" shouldFilter={false}>
        <div className="border-b px-3 py-2 text-xs text-muted-foreground" data-testid="latex-visual-slash-query">
          <span className="font-mono text-foreground">/{query}</span>
          <span className="ml-2">Type to filter · ↑↓ navigate · Enter select · Esc close</span>
        </div>
        {flat.length === 0 && <CommandEmpty>No matching LaTeX block.</CommandEmpty>}
        <CommandList className="max-h-[330px] overscroll-contain">
          {groups.map((group, groupIndex) => (
            <React.Fragment key={group.heading}>
              {groupIndex > 0 && <CommandSeparator />}
              <CommandGroup heading={group.heading}>
                {group.items.map((item) => {
                  const Icon = item.icon;
                  const index = itemIndex++;
                  const selected = index === safeSelectedIndex;
                  return (
                    <CommandItem
                      key={item.id}
                      value={item.value}
                      data-slash-index={index}
                      aria-selected={selected}
                      className={selected ? 'bg-accent text-accent-foreground' : undefined}
                      onSelect={() => onSelect(item.id)}
                    >
                      <CommandMenuRow
                        icon={<Icon aria-hidden />}
                        title={item.title}
                        description={item.description}
                      />
                    </CommandItem>
                  );
                })}
              </CommandGroup>
            </React.Fragment>
          ))}
        </CommandList>
      </Command>
    </div>
  );
}
