"use client";

import {
  Extension,
  type Editor,
  type Range,
  ReactRenderer,
} from "@tiptap/react";
import Suggestion, { type SuggestionKeyDownProps } from "@tiptap/suggestion";
import { PluginKey } from "@tiptap/pm/state";
import * as React from "react";
import {
  Heading1,
  Heading2,
  Heading3,
  List,
  ListOrdered,
  ListTodo,
  ImageIcon,
  Code2,
  Table2,
  Minus,
  MessageSquareQuote,
  ChevronRight,
  Sparkles,
  Type,
} from "lucide-react";

import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";

export const slashCommandPluginKey = new PluginKey("slashCommand");

export type SlashItem = {
  title: string;
  description: string;
  section: string;
  icon: React.ComponentType<{ className?: string }>;
  command: (opts: { editor: Editor; range: Range }) => void;
};

/** TipTap chain typing does not merge all extension commands on the base Editor type. */
function afterSlashDelete(editor: Editor, range: Range) {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return editor.chain().focus().deleteRange(range) as any;
}

function getSlashItems(): SlashItem[] {
  return [
    {
      title: "Paragraph",
      description: "Plain text block",
      section: "Text",
      icon: Type,
      command: ({ editor, range }) => {
        afterSlashDelete(editor, range).setParagraph().run();
      },
    },
    {
      title: "Heading 1",
      description: "Large section title",
      section: "Text",
      icon: Heading1,
      command: ({ editor, range }) => {
        afterSlashDelete(editor, range).setHeading({ level: 1 }).run();
      },
    },
    {
      title: "Heading 2",
      description: "Medium section title",
      section: "Text",
      icon: Heading2,
      command: ({ editor, range }) => {
        afterSlashDelete(editor, range).setHeading({ level: 2 }).run();
      },
    },
    {
      title: "Heading 3",
      description: "Small section title",
      section: "Text",
      icon: Heading3,
      command: ({ editor, range }) => {
        afterSlashDelete(editor, range).setHeading({ level: 3 }).run();
      },
    },
    {
      title: "Bullet List",
      description: "Unordered list",
      section: "Lists",
      icon: List,
      command: ({ editor, range }) => {
        afterSlashDelete(editor, range).toggleBulletList().run();
      },
    },
    {
      title: "Numbered List",
      description: "Ordered list",
      section: "Lists",
      icon: ListOrdered,
      command: ({ editor, range }) => {
        afterSlashDelete(editor, range).toggleOrderedList().run();
      },
    },
    {
      title: "Task List",
      description: "Checklist with tasks",
      section: "Lists",
      icon: ListTodo,
      command: ({ editor, range }) => {
        afterSlashDelete(editor, range).toggleTaskList().run();
      },
    },
    {
      title: "Image",
      description: "Embed an image by URL",
      section: "Media",
      icon: ImageIcon,
      command: ({ editor, range }) => {
        const url = window.prompt("Image URL");
        if (!url) return;
        afterSlashDelete(editor, range).setImage({ src: url }).run();
      },
    },
    {
      title: "Code Block",
      description: "Syntax-highlighted code",
      section: "Media",
      icon: Code2,
      command: ({ editor, range }) => {
        afterSlashDelete(editor, range).toggleCodeBlock().run();
      },
    },
    {
      title: "Table",
      description: "3×3 table with header",
      section: "Media",
      icon: Table2,
      command: ({ editor, range }) => {
        afterSlashDelete(editor, range)
          .insertTable({ rows: 3, cols: 3, withHeaderRow: true })
          .run();
      },
    },
    {
      title: "Horizontal Rule",
      description: "Divider line",
      section: "Media",
      icon: Minus,
      command: ({ editor, range }) => {
        afterSlashDelete(editor, range).setHorizontalRule().run();
      },
    },
    {
      title: "Callout",
      description: "Highlighted callout block",
      section: "Advanced",
      icon: Sparkles,
      command: ({ editor, range }) => {
        afterSlashDelete(editor, range)
          .insertContent(
            '<blockquote class="notion-callout border-l-4 border-teal pl-4 py-2 my-2 rounded-r-md bg-muted/50"><p>Callout</p></blockquote>',
          )
          .run();
      },
    },
    {
      title: "Toggle",
      description: "Collapsible section (placeholder)",
      section: "Advanced",
      icon: ChevronRight,
      command: ({ editor, range }) => {
        afterSlashDelete(editor, range)
          .insertContent({
            type: "heading",
            attrs: { level: 3 },
            content: [{ type: "text", text: "Toggle heading" }],
          })
          .run();
      },
    },
    {
      title: "Quote",
      description: "Blockquote citation",
      section: "Advanced",
      icon: MessageSquareQuote,
      command: ({ editor, range }) => {
        afterSlashDelete(editor, range).toggleBlockquote().run();
      },
    },
  ];
}

function filterItems(query: string): SlashItem[] {
  const q = query.trim().toLowerCase();
  const all = getSlashItems();
  if (!q) return all;
  return all.filter(
    (item) =>
      item.title.toLowerCase().includes(q) ||
      item.description.toLowerCase().includes(q) ||
      item.section.toLowerCase().includes(q),
  );
}

export type SlashMenuProps = {
  items: SlashItem[];
  command: (item: SlashItem) => void;
  editor: Editor;
};

export type SlashMenuHandle = {
  onKeyDown: (props: SuggestionKeyDownProps) => boolean;
};

export const SlashMenu = React.forwardRef<SlashMenuHandle, SlashMenuProps>(
  function SlashMenu({ items, command }, ref) {
    const [selected, setSelected] = React.useState(0);

    React.useEffect(() => {
      setSelected(0);
    }, [items]);

    const grouped = React.useMemo(() => {
      const map = new Map<string, SlashItem[]>();
      for (const item of items) {
        const list = map.get(item.section) ?? [];
        list.push(item);
        map.set(item.section, list);
      }
      return map;
    }, [items]);

    React.useImperativeHandle(ref, () => ({
      onKeyDown: ({ event }) => {
        if (items.length === 0) return false;
        if (event.key === "ArrowDown") {
          event.preventDefault();
          setSelected((i) => (i + 1) % Math.max(items.length, 1));
          return true;
        }
        if (event.key === "ArrowUp") {
          event.preventDefault();
          setSelected((i) =>
            i === 0 ? Math.max(items.length - 1, 0) : i - 1,
          );
          return true;
        }
        if (event.key === "Enter") {
          event.preventDefault();
          const item = items[selected];
          if (item) command(item);
          return true;
        }
        return false;
      },
    }));

    if (items.length === 0) {
      return (
        <div className="animate-in fade-in-0 zoom-in-95 duration-150 z-[100] w-72 rounded-lg border border-border bg-popover p-3 text-sm text-muted-foreground shadow-lg">
          No matching commands
        </div>
      );
    }

    let flatIndex = 0;

    return (
      <div
        className={cn(
          "animate-in fade-in-0 zoom-in-95 duration-150 z-[100] w-80 overflow-hidden rounded-lg border border-border bg-popover shadow-lg",
        )}
      >
        <ScrollArea className="max-h-[min(320px,50vh)]">
          <div className="p-1.5">
            {Array.from(grouped.entries()).map(
              ([section, sectionItems], sectionIndex, sections) => (
                <div key={section}>
                  <div className="px-2 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
                    {section}
                  </div>
                  <div className="space-y-0.5">
                    {sectionItems.map((item) => {
                      const idx = flatIndex++;
                      const Icon = item.icon;
                      const isActive = idx === selected;
                      return (
                        <Button
                          key={`${section}-${item.title}`}
                          type="button"
                          variant="ghost"
                          className={cn(
                            "h-auto w-full justify-start gap-3 px-2 py-2 text-left font-normal",
                            isActive &&
                              "bg-primary/10 text-primary dark:bg-primary/20",
                          )}
                          onClick={() => command(item)}
                          onMouseEnter={() => setSelected(idx)}
                        >
                          <span
                            className={cn(
                              "flex size-9 shrink-0 items-center justify-center rounded-md border border-border bg-muted/60",
                              isActive && "border-primary/40 bg-primary/5",
                            )}
                          >
                            <Icon className="size-4 text-foreground" />
                          </span>
                          <span className="min-w-0 flex-1">
                            <span className="block text-sm font-medium leading-tight">
                              {item.title}
                            </span>
                            <span className="text-xs text-muted-foreground">
                              {item.description}
                            </span>
                          </span>
                        </Button>
                      );
                    })}
                  </div>
                  {sectionIndex < sections.length - 1 ? (
                    <Separator className="my-2 opacity-50" />
                  ) : null}
                </div>
              ),
            )}
          </div>
        </ScrollArea>
      </div>
    );
  },
);

SlashMenu.displayName = "SlashMenu";

export const SlashCommand = Extension.create({
  name: "slashCommand",

  addProseMirrorPlugins() {
    return [
      Suggestion({
        editor: this.editor,
        pluginKey: slashCommandPluginKey,
        char: "/",
        allowSpaces: true,
        startOfLine: false,
        command: ({ editor: ed, range, props }) => {
          props.command({ editor: ed, range });
        },
        items: ({ query }) => filterItems(query),
        render: () => {
          let component: ReactRenderer | null = null;

          return {
            onStart: (props) => {
              component = new ReactRenderer(SlashMenu, {
                props: {
                  ...props,
                  command: (item: SlashItem) => {
                    item.command({
                      editor: props.editor,
                      range: props.range,
                    });
                  },
                },
                editor: props.editor,
              });
              component.element.style.position = "absolute";
              component.element.style.zIndex = "100";
              document.body.appendChild(component.element);
              updatePosition(props);
            },
            onUpdate: (props) => {
              component?.updateProps({
                ...props,
                command: (item: SlashItem) => {
                  item.command({
                    editor: props.editor,
                    range: props.range,
                  });
                },
              });
              updatePosition(props);
            },
            onExit: () => {
              component?.destroy();
              component = null;
            },
            onKeyDown: (keyProps) => {
              const ref = component?.ref as SlashMenuHandle | null;
              if (ref?.onKeyDown) {
                return ref.onKeyDown(keyProps);
              }
              return false;
            },
          };

          function updatePosition(p: {
            clientRect?: (() => DOMRect | null) | null;
          }) {
            if (!component) return;
            const rect = p.clientRect?.();
            if (!rect) return;
            const el = component.element;
            el.style.left = `${rect.left}px`;
            el.style.top = `${rect.bottom + 6}px`;
          }
        },
      }),
    ];
  },
});
