import { useEditor, EditorContent } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import Link from "@tiptap/extension-link";
import Image from "@tiptap/extension-image";
import { useEffect, useState } from "react";

interface RichTextEditorProps {
  /** Hidden form field name; the rendered HTML is mirrored into it. */
  name: string;
  /** Visible label rendered above the editor. */
  label: string;
  /** Initial HTML loaded into the editor. */
  defaultValue?: string;
  /** Optional helper text shown below the editor. */
  helpText?: string;
  /** Insertable variable tokens shown as quick-insert buttons. */
  variables?: { token: string; label?: string }[];
  /** Min editor height in px. */
  minHeight?: number;
  /**
   * If set, occurrences of `cid:invoice-logo` (in src attributes) are
   * displayed using this data/HTTPS URL inside the editor, but mirrored
   * back to `cid:invoice-logo` in the submitted hidden field. This keeps
   * the stored template portable while showing the real logo while editing.
   */
  logoDataUrl?: string | null;
}

/**
 * TipTap-based WYSIWYG editor that mirrors its HTML content into a hidden
 * input so the parent <Form> can submit it like any other field.
 *
 * Variables like {{invoiceNumber}} are inserted as plain text — the email
 * renderer is responsible for substituting them at send time.
 */
export function RichTextEditor({
  name,
  label,
  defaultValue = "",
  helpText,
  variables = [],
  minHeight = 200,
  logoDataUrl = null,
}: RichTextEditorProps) {
  // TipTap calls into the DOM on init; defer mounting until after hydration
  // so SSR markup matches the initial client render.
  const [mounted, setMounted] = useState(false);
  useEffect(() => setMounted(true), []);

  const editor = useEditor({
    immediatelyRender: false,
    extensions: [
      StarterKit.configure({
        // Drop heading levels we don't need to keep the toolbar focused.
        heading: { levels: [2, 3] },
      }),
      Link.configure({ openOnClick: false }),
      Image.configure({ inline: false, allowBase64: true }),
    ],
    content: swapCidToLogo(defaultValue || "<p></p>", logoDataUrl),
    editorProps: {
      attributes: {
        class: "wysiwyg-editor",
        style: `min-height:${minHeight}px;padding:8px 12px;border:1px solid #c9cccf;border-top:0;border-radius:0 0 6px 6px;background:#fff;outline:none;`,
      },
    },
  });

  // Keep editor disposed cleanly on unmount.
  useEffect(() => () => editor?.destroy(), [editor]);

  const html = swapLogoToCid(editor?.getHTML() ?? defaultValue, logoDataUrl);

  if (!mounted) {
    // Server / pre-hydration fallback: a textarea so the value is still
    // submittable if JS fails or before TipTap mounts.
    return (
      <div>
        {label ? (
          <label style={{ display: "block", fontSize: 14, fontWeight: 500, marginBottom: 6 }}>
            {label}
          </label>
        ) : null}
        <textarea
          name={name}
          defaultValue={defaultValue}
          style={{ width: "100%", minHeight, padding: 8, border: "1px solid #c9cccf", borderRadius: 6 }}
        />
        {helpText ? (
          <div style={{ fontSize: 12, color: "#6d7175", marginTop: 4 }}>{helpText}</div>
        ) : null}
      </div>
    );
  }

  return (
    <div>
      {label ? (
        <label style={{ display: "block", fontSize: 14, fontWeight: 500, marginBottom: 6 }}>
          {label}
        </label>
      ) : null}
      <div
        role="toolbar"
        style={{
          display: "flex",
          flexWrap: "wrap",
          gap: 4,
          padding: 6,
          border: "1px solid #c9cccf",
          borderRadius: "6px 6px 0 0",
          background: "#f6f6f7",
        }}
      >
        <ToolbarButton
          onClick={() => editor?.chain().focus().toggleBold().run()}
          active={editor?.isActive("bold") ?? false}
          label="B"
          title="Bold"
          bold
        />
        <ToolbarButton
          onClick={() => editor?.chain().focus().toggleItalic().run()}
          active={editor?.isActive("italic") ?? false}
          label="I"
          title="Italic"
          italic
        />
        <ToolbarButton
          onClick={() => editor?.chain().focus().toggleStrike().run()}
          active={editor?.isActive("strike") ?? false}
          label="S"
          title="Strikethrough"
        />
        <Sep />
        <ToolbarButton
          onClick={() => editor?.chain().focus().setParagraph().run()}
          active={editor?.isActive("paragraph") ?? false}
          label="¶"
          title="Paragraph"
        />
        <ToolbarButton
          onClick={() => editor?.chain().focus().toggleHeading({ level: 2 }).run()}
          active={editor?.isActive("heading", { level: 2 }) ?? false}
          label="H2"
          title="Heading 2"
        />
        <ToolbarButton
          onClick={() => editor?.chain().focus().toggleHeading({ level: 3 }).run()}
          active={editor?.isActive("heading", { level: 3 }) ?? false}
          label="H3"
          title="Heading 3"
        />
        <Sep />
        <ToolbarButton
          onClick={() => editor?.chain().focus().toggleBulletList().run()}
          active={editor?.isActive("bulletList") ?? false}
          label="• List"
          title="Bullet list"
        />
        <ToolbarButton
          onClick={() => editor?.chain().focus().toggleOrderedList().run()}
          active={editor?.isActive("orderedList") ?? false}
          label="1. List"
          title="Numbered list"
        />
        <Sep />
        <ToolbarButton
          onClick={() => {
            const url = window.prompt("URL");
            if (url) editor?.chain().focus().setLink({ href: url }).run();
            else editor?.chain().focus().unsetLink().run();
          }}
          active={editor?.isActive("link") ?? false}
          label="Link"
          title="Insert/remove link"
        />
        <Sep />
        <ToolbarButton
          onClick={() => editor?.chain().focus().undo().run()}
          active={false}
          label="↺"
          title="Undo"
        />
        <ToolbarButton
          onClick={() => editor?.chain().focus().redo().run()}
          active={false}
          label="↻"
          title="Redo"
        />
      </div>
      <EditorContent editor={editor} />
      <input type="hidden" name={name} value={html} />
      {variables.length > 0 ? (
        <div style={{ marginTop: 6, display: "flex", flexWrap: "wrap", gap: 4, alignItems: "center" }}>
          <span style={{ fontSize: 12, color: "#6d7175", marginRight: 4 }}>Insert variable:</span>
          {variables.map((v) => (
            <button
              key={v.token}
              type="button"
              onClick={() => editor?.chain().focus().insertContent(v.token).run()}
              style={{
                fontSize: 12,
                padding: "2px 8px",
                border: "1px solid #c9cccf",
                borderRadius: 12,
                background: "#fff",
                cursor: "pointer",
              }}
              title={`Inserts ${v.token}`}
            >
              {v.label ?? v.token}
            </button>
          ))}
        </div>
      ) : null}
      {helpText ? (
        <div style={{ fontSize: 12, color: "#6d7175", marginTop: 4 }}>{helpText}</div>
      ) : null}
    </div>
  );
}

function ToolbarButton({
  onClick,
  active,
  label,
  title,
  bold,
  italic,
}: {
  onClick: () => void;
  active: boolean;
  label: string;
  title: string;
  bold?: boolean;
  italic?: boolean;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      title={title}
      style={{
        padding: "4px 10px",
        background: active ? "#e3e5e7" : "#fff",
        border: "1px solid #c9cccf",
        borderRadius: 4,
        fontWeight: bold ? 700 : 500,
        fontStyle: italic ? "italic" : "normal",
        cursor: "pointer",
        fontSize: 13,
        minWidth: 28,
      }}
    >
      {label}
    </button>
  );
}

function Sep() {
  return <span aria-hidden style={{ width: 1, background: "#c9cccf", margin: "2px 4px" }} />;
}

/**
 * Replaces `src="cid:invoice-logo"` with the supplied URL so the editor
 * can display the actual logo. Done as a string replace because TipTap
 * doesn't render `cid:` URLs.
 */
function swapCidToLogo(html: string, logoUrl: string | null): string {
  if (!logoUrl) return html;
  const escaped = logoUrl.replace(/"/g, "&quot;");
  return html
    .replace(/src="cid:invoice-logo"/g, `src="${escaped}"`)
    .replace(/src='cid:invoice-logo'/g, `src='${escaped}'`);
}

/** Inverse of swapCidToLogo — ensures the cid token is what we store. */
function swapLogoToCid(html: string, logoUrl: string | null): string {
  if (!logoUrl) return html;
  const escaped = logoUrl.replace(/"/g, "&quot;");
  return html
    .replace(new RegExp(`src="${escapeRegExp(escaped)}"`, "g"), 'src="cid:invoice-logo"')
    .replace(new RegExp(`src='${escapeRegExp(escaped)}'`, "g"), "src='cid:invoice-logo'");
}

function escapeRegExp(s: string): string {
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
