"use client";

import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react";
import { EditorContent, useEditor } from "@tiptap/react";
import { Extension } from "@tiptap/core";
import StarterKit from "@tiptap/starter-kit";
import type { BlocksV1 } from "@slackwsh/contracts";
import { blocksToPlainText, sanitizeMessageBlocks } from "@slackwsh/core";

export interface RichTextValue {
  text: string;
  blocks: BlocksV1;
}

export interface RichTextEditorHandle {
  focus: () => void;
  clear: () => void;
  insertText: (text: string) => void;
  /** Replace the whole draft with plain text (used by AI rewrite). */
  setPlainText: (text: string) => void;
  replaceCurrentMention: (text: string) => void;
  getValue: () => RichTextValue;
}

interface RichTextEditorProps {
  value: string;
  blocks?: unknown;
  placeholder: string;
  ariaLabel: string;
  className?: string;
  compact?: boolean;
  disabled?: boolean;
  autoFocus?: boolean;
  onChange: (value: RichTextValue, cursor: number) => void;
  onSubmit: () => void;
  onPasteFiles?: (files: File[]) => void;
  onSpecialKey?: (event: KeyboardEvent) => boolean;
}

function LinkIcon() {
  return <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 15l6-6M7 18l-1 1a3.5 3.5 0 0 1-5-5l4-4a3.5 3.5 0 0 1 5 0M17 6l1-1a3.5 3.5 0 1 1 5 5l-4 4a3.5 3.5 0 0 1-5 0" /></svg>;
}

function OrderedListIcon() {
  return <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6h13M9 12h13M9 18h13M2 5h2v3M2 8h3M2 11.5c.4-.6 2.8-.8 2.8.6 0 1-2.7 2.1-2.8 3h3M2 18h1.8c1.5 0 1.5 2 0 2H2m1.8 0c1.5 0 1.5 2 0 2H2" /></svg>;
}

function BulletListIcon() {
  return <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6h13M9 12h13M9 18h13" /><circle cx="3.5" cy="6" r="1.1" /><circle cx="3.5" cy="12" r="1.1" /><circle cx="3.5" cy="18" r="1.1" /></svg>;
}

function QuoteIcon() {
  return <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 3v18M9 7h12M9 12h9M9 17h12" /></svg>;
}

function CodeBlockIcon() {
  return <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 4H4v16h4M16 4h4v16h-4M10 9l-3 3 3 3M14 9l3 3-3 3" /></svg>;
}

const SlackKeyboardShortcuts = Extension.create({
  name: "slackKeyboardShortcuts",
  addKeyboardShortcuts() {
    return {
      "Mod-Shift-7": () => this.editor.commands.toggleOrderedList(),
      "Mod-Shift-8": () => this.editor.commands.toggleBulletList(),
      "Mod-Shift-9": () => this.editor.commands.toggleBlockquote(),
      "Mod-Shift-c": () => this.editor.commands.toggleCode(),
      "Mod-Alt-Shift-c": () => this.editor.commands.toggleCodeBlock(),
    };
  },
});

function initialDocument(blocks: unknown, text: string) {
  const sanitized = sanitizeMessageBlocks(blocks, text);
  return sanitized.doc;
}

function plainCursor(editor: NonNullable<ReturnType<typeof useEditor>>): number {
  const position = editor.state.selection.from;
  return editor.state.doc.textBetween(0, position, "\n", "\n").length;
}

export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(function RichTextEditor(
  {
    value,
    blocks,
    placeholder,
    ariaLabel,
    className = "",
    compact = false,
    disabled = false,
    autoFocus = false,
    onChange,
    onSubmit,
    onPasteFiles,
    onSpecialKey,
  },
  ref,
) {
  const [, setRevision] = useState(0);
  const extensions = useMemo(
    () => [
      StarterKit.configure({
        heading: { levels: [1, 2, 3] },
        link: { openOnClick: false, autolink: true, linkOnPaste: true, defaultProtocol: "https" },
      }),
      SlackKeyboardShortcuts,
    ],
    [],
  );

  const editor = useEditor({
    immediatelyRender: false,
    extensions,
    content: initialDocument(blocks, value),
    editable: !disabled,
    autofocus: autoFocus ? "end" : false,
    editorProps: {
      attributes: {
        role: "textbox",
        "aria-label": ariaLabel,
        "aria-multiline": "true",
        "data-placeholder": placeholder,
        spellcheck: "true",
      },
      handleTextInput(view, from, to, text) {
        return view.state.doc.textContent.length - (to - from) + text.length > 40_000;
      },
      handlePaste(_view, event) {
        const files = Array.from(event.clipboardData?.files ?? []);
        if (files.length === 0 || !onPasteFiles) return false;
        event.preventDefault();
        onPasteFiles(files);
        return true;
      },
      handleKeyDown(_view, event) {
        if (onSpecialKey?.(event)) return true;
        const mod = event.ctrlKey || event.metaKey;
        if (mod && event.shiftKey && event.key.toLowerCase() === "u") {
          event.preventDefault();
          setLink();
          return true;
        }
        if (event.key === "Enter" && !event.shiftKey) {
          event.preventDefault();
          onSubmit();
          return true;
        }
        return false;
      },
    },
    onUpdate({ editor: current }) {
      const safe = sanitizeMessageBlocks({ v: 1, doc: current.getJSON() });
      onChange({ text: blocksToPlainText(safe), blocks: safe }, plainCursor(current));
      setRevision((revision) => revision + 1);
    },
    onSelectionUpdate() {
      setRevision((revision) => revision + 1);
    },
  });

  useEffect(() => {
    if (!editor) return;
    editor.setEditable(!disabled);
  }, [disabled, editor]);

  useEffect(() => {
    if (!editor || editor.isFocused) return;
    const currentText = blocksToPlainText({ v: 1, doc: editor.getJSON() });
    if (currentText === value) return;
    editor.commands.setContent(initialDocument(blocks, value), { emitUpdate: false });
    setRevision((revision) => revision + 1);
  }, [blocks, editor, value]);

  useImperativeHandle(ref, () => ({
    focus: () => editor?.commands.focus("end"),
    clear: () => {
      if (!editor) return;
      editor.commands.setContent(initialDocument(undefined, ""), { emitUpdate: false });
      editor.commands.focus("start");
      setRevision((revision) => revision + 1);
    },
    insertText: (text) => editor?.chain().focus().insertContent(text).run(),
    setPlainText: (text) => {
      if (!editor) return;
      const safe = sanitizeMessageBlocks(undefined, text);
      editor.commands.setContent(initialDocument(safe, text), { emitUpdate: false });
      onChange({ text: blocksToPlainText(safe), blocks: safe }, plainCursor(editor));
      editor.commands.focus("end");
      setRevision((revision) => revision + 1);
    },
    replaceCurrentMention: (text) => {
      if (!editor) return;
      const { from } = editor.state.selection;
      const lookBehind = editor.state.doc.textBetween(Math.max(0, from - 100), from, "\n", "\n");
      const match = /@[a-zA-Z0-9._-]*$/.exec(lookBehind);
      if (!match) {
        editor.chain().focus().insertContent(text).run();
        return;
      }
      editor.chain().focus().deleteRange({ from: Math.max(1, from - match[0].length), to: from }).insertContent(text).run();
    },
    getValue: () => {
      if (!editor) return { text: value, blocks: sanitizeMessageBlocks(blocks, value) };
      const safe = sanitizeMessageBlocks({ v: 1, doc: editor.getJSON() });
      return { text: blocksToPlainText(safe), blocks: safe };
    },
  }), [blocks, editor, onChange, value]);

  function toolbarAction(action: () => void) {
    return (event: React.MouseEvent<HTMLButtonElement>) => {
      event.preventDefault();
      action();
      setRevision((revision) => revision + 1);
    };
  }

  function setLink() {
    if (!editor) return;
    const previous = editor.getAttributes("link").href as string | undefined;
    const href = window.prompt("Paste a link", previous ?? "https://");
    if (href == null) return;
    if (!href.trim()) editor.chain().focus().extendMarkRange("link").unsetLink().run();
    else editor.chain().focus().extendMarkRange("link").setLink({ href: href.trim() }).run();
  }

  const tool = (label: string, active: boolean, action: () => void, content: React.ReactNode, shortcut?: string) => (
    <button
      type="button"
      className={active ? "active" : undefined}
      aria-label={label}
      aria-pressed={active}
      title={shortcut ? `${label} (${shortcut})` : label}
      disabled={!editor || disabled}
      onMouseDown={toolbarAction(action)}
    >
      {content}
    </button>
  );

  return (
    <div className={`rich-editor ${compact ? "compact" : ""} ${editor?.isEmpty ? "empty" : ""} ${className}`.trim()} data-placeholder={placeholder}>
      <div className="rich-editor-toolbar" role="toolbar" aria-label="Text formatting">
        {tool("Bold", Boolean(editor?.isActive("bold")), () => editor?.chain().focus().toggleBold().run(), <strong>B</strong>, "Ctrl+B")}
        {tool("Italic", Boolean(editor?.isActive("italic")), () => editor?.chain().focus().toggleItalic().run(), <em>I</em>, "Ctrl+I")}
        {tool("Underline", Boolean(editor?.isActive("underline")), () => editor?.chain().focus().toggleUnderline().run(), <u>U</u>, "Ctrl+U")}
        {tool("Strikethrough", Boolean(editor?.isActive("strike")), () => editor?.chain().focus().toggleStrike().run(), <s>S</s>, "Ctrl+Shift+X")}
        <i aria-hidden="true" />
        {tool("Add link", Boolean(editor?.isActive("link")), setLink, <LinkIcon />, "Ctrl+Shift+U")}
        {tool("Numbered list", Boolean(editor?.isActive("orderedList")), () => editor?.chain().focus().toggleOrderedList().run(), <OrderedListIcon />, "Ctrl+Shift+7")}
        {tool("Bulleted list", Boolean(editor?.isActive("bulletList")), () => editor?.chain().focus().toggleBulletList().run(), <BulletListIcon />, "Ctrl+Shift+8")}
        <i aria-hidden="true" />
        {tool("Block quote", Boolean(editor?.isActive("blockquote")), () => editor?.chain().focus().toggleBlockquote().run(), <QuoteIcon />, "Ctrl+Shift+9")}
        {tool("Inline code", Boolean(editor?.isActive("code")), () => editor?.chain().focus().toggleCode().run(), <span className="rich-code-icon">&lt;/&gt;</span>, "Ctrl+Shift+C")}
        {tool("Code block", Boolean(editor?.isActive("codeBlock")), () => editor?.chain().focus().toggleCodeBlock().run(), <CodeBlockIcon />, "Ctrl+Alt+Shift+C")}
      </div>
      <EditorContent editor={editor} />
    </div>
  );
});
