import { Extension } from '@tiptap/core';
import type { Editor, Range } from '@tiptap/core';
import Suggestion, { type SuggestionProps, type SuggestionKeyDownProps } from '@tiptap/suggestion';
import { ReactRenderer } from '@tiptap/react';

import { SlashMenuList } from './SlashMenuList.js';

export interface SlashItem {
  title: string;
  description: string;
  aliases?: string[];
  command: (props: { editor: Editor; range: Range }) => void;
}

/** The built-in block actions offered by the slash menu. */
export const defaultSlashItems: SlashItem[] = [
  {
    title: 'Heading 1',
    description: 'Big section heading',
    aliases: ['h1', 'title'],
    command: ({ editor, range }) =>
      editor.chain().focus().deleteRange(range).setNode('heading', { level: 1 }).run(),
  },
  {
    title: 'Heading 2',
    description: 'Medium section heading',
    aliases: ['h2', 'subtitle'],
    command: ({ editor, range }) =>
      editor.chain().focus().deleteRange(range).setNode('heading', { level: 2 }).run(),
  },
  {
    title: 'Bullet list',
    description: 'Unordered list',
    aliases: ['ul', 'unordered'],
    command: ({ editor, range }) =>
      editor.chain().focus().deleteRange(range).toggleBulletList().run(),
  },
  {
    title: 'Numbered list',
    description: 'Ordered list',
    aliases: ['ol', 'ordered'],
    command: ({ editor, range }) =>
      editor.chain().focus().deleteRange(range).toggleOrderedList().run(),
  },
  {
    title: 'Quote',
    description: 'Blockquote',
    aliases: ['blockquote'],
    command: ({ editor, range }) =>
      editor.chain().focus().deleteRange(range).toggleBlockquote().run(),
  },
  {
    title: 'Code block',
    description: 'Monospaced code',
    aliases: ['code', 'pre'],
    command: ({ editor, range }) =>
      editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
  },
];

/** Pure, case-insensitive filter over title + aliases — unit-testable. */
export function filterSlashItems(
  query: string,
  items: SlashItem[] = defaultSlashItems
): SlashItem[] {
  const q = query.trim().toLowerCase();
  if (!q) return items;
  return items.filter(
    item =>
      item.title.toLowerCase().includes(q) ||
      (item.aliases ?? []).some(a => a.toLowerCase().includes(q))
  );
}

/**
 * `/`-triggered command menu. Built on `@tiptap/suggestion`; the popup is a
 * React component positioned at the caret. Only mounts while the user is
 * typing a slash command, so it never appears on first render.
 */
export const SlashCommands = Extension.create({
  name: 'slashCommands',

  addProseMirrorPlugins() {
    return [
      Suggestion<SlashItem>({
        editor: this.editor,
        char: '/',
        startOfLine: false,
        items: ({ query }) => filterSlashItems(query),
        command: ({ editor, range, props }) => props.command({ editor, range }),
        render: () => {
          let component: ReactRenderer<{
            onKeyDown: (p: SuggestionKeyDownProps) => boolean;
          }> | null = null;
          let popup: HTMLDivElement | null = null;

          const position = (props: SuggestionProps<SlashItem>) => {
            if (!popup) return;
            const rect = props.clientRect?.();
            if (!rect) return;
            popup.style.left = `${rect.left}px`;
            popup.style.top = `${rect.bottom + 4}px`;
          };

          return {
            onStart: (props: SuggestionProps<SlashItem>) => {
              component = new ReactRenderer(SlashMenuList, { props, editor: props.editor });
              popup = document.createElement('div');
              popup.setAttribute('data-slash-menu', 'true');
              popup.style.position = 'absolute';
              popup.style.zIndex = '50';
              popup.appendChild(component.element);
              document.body.appendChild(popup);
              position(props);
            },
            onUpdate: (props: SuggestionProps<SlashItem>) => {
              component?.updateProps(props);
              position(props);
            },
            onKeyDown: (props: SuggestionKeyDownProps) => {
              if (props.event.key === 'Escape') {
                popup?.remove();
                return true;
              }
              return component?.ref?.onKeyDown(props) ?? false;
            },
            onExit: () => {
              popup?.remove();
              popup = null;
              component?.destroy();
              component = null;
            },
          };
        },
      }),
    ];
  },
});
