"use client";

import {
  useState,
  useEffect,
  useRef,
  useCallback,
  useLayoutEffect,
  forwardRef,
  useImperativeHandle,
} from "react";
import { Extension } from "@tiptap/core";
import Suggestion, { type SuggestionOptions } from "@tiptap/suggestion";
import { PluginKey } from "@tiptap/pm/state";
import { createPortal } from "react-dom";
import type { Editor, Range } from "@tiptap/core";
import type { SearchIndexItem } from "@/lib/search-index";
import { buildEntryLink, buildFileLink } from "@/lib/workspace-links";

// Unique plugin keys so both suggestions can coexist
const slashCommandPluginKey = new PluginKey("slashCommand");
const fileMentionPluginKey = new PluginKey("fileMention");

// --- Types ---

export type TreeNode = {
  name: string;
  path: string;
  type: "object" | "document" | "folder" | "file" | "database" | "report" | "app";
  icon?: string;
  children?: TreeNode[];
};

type SlashItem = {
  title: string;
  description?: string;
  badge?: string;
  icon: React.ReactNode;
  category: "file" | "block" | "entry";
  command: (props: { editor: Editor; range: Range }) => void;
};

/** Search function signature accepted by createWorkspaceMention. */
export type MentionSearchFn = (query: string, limit?: number) => SearchIndexItem[];

function nodeTypeIcon(type: string) {
  switch (type) {
    case "document":
      return (
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
          <path d="M14 2v4a2 2 0 0 0 2 2h4" />
        </svg>
      );
    case "object":
      return (
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <path d="M12 3v18" /><rect width="18" height="18" x="3" y="3" rx="2" /><path d="M3 9h18" /><path d="M3 15h18" />
        </svg>
      );
    case "report":
      return (
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <line x1="12" x2="12" y1="20" y2="10" />
          <line x1="18" x2="18" y1="20" y2="4" />
          <line x1="6" x2="6" y1="20" y2="14" />
        </svg>
      );
    default:
      return (
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
          <path d="M14 2v4a2 2 0 0 0 2 2h4" />
        </svg>
      );
  }
}

// --- Block command icons ---

const headingIcon = (level: number) => (
  <span className="slash-cmd-icon-text">H{level}</span>
);

const bulletListIcon = (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <line x1="8" x2="21" y1="6" y2="6" /><line x1="8" x2="21" y1="12" y2="12" /><line x1="8" x2="21" y1="18" y2="18" />
    <line x1="3" x2="3.01" y1="6" y2="6" /><line x1="3" x2="3.01" y1="12" y2="12" /><line x1="3" x2="3.01" y1="18" y2="18" />
  </svg>
);

const orderedListIcon = (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <line x1="10" x2="21" y1="6" y2="6" /><line x1="10" x2="21" y1="12" y2="12" /><line x1="10" x2="21" y1="18" y2="18" />
    <path d="M4 6h1v4" /><path d="M4 10h2" /><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1" />
  </svg>
);

const blockquoteIcon = (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V21z" />
    <path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3z" />
  </svg>
);

const codeBlockIcon = (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <polyline points="16 18 22 12 16 6" /><polyline points="8 6 2 12 8 18" />
  </svg>
);

const horizontalRuleIcon = (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <line x1="2" x2="22" y1="12" y2="12" />
  </svg>
);

const imageIcon = (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
    <circle cx="9" cy="9" r="2" />
    <path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
  </svg>
);

const tableIcon = (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M12 3v18" /><rect width="18" height="18" x="3" y="3" rx="2" /><path d="M3 9h18" /><path d="M3 15h18" />
  </svg>
);

const taskListIcon = (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <rect x="3" y="5" width="6" height="6" rx="1" /><path d="m3 17 2 2 4-4" /><line x1="13" x2="21" y1="6" y2="6" /><line x1="13" x2="21" y1="18" y2="18" />
  </svg>
);

const reportIcon = (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <line x1="12" x2="12" y1="20" y2="10" />
    <line x1="18" x2="18" y1="20" y2="4" />
    <line x1="6" x2="6" y1="20" y2="14" />
  </svg>
);

const entryIcon = (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M16 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8Z" />
    <path d="M15 3v4a2 2 0 0 0 2 2h4" />
    <path d="M10 16h4" /><path d="M10 12h4" />
  </svg>
);

// --- Build items ---

/** Convert a SearchIndexItem to a SlashItem for the @ mention popup. */
function searchItemToSlashItem(item: SearchIndexItem): SlashItem {
  if (item.kind === "entry") {
    const label = item.label || `(${item.objectName} entry)`;
    return {
      title: label,
      description: item.fields
        ? Object.entries(item.fields)
            .slice(0, 2)
            .map(([k, v]) => `${k}: ${v}`)
            .join(" | ")
        : undefined,
      badge: item.objectName,
      icon: entryIcon,
      category: "entry",
      command: ({ editor, range }: { editor: Editor; range: Range }) => {
        const href = buildEntryLink(item.objectName!, item.entryId!);
        editor
          .chain()
          .focus()
          .deleteRange(range)
          .insertContent({
            type: "text",
            text: label,
            marks: [{ type: "link", attrs: { href, target: null } }],
          })
          .run();
      },
    };
  }

  const nodeType = item.nodeType ?? (item.kind === "object" ? "object" : "file");
  const label = item.label || item.path || item.id;
  return {
    title: label,
    description: item.sublabel,
    icon: nodeTypeIcon(nodeType),
    category: "file",
    command: ({ editor, range }: { editor: Editor; range: Range }) => {
      const href = buildFileLink(item.path ?? item.id);
      editor
        .chain()
        .focus()
        .deleteRange(range)
        .insertContent({
          type: "text",
          text: label,
          marks: [
            { type: "link", attrs: { href, target: null } },
          ],
        })
        .run();
    },
  };
}

function buildBlockCommands(): SlashItem[] {
  return [
    {
      title: "Heading 1",
      description: "Large section heading",
      icon: headingIcon(1),
      category: "block",
      command: ({ editor, range }) => {
        editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
      },
    },
    {
      title: "Heading 2",
      description: "Medium section heading",
      icon: headingIcon(2),
      category: "block",
      command: ({ editor, range }) => {
        editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run();
      },
    },
    {
      title: "Heading 3",
      description: "Small section heading",
      icon: headingIcon(3),
      category: "block",
      command: ({ editor, range }) => {
        editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run();
      },
    },
    {
      title: "Bullet List",
      description: "Unordered list",
      icon: bulletListIcon,
      category: "block",
      command: ({ editor, range }) => {
        editor.chain().focus().deleteRange(range).toggleBulletList().run();
      },
    },
    {
      title: "Numbered List",
      description: "Ordered list",
      icon: orderedListIcon,
      category: "block",
      command: ({ editor, range }) => {
        editor.chain().focus().deleteRange(range).toggleOrderedList().run();
      },
    },
    {
      title: "Task List",
      description: "Checklist with checkboxes",
      icon: taskListIcon,
      category: "block",
      command: ({ editor, range }) => {
        editor.chain().focus().deleteRange(range).toggleTaskList().run();
      },
    },
    {
      title: "Blockquote",
      description: "Quote block",
      icon: blockquoteIcon,
      category: "block",
      command: ({ editor, range }) => {
        editor.chain().focus().deleteRange(range).toggleBlockquote().run();
      },
    },
    {
      title: "Code Block",
      description: "Fenced code block",
      icon: codeBlockIcon,
      category: "block",
      command: ({ editor, range }) => {
        editor.chain().focus().deleteRange(range).toggleCodeBlock().run();
      },
    },
    {
      title: "Horizontal Rule",
      description: "Divider line",
      icon: horizontalRuleIcon,
      category: "block",
      command: ({ editor, range }) => {
        editor.chain().focus().deleteRange(range).setHorizontalRule().run();
      },
    },
    {
      title: "Image",
      description: "Insert image from URL",
      icon: imageIcon,
      category: "block",
      command: ({ editor, range }) => {
        const url = window.prompt("Image URL:");
        if (url) {
          editor.chain().focus().deleteRange(range).setImage({ src: url }).run();
        }
      },
    },
    {
      title: "Table",
      description: "Insert a 3x3 table",
      icon: tableIcon,
      category: "block",
      command: ({ editor, range }) => {
        editor
          .chain()
          .focus()
          .deleteRange(range)
          .insertTable({ rows: 3, cols: 3, withHeaderRow: true })
          .run();
      },
    },
    {
      title: "Report Chart",
      description: "Interactive report-json block",
      icon: reportIcon,
      category: "block",
      command: ({ editor, range }) => {
        const template = JSON.stringify(
          {
            version: 1,
            title: "New Report",
            panels: [
              {
                id: "panel-1",
                title: "Chart",
                type: "bar",
                sql: "SELECT 1 as x, 10 as y",
                mapping: { xAxis: "x", yAxis: ["y"] },
              },
            ],
          },
          null,
          2,
        );
        editor
          .chain()
          .focus()
          .deleteRange(range)
          .insertContent({
            type: "reportBlock",
            attrs: { config: template },
          })
          .run();
      },
    },
  ];
}

// buildFileItems removed -- replaced by searchItemToSlashItem + search index

// --- Popup Component ---

type CommandListRef = {
  onKeyDown: (props: { event: KeyboardEvent }) => boolean;
};

type CommandListProps = {
  items: SlashItem[];
  command: (item: SlashItem) => void;
};

const CommandList = forwardRef<CommandListRef, CommandListProps>(
  ({ items, command }, ref) => {
    const [selectedIndex, setSelectedIndex] = useState(0);
    const listRef = useRef<HTMLDivElement>(null);

    useEffect(() => {
      setSelectedIndex(0);
    }, [items]);

    // Scroll selected into view
    useEffect(() => {
      const el = listRef.current?.children[selectedIndex] as HTMLElement | undefined;
      el?.scrollIntoView({ block: "nearest" });
    }, [selectedIndex]);

    const selectItem = useCallback(
      (index: number) => {
        const item = items[index];
        if (item) {
          command(item);
        }
      },
      [items, command],
    );

    useImperativeHandle(ref, () => ({
      onKeyDown: ({ event }: { event: KeyboardEvent }) => {
        if (event.key === "ArrowUp") {
          setSelectedIndex((i) => (i + items.length - 1) % items.length);
          return true;
        }
        if (event.key === "ArrowDown") {
          setSelectedIndex((i) => (i + 1) % items.length);
          return true;
        }
        if (event.key === "Enter") {
          selectItem(selectedIndex);
          return true;
        }
        return false;
      },
    }));

    if (items.length === 0) {
      return (
        <div className="slash-cmd-popup">
          <div className="slash-cmd-empty">No results</div>
        </div>
      );
    }

    return (
      <div className="slash-cmd-popup" ref={listRef}>
        {items.map((item, index) => (
          <button
            type="button"
            key={`${item.category}-${item.title}-${index}`}
            className={`slash-cmd-item ${index === selectedIndex ? "slash-cmd-item-active" : ""}`}
            onClick={() => selectItem(index)}
            onMouseEnter={() => setSelectedIndex(index)}
          >
            <span className="slash-cmd-item-icon">{item.icon}</span>
            <span className="slash-cmd-item-body">
              <span className="slash-cmd-item-title">
                {item.title}
                {item.badge && (
                  <span className="slash-cmd-item-badge">{item.badge}</span>
                )}
              </span>
              {item.description && (
                <span className="slash-cmd-item-desc">{item.description}</span>
              )}
            </span>
          </button>
        ))}
      </div>
    );
  },
);

CommandList.displayName = "CommandList";

// --- Floating wrapper that renders into a portal ---

function SlashPopupRenderer({
  items,
  command,
  clientRect,
  componentRef,
}: {
  items: SlashItem[];
  command: (item: SlashItem) => void;
  clientRect: (() => DOMRect | null) | null | undefined;
  componentRef: React.RefObject<CommandListRef | null>;
}) {
  const popupRef = useRef<HTMLDivElement>(null);

  // Position popup near the cursor
  useLayoutEffect(() => {
    if (!popupRef.current || !clientRect) {return;}
    const rect = clientRect();
    if (!rect) {return;}
    const el = popupRef.current;
    el.style.position = "fixed";
    el.style.left = `${rect.left}px`;
    el.style.top = `${rect.bottom + 4}px`;
    el.style.zIndex = "50";
  }, [clientRect, items]);

  return createPortal(
    <div ref={popupRef}>
      <CommandList ref={componentRef} items={items} command={command} />
    </div>,
    document.body,
  );
}

// --- Shared suggestion render factory ---

function createSuggestionRenderer() {
  return () => {
    let container: HTMLDivElement | null = null;
    let root: ReturnType<typeof import("react-dom/client").createRoot> | null = null;
    const componentRef: React.RefObject<CommandListRef | null> = { current: null };

    return {
      onStart: (props: {
        items: SlashItem[];
        command: (item: SlashItem) => void;
        clientRect?: (() => DOMRect | null) | null;
      }) => {
        container = document.createElement("div");
        document.body.appendChild(container);

        void import("react-dom/client").then(({ createRoot }) => {
          root = createRoot(container!);
          root.render(
            <SlashPopupRenderer
              items={props.items}
              command={props.command}
              clientRect={props.clientRect}
              componentRef={componentRef}
            />,
          );
        });
      },
      onUpdate: (props: {
        items: SlashItem[];
        command: (item: SlashItem) => void;
        clientRect?: (() => DOMRect | null) | null;
      }) => {
        root?.render(
          <SlashPopupRenderer
            items={props.items}
            command={props.command}
            clientRect={props.clientRect}
            componentRef={componentRef}
          />,
        );
      },
      onKeyDown: (props: { event: KeyboardEvent }) => {
        if (props.event.key === "Escape") {
          root?.unmount();
          container?.remove();
          container = null;
          root = null;
          return true;
        }
        return componentRef.current?.onKeyDown(props) ?? false;
      },
      onExit: () => {
        root?.unmount();
        container?.remove();
        container = null;
        root = null;
      },
    };
  };
}

// --- Extension factories ---

/**
 * "/" slash command -- markdown block commands only (headings, lists, code, etc.)
 */
export function createSlashCommand() {
  const blockCommands = buildBlockCommands();

  return Extension.create({
    name: "slashCommand",

    addOptions() {
      return {
        suggestion: {
          char: "/",
          pluginKey: slashCommandPluginKey,
          startOfLine: false,
          command: ({ editor, range, props: item }: { editor: Editor; range: Range; props: SlashItem }) => {
            item.command({ editor, range });
          },
          items: ({ query }: { query: string }) => {
            const q = query.toLowerCase();
            if (!q) {return blockCommands;}
            return blockCommands.filter(
              (item) =>
                item.title.toLowerCase().includes(q) ||
                (item.description?.toLowerCase().includes(q) ?? false),
            );
          },
          render: createSuggestionRenderer(),
        } satisfies Partial<SuggestionOptions<SlashItem>>,
      };
    },

    addProseMirrorPlugins() {
      return [
        Suggestion({
          editor: this.editor,
          ...this.options.suggestion,
        }),
      ];
    },
  });
}

/**
 * "@" mention command -- unified workspace search (files + objects + entries).
 * Accepts a search function from the useSearchIndex hook for fast fuzzy matching.
 */
export function createWorkspaceMention(searchFn: MentionSearchFn) {
  return Extension.create({
    name: "fileMention",

    addOptions() {
      return {
        suggestion: {
          char: "@",
          pluginKey: fileMentionPluginKey,
          startOfLine: false,
          command: ({ editor, range, props: item }: { editor: Editor; range: Range; props: SlashItem }) => {
            item.command({ editor, range });
          },
          items: ({ query }: { query: string }) => {
            const results = searchFn(query, 15);
            return results.map(searchItemToSlashItem);
          },
          render: createSuggestionRenderer(),
        } satisfies Partial<SuggestionOptions<SlashItem>>,
      };
    },

    addProseMirrorPlugins() {
      return [
        Suggestion({
          editor: this.editor,
          ...this.options.suggestion,
        }),
      ];
    },
  });
}

/**
 * "@" mention command -- legacy file-only cross-linking (fallback).
 * @deprecated Use createWorkspaceMention with useSearchIndex instead.
 */
export function createFileMention(tree: TreeNode[]) {
  function flattenTree(nodes: TreeNode[]): TreeNode[] {
    const result: TreeNode[] = [];
    for (const node of nodes) {
      if (node.type !== "folder") {result.push(node);}
      if (node.children) {result.push(...flattenTree(node.children));}
    }
    return result;
  }

  const flatFiles = flattenTree(tree);
  const searchItems: SearchIndexItem[] = flatFiles.map((node) => ({
    id: node.path,
    label: node.name.replace(/\.md$/, ""),
    sublabel: node.path,
    kind: (node.type === "object" ? "object" : "file") as SearchIndexItem["kind"],
    path: node.path,
    nodeType: node.type as SearchIndexItem["nodeType"],
  }));

  const searchFn: MentionSearchFn = (query, limit = 15) => {
    if (!query) {return searchItems.slice(0, limit);}
    const q = query.toLowerCase();
    return searchItems
      .filter(
        (item) =>
          item.label.toLowerCase().includes(q) ||
          (item.sublabel?.toLowerCase().includes(q) ?? false),
      )
      .slice(0, limit);
  };

  return createWorkspaceMention(searchFn);
}
