"use client";

import Image from "@tiptap/extension-image";
import { Placeholder } from "@tiptap/extension-placeholder";
import { TableKit } from "@tiptap/extension-table";
import type { Editor } from "@tiptap/react";
import { EditorContent, useEditor, useEditorState } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import { useEffect, useId, useRef, useState } from "react";
import { MarkdownInputRules } from "@/components/admin/markdown-input-rules";
import { ErrorText, Label, cx } from "@/components/ui";
import { renderMarkdown } from "@/lib/markdown";
import { uploadImageFile } from "@/lib/upload-client";

/**
 * WordPress-style WYSIWYG editor (Tiptap/ProseMirror).
 *
 * - Emits HTML into a hidden field so the surrounding server-action form
 *   submits it like any other input (sanitized server-side on save).
 * - Images are uploaded via /api/admin/uploads — from the toolbar button,
 *   by dropping files onto the editor, or by pasting from the clipboard —
 *   and inserted inline as /uploads/... URLs.
 * - Pasting plain text that looks like Markdown converts it through the
 *   same remark pipeline used everywhere else; pasting rich HTML uses
 *   ProseMirror's native handling.
 */

// Cheap markdown sniff: headings, lists, quotes, fences, emphasis,
// links, or inline code. Plain prose without these pastes untouched.
const MARKDOWN_PATTERN =
  /(^|\n)\s{0,3}(#{1,6}\s|[-*+]\s|\d+\.\s|>\s?|```)|\*\*[^*\n]+\*\*|__[^_\n]+__|\[[^\]\n]+\]\([^)\n]+\)|`[^`\n]+`/;

function looksLikeMarkdown(text: string): boolean {
  return MARKDOWN_PATTERN.test(text);
}

function ToolButton({
  label,
  active = false,
  disabled = false,
  onClick,
  children,
  className,
}: {
  label: string;
  active?: boolean;
  disabled?: boolean;
  onClick: () => void;
  children: React.ReactNode;
  className?: string;
}) {
  return (
    <button
      type="button"
      aria-label={label}
      title={label}
      aria-pressed={active}
      disabled={disabled}
      // Keep the editor focused while clicking toolbar buttons; without
      // this the button grabs focus on mousedown and typed characters go
      // to the button instead of the document.
      onMouseDown={(event) => event.preventDefault()}
      onClick={onClick}
      className={cx(
        "inline-flex h-8 min-w-8 items-center justify-center rounded px-1.5 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-40",
        active
          ? "bg-link text-ink-inverse"
          : "text-ink hover:bg-background hover:text-ink-strong",
        className,
      )}
    >
      {children}
    </button>
  );
}

function ToolDivider() {
  return <span aria-hidden="true" className="mx-1 h-5 w-px self-center bg-edge-strong" />;
}

export function RichTextEditor({
  name,
  label,
  initialHTML,
  error,
  minHeightClassName = "min-h-72",
}: {
  name: string;
  label: string;
  initialHTML: string;
  error?: string;
  /** Tailwind min-height class for the writing canvas. */
  minHeightClassName?: string;
}) {
  const id = useId();
  const errorId = `${id}-error`;
  const [html, setHtml] = useState(initialHTML);
  const [uploadState, setUploadState] = useState<
    { kind: "idle" } | { kind: "uploading" } | { kind: "error"; message: string }
  >({ kind: "idle" });
  const fileInputRef = useRef<HTMLInputElement>(null);
  const editorRef = useRef<Editor | null>(null);

  async function uploadAndInsert(files: File[]) {
    const editor = editorRef.current;
    const images = files.filter((f) => f.type.startsWith("image/"));
    if (!editor || images.length === 0) return;
    setUploadState({ kind: "uploading" });
    for (const file of images) {
      const result = await uploadImageFile(file);
      if ("error" in result) {
        setUploadState({ kind: "error", message: result.error });
        return;
      }
      editor.chain().focus().setImage({ src: result.url, alt: "" }).run();
    }
    setUploadState({ kind: "idle" });
  }

  const editor = useEditor({
    immediatelyRender: false,
    extensions: [
      StarterKit.configure({
        heading: { levels: [2, 3, 4] },
        link: { openOnClick: false, autolink: true, defaultProtocol: "https" },
      }),
      Image.configure({ allowBase64: false }),
      TableKit.configure({ table: { resizable: false } }),
      Placeholder.configure({ placeholder: "Write your story…" }),
      MarkdownInputRules,
    ],
    content: initialHTML,
    editorProps: {
      attributes: {
        class: `markdown-body ${minHeightClassName} px-4 py-3 focus:outline-none`,
        "aria-label": label,
      },
      handlePaste: (_view, event) => {
        const clipboard = event.clipboardData;
        if (!clipboard) return false;

        const files = Array.from(clipboard.files ?? []);
        if (files.some((f) => f.type.startsWith("image/"))) {
          event.preventDefault();
          void uploadAndInsert(files);
          return true;
        }

        // Rich HTML pastes keep ProseMirror's native handling.
        if (clipboard.getData("text/html")) return false;

        const text = clipboard.getData("text/plain");
        if (text && looksLikeMarkdown(text)) {
          event.preventDefault();
          editorRef.current?.chain().focus().insertContent(renderMarkdown(text)).run();
          return true;
        }
        return false;
      },
      handleDrop: (_view, event) => {
        const files = Array.from(event.dataTransfer?.files ?? []);
        if (files.some((f) => f.type.startsWith("image/"))) {
          event.preventDefault();
          void uploadAndInsert(files);
          return true;
        }
        return false;
      },
    },
    onUpdate: ({ editor }) => {
      setHtml(editor.isEmpty ? "" : editor.getHTML());
    },
  });

  // The paste/drop handlers above are created once by useEditor and reach
  // the editor through this ref; render-time assignment would trip the
  // react-hooks/refs rule, so sync it in an effect instead.
  useEffect(() => {
    editorRef.current = editor;
  }, [editor]);

  const state = useEditorState({
    editor,
    selector: ({ editor }) =>
      editor
        ? {
            paragraph: editor.isActive("paragraph"),
            h2: editor.isActive("heading", { level: 2 }),
            h3: editor.isActive("heading", { level: 3 }),
            h4: editor.isActive("heading", { level: 4 }),
            bold: editor.isActive("bold"),
            italic: editor.isActive("italic"),
            underline: editor.isActive("underline"),
            strike: editor.isActive("strike"),
            code: editor.isActive("code"),
            link: editor.isActive("link"),
            bulletList: editor.isActive("bulletList"),
            orderedList: editor.isActive("orderedList"),
            blockquote: editor.isActive("blockquote"),
            codeBlock: editor.isActive("codeBlock"),
            image: editor.isActive("image"),
            table: editor.isActive("table"),
            canUndo: editor.can().undo(),
            canRedo: editor.can().redo(),
          }
        : null,
  });

  function setLink() {
    if (!editor) return;
    const current = editor.getAttributes("link").href as string | undefined;
    const url = window.prompt("Link URL (leave empty to remove):", current ?? "");
    if (url === null) return;
    if (url === "") {
      editor.chain().focus().unsetLink().run();
    } else {
      editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
    }
  }

  function editImageAlt() {
    if (!editor) return;
    const current = (editor.getAttributes("image").alt as string | undefined) ?? "";
    const alt = window.prompt(
      "Alt text for this image (leave empty if decorative):",
      current,
    );
    if (alt === null) return;
    editor.chain().focus().updateAttributes("image", { alt }).run();
  }

  const chain = () => editor!.chain().focus();

  return (
    <div>
      <Label className="mb-1.5">{label}</Label>
      <input type="hidden" name={name} value={html} />
      <div
        className={cx(
          "editor-shell rounded-md border bg-background",
          error ? "border-danger" : "border-edge",
        )}
      >
        <div
          role="toolbar"
          aria-label={`${label} formatting`}
          className="flex flex-wrap items-center gap-0.5 border-b border-edge bg-surface px-2 py-1.5"
        >
          <ToolButton label="Paragraph" active={state?.paragraph} disabled={!editor} onClick={() => chain().setParagraph().run()}>
            ¶
          </ToolButton>
          <ToolButton label="Heading level 2" active={state?.h2} disabled={!editor} onClick={() => chain().toggleHeading({ level: 2 }).run()}>
            H2
          </ToolButton>
          <ToolButton label="Heading level 3" active={state?.h3} disabled={!editor} onClick={() => chain().toggleHeading({ level: 3 }).run()}>
            H3
          </ToolButton>
          <ToolButton label="Heading level 4" active={state?.h4} disabled={!editor} onClick={() => chain().toggleHeading({ level: 4 }).run()}>
            H4
          </ToolButton>
          <ToolDivider />
          <ToolButton label="Bold" active={state?.bold} disabled={!editor} onClick={() => chain().toggleBold().run()} className="font-bold">
            B
          </ToolButton>
          <ToolButton label="Italic" active={state?.italic} disabled={!editor} onClick={() => chain().toggleItalic().run()} className="italic">
            I
          </ToolButton>
          <ToolButton label="Underline" active={state?.underline} disabled={!editor} onClick={() => chain().toggleUnderline().run()} className="underline">
            U
          </ToolButton>
          <ToolButton label="Strikethrough" active={state?.strike} disabled={!editor} onClick={() => chain().toggleStrike().run()} className="line-through">
            S
          </ToolButton>
          <ToolButton label="Inline code" active={state?.code} disabled={!editor} onClick={() => chain().toggleCode().run()} className="font-mono text-xs">
            {"</>"}
          </ToolButton>
          <ToolButton label="Link" active={state?.link} disabled={!editor} onClick={setLink}>
            🔗
          </ToolButton>
          <ToolDivider />
          <ToolButton label="Bullet list" active={state?.bulletList} disabled={!editor} onClick={() => chain().toggleBulletList().run()}>
            ••
          </ToolButton>
          <ToolButton label="Numbered list" active={state?.orderedList} disabled={!editor} onClick={() => chain().toggleOrderedList().run()}>
            1.
          </ToolButton>
          <ToolButton label="Blockquote" active={state?.blockquote} disabled={!editor} onClick={() => chain().toggleBlockquote().run()}>
            ❝
          </ToolButton>
          <ToolButton label="Code block" active={state?.codeBlock} disabled={!editor} onClick={() => chain().toggleCodeBlock().run()} className="font-mono text-xs">
            {"{ }"}
          </ToolButton>
          <ToolButton label="Horizontal rule" disabled={!editor} onClick={() => chain().setHorizontalRule().run()}>
            —
          </ToolButton>
          <ToolDivider />
          <ToolButton label="Upload and insert image" disabled={!editor} onClick={() => fileInputRef.current?.click()}>
            🖼
          </ToolButton>
          {state?.image && (
            <ToolButton label="Edit image alt text" disabled={!editor} onClick={editImageAlt}>
              Alt
            </ToolButton>
          )}
          <ToolButton
            label="Insert table"
            active={state?.table}
            disabled={!editor}
            onClick={() => chain().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()}
          >
            ⊞
          </ToolButton>
          <ToolDivider />
          <ToolButton label="Undo" disabled={!editor || !state?.canUndo} onClick={() => chain().undo().run()}>
            ↺
          </ToolButton>
          <ToolButton label="Redo" disabled={!editor || !state?.canRedo} onClick={() => chain().redo().run()}>
            ↻
          </ToolButton>
        </div>

        {state?.table && (
          <div
            role="toolbar"
            aria-label="Table editing"
            className="flex flex-wrap items-center gap-0.5 border-b border-edge bg-surface px-2 py-1"
          >
            <ToolButton label="Add column after" disabled={!editor} onClick={() => chain().addColumnAfter().run()}>
              +Col
            </ToolButton>
            <ToolButton label="Delete column" disabled={!editor} onClick={() => chain().deleteColumn().run()}>
              −Col
            </ToolButton>
            <ToolButton label="Add row after" disabled={!editor} onClick={() => chain().addRowAfter().run()}>
              +Row
            </ToolButton>
            <ToolButton label="Delete row" disabled={!editor} onClick={() => chain().deleteRow().run()}>
              −Row
            </ToolButton>
            <ToolButton label="Toggle header row" disabled={!editor} onClick={() => chain().toggleHeaderRow().run()}>
              Header
            </ToolButton>
            <ToolButton label="Delete table" disabled={!editor} onClick={() => chain().deleteTable().run()}>
              ✕ Table
            </ToolButton>
          </div>
        )}

        <EditorContent editor={editor} />

        <input
          ref={fileInputRef}
          type="file"
          accept="image/png,image/jpeg,image/webp,image/gif,image/avif"
          multiple
          hidden
          data-testid="editor-image-input"
          onChange={(event) => {
            const files = Array.from(event.target.files ?? []);
            event.target.value = "";
            void uploadAndInsert(files);
          }}
        />
      </div>

      <p className="mt-1.5 text-xs text-ink-muted" role="status" aria-live="polite">
        {uploadState.kind === "uploading" && "Uploading image…"}
        {uploadState.kind === "error" && (
          <span className="text-danger">{uploadState.message}</span>
        )}
        {uploadState.kind === "idle" &&
          "Drop or paste images to upload them. Pasted Markdown is converted automatically."}
      </p>
      <ErrorText id={errorId}>{error}</ErrorText>
    </div>
  );
}
