"use client";

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
} from "@/components/ui/select";
import { cn } from "@/lib/utils";
import { CodeNode, CodeHighlightNode } from "@lexical/code";
import { $generateHtmlFromNodes, $generateNodesFromDOM } from "@lexical/html";
import {
  $isListNode,
  INSERT_ORDERED_LIST_COMMAND,
  INSERT_UNORDERED_LIST_COMMAND,
  ListItemNode,
  ListNode,
} from "@lexical/list";
import { AutoLinkNode, $createLinkNode, LinkNode } from "@lexical/link";
import { TRANSFORMERS } from "@lexical/markdown";
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
import {
  HorizontalRuleNode,
  INSERT_HORIZONTAL_RULE_COMMAND,
} from "@lexical/react/LexicalHorizontalRuleNode";
import { HorizontalRulePlugin } from "@lexical/react/LexicalHorizontalRulePlugin";
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
import { AutoLinkPlugin } from "@lexical/react/LexicalAutoLinkPlugin";
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
import { TabIndentationPlugin } from "@lexical/react/LexicalTabIndentationPlugin";
import { TablePlugin } from "@lexical/react/LexicalTablePlugin";
import {
  $createHeadingNode,
  $createQuoteNode,
  $isHeadingNode,
  HeadingNode,
  HeadingTagType,
  QuoteNode,
} from "@lexical/rich-text";
import { $patchStyleText, $setBlocksType } from "@lexical/selection";
import {
  INSERT_TABLE_COMMAND,
  TableCellNode,
  TableNode,
  TableRowNode,
} from "@lexical/table";
import { $findMatchingParent } from "@lexical/utils";
import {
  $createParagraphNode,
  $createTextNode,
  $getNodeByKey,
  $getRoot,
  $getSelection,
  $insertNodes,
  $isRangeSelection,
  $isRootOrShadowRoot,
  COMMAND_PRIORITY_EDITOR,
  DecoratorNode,
  FORMAT_ELEMENT_COMMAND,
  FORMAT_TEXT_COMMAND,
  LexicalEditor,
  LexicalNode,
  NodeKey,
  SerializedLexicalNode,
  Spread,
  createCommand,
} from "lexical";
import {
  AlignCenter,
  AlignLeft,
  AlignRight,
  Baseline,
  Bold,
  ChevronsUpDown,
  FileUp,
  Highlighter,
  Image as ImageIcon,
  Italic,
  List,
  ListOrdered,
  Minus,
  Strikethrough,
  Table,
  Type,
  Underline,
} from "lucide-react";
import type { JSX, ReactNode } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { MediaInsertDialog } from "./media-insert-dialog";

/* -------------------------------------------------------------------------- */
/* AutoLink matchers                                                           */
/* -------------------------------------------------------------------------- */

const URL_MATCHER =
  /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;

const EMAIL_MATCHER =
  /(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/;

const LINK_MATCHERS = [
  (text: string) => {
    const match = URL_MATCHER.exec(text);
    if (!match) return null;
    const fullMatch = match[0];
    return {
      index: match.index,
      length: fullMatch.length,
      text: fullMatch,
      url: fullMatch.startsWith("http") ? fullMatch : `https://${fullMatch}`,
    };
  },
  (text: string) => {
    const match = EMAIL_MATCHER.exec(text);
    if (!match) return null;
    return {
      index: match.index,
      length: match[0].length,
      text: match[0],
      url: `mailto:${match[0]}`,
    };
  },
];

/* -------------------------------------------------------------------------- */
/* ResizableImage — rendered by ImageNode.decorate()                          */
/* -------------------------------------------------------------------------- */

function ResizableImage({
  src,
  altText,
  width,
  nodeKey,
}: {
  src: string;
  altText: string;
  width: number | null;
  nodeKey: NodeKey;
}) {
  const [editor] = useLexicalComposerContext();
  const [isSelected, setIsSelected] = useState(false);
  const [localWidth, setLocalWidth] = useState<number | null>(width);
  const imgRef = useRef<HTMLImageElement>(null);
  const containerRef = useRef<HTMLDivElement>(null);

  // Sync when the node's stored width changes (e.g. initial hydration)
  useEffect(() => {
    setLocalWidth(width);
  }, [width]);

  // Deselect on outside click
  useEffect(() => {
    const handler = (e: MouseEvent) => {
      if (
        containerRef.current &&
        !containerRef.current.contains(e.target as Node)
      ) {
        setIsSelected(false);
      }
    };
    document.addEventListener("mousedown", handler);
    return () => document.removeEventListener("mousedown", handler);
  }, []);

  /** Generic resize starter — pass a direction multiplier (+1 right, -1 left) */
  const startResize = (e: React.MouseEvent<HTMLElement>, dir: 1 | -1) => {
    e.preventDefault();
    e.stopPropagation();
    const startX = e.clientX;
    const startW = imgRef.current?.offsetWidth ?? localWidth ?? 300;

    const onMove = (ev: MouseEvent) => {
      const newW = Math.max(40, startW + dir * (ev.clientX - startX));
      setLocalWidth(newW);
    };

    const onUp = (ev: MouseEvent) => {
      const newW = Math.max(40, startW + dir * (ev.clientX - startX));
      setLocalWidth(newW);
      editor.update(() => {
        const node = $getNodeByKey(nodeKey);
        if (node instanceof ImageNode) {
          node.setWidth(newW);
        }
      });
      document.removeEventListener("mousemove", onMove);
      document.removeEventListener("mouseup", onUp);
    };

    document.addEventListener("mousemove", onMove);
    document.addEventListener("mouseup", onUp);
  };

  const w = localWidth ?? undefined;

  return (
    <span
      ref={containerRef}
      style={{
        display: "inline-block",
        position: "relative",
        width: w,
        maxWidth: "100%",
        cursor: "default",
        userSelect: "none",
        lineHeight: 0,
      }}
      onClick={() => setIsSelected(true)}
    >
      {/* eslint-disable-next-line @next/next/no-img-element */}
      <img
        ref={imgRef}
        src={src}
        alt={altText}
        draggable={false}
        style={{
          display: "block",
          width: w ? `${w}px` : "auto",
          maxWidth: "100%",
        }}
      />

      {isSelected && (
        <>
          {/* Selection border */}
          <span
            style={{
              position: "absolute",
              inset: 0,
              border: "2px solid #1C75BC",
              pointerEvents: "none",
              borderRadius: 2,
            }}
          />

          {/* Bottom-right resize handle */}
          <span
            title="Resize"
            style={{
              position: "absolute",
              bottom: -5,
              right: -5,
              width: 10,
              height: 10,
              backgroundColor: "#1C75BC",
              border: "2px solid white",
              borderRadius: 2,
              cursor: "se-resize",
              display: "inline-block",
            }}
            onMouseDown={(e) => startResize(e, 1)}
          />

          {/* Bottom-left resize handle */}
          <span
            title="Resize"
            style={{
              position: "absolute",
              bottom: -5,
              left: -5,
              width: 10,
              height: 10,
              backgroundColor: "#1C75BC",
              border: "2px solid white",
              borderRadius: 2,
              cursor: "sw-resize",
              display: "inline-block",
            }}
            onMouseDown={(e) => startResize(e, -1)}
          />

          {/* Top-right handle */}
          <span
            style={{
              position: "absolute",
              top: -5,
              right: -5,
              width: 10,
              height: 10,
              backgroundColor: "#1C75BC",
              border: "2px solid white",
              borderRadius: 2,
              cursor: "ne-resize",
              display: "inline-block",
            }}
            onMouseDown={(e) => startResize(e, 1)}
          />

          {/* Top-left handle */}
          <span
            style={{
              position: "absolute",
              top: -5,
              left: -5,
              width: 10,
              height: 10,
              backgroundColor: "#1C75BC",
              border: "2px solid white",
              borderRadius: 2,
              cursor: "nw-resize",
              display: "inline-block",
            }}
            onMouseDown={(e) => startResize(e, -1)}
          />

          {/* Dimension badge */}
          {w && (
            <span
              style={{
                position: "absolute",
                bottom: 8,
                left: "50%",
                transform: "translateX(-50%)",
                backgroundColor: "rgba(0,0,0,0.6)",
                color: "white",
                fontSize: 10,
                padding: "2px 6px",
                borderRadius: 4,
                pointerEvents: "none",
                whiteSpace: "nowrap",
                display: "inline-block",
              }}
            >
              {w}px
            </span>
          )}
        </>
      )}
    </span>
  );
}

/* -------------------------------------------------------------------------- */
/* ImageNode                                                                  */
/* -------------------------------------------------------------------------- */

export type SerializedImageNode = Spread<
  { src: string; altText: string; width: number | null },
  SerializedLexicalNode
>;

class ImageNode extends DecoratorNode<ReactNode> {
  __src: string;
  __altText: string;
  __width: number | null;

  static getType(): string {
    return "image";
  }

  static clone(node: ImageNode): ImageNode {
    return new ImageNode(node.__src, node.__altText, node.__width, node.__key);
  }

  constructor(
    src: string,
    altText: string,
    width?: number | null,
    key?: NodeKey
  ) {
    super(key);
    this.__src = src;
    this.__altText = altText;
    this.__width = width ?? null;
  }

  setWidth(width: number): void {
    const writable = this.getWritable();
    writable.__width = width;
  }

  createDOM(): HTMLElement {
    const span = document.createElement("span");
    span.style.display = "inline-block";
    span.style.maxWidth = "100%";
    return span;
  }

  updateDOM(): false {
    return false;
  }

  static importJSON(serializedNode: SerializedImageNode): ImageNode {
    return new ImageNode(
      serializedNode.src,
      serializedNode.altText,
      serializedNode.width ?? null
    );
  }

  exportJSON(): SerializedImageNode {
    return {
      type: "image",
      version: 1,
      src: this.__src,
      altText: this.__altText,
      width: this.__width,
    };
  }

  exportDOM(): { element: HTMLElement } {
    const img = document.createElement("img");
    img.setAttribute("src", this.__src);
    img.setAttribute("alt", this.__altText);
    if (this.__width) img.style.width = `${this.__width}px`;
    img.style.maxWidth = "100%";
    return { element: img };
  }

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  static importDOM(): Record<string, any> | null {
    return {
      img: () => ({
        conversion: (domNode: HTMLElement) => {
          if (!(domNode instanceof HTMLImageElement)) return null;
          const src = domNode.getAttribute("src") || "";
          const alt = domNode.getAttribute("alt") || "";
          const w = domNode.style.width
            ? parseInt(domNode.style.width)
            : null;
          return { node: new ImageNode(src, alt, w) };
        },
        priority: 1 as const,
      }),
    };
  }

  decorate(): ReactNode {
    return (
      <ResizableImage
        src={this.__src}
        altText={this.__altText}
        width={this.__width}
        nodeKey={this.getKey()}
      />
    );
  }
}

function $createImageNode(src: string, altText: string): ImageNode {
  return new ImageNode(src, altText);
}

export const INSERT_IMAGE_COMMAND = createCommand<{
  src: string;
  altText: string;
}>("INSERT_IMAGE_COMMAND");

export const INSERT_FILE_COMMAND = createCommand<{
  url: string;
  name: string;
}>("INSERT_FILE_COMMAND");

function ImagePlugin(): null {
  const [editor] = useLexicalComposerContext();

  useEffect(() => {
    const removeImage = editor.registerCommand(
      INSERT_IMAGE_COMMAND,
      (payload) => {
        $insertNodes([$createImageNode(payload.src, payload.altText)]);
        return true;
      },
      COMMAND_PRIORITY_EDITOR
    );

    const removeFile = editor.registerCommand(
      INSERT_FILE_COMMAND,
      (payload) => {
        const link = $createLinkNode(payload.url);
        link.append($createTextNode(payload.name));
        $insertNodes([link]);
        return true;
      },
      COMMAND_PRIORITY_EDITOR
    );

    return () => {
      removeImage();
      removeFile();
    };
  }, [editor]);

  return null;
}

/* -------------------------------------------------------------------------- */
/* HtmlValuePlugin — syncs the `value` prop into the editor                  */
/* -------------------------------------------------------------------------- */

function isEmptyHtml(html: string): boolean {
  const t = html.trim();
  return (
    t === "" || t === "<p></p>" || t === "<p><br></p>" || t === "<p><br/></p>"
  );
}

function HtmlValuePlugin({ value }: { value: string }): null {
  const [editor] = useLexicalComposerContext();
  const lastApplied = useRef<string | null>(null);
  const initialized = useRef(false);

  useEffect(() => {
    if (lastApplied.current === value) return;

    if (initialized.current) {
      let current = "";
      editor.read(() => {
        current = $generateHtmlFromNodes(editor);
      });
      if (current === value) {
        lastApplied.current = value;
        return;
      }
    }

    editor.update(() => {
      const root = $getRoot();
      root.clear();
      if (!isEmptyHtml(value)) {
        try {
          const html = value.trim().startsWith('<') ? value : `<p>${value}</p>`;
          const dom = new DOMParser().parseFromString(html, "text/html");
          const nodes = $generateNodesFromDOM(editor, dom);
          root.append(...nodes.filter((n): n is LexicalNode => !!n));
        } catch {
          /* silent */
        }
      }
      lastApplied.current = value;
      initialized.current = true;
    });
  }, [editor, value]);

  return null;
}

/* -------------------------------------------------------------------------- */
/* Toolbar                                                                    */
/* -------------------------------------------------------------------------- */

type BlockType = "paragraph" | "h1" | "h2" | "h3" | "quote";

const FONT_SIZES = ["12", "14", "16", "18", "20", "24", "28", "32"] as const;

const BLOCK_LABELS: Record<BlockType, string> = {
  paragraph: "Paragraph",
  h1: "Heading 1",
  h2: "Heading 2",
  h3: "Heading 3",
  quote: "Quote",
};

function ToolbarButton({
  onClick,
  active,
  disabled,
  title,
  children,
  variant = "default",
}: {
  onClick: () => void;
  active?: boolean;
  disabled?: boolean;
  title: string;
  children: ReactNode;
  variant?: "default" | "primary";
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      title={title}
      disabled={disabled}
      aria-pressed={active}
      className={cn(
        "inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-muted active:bg-muted/70 disabled:pointer-events-none disabled:opacity-50",
        variant === "primary" ? "text-primary-500" : "text-muted-foreground",
        active && "bg-primary-50 text-primary-500"
      )}
    >
      {children}
    </button>
  );
}

function ToolbarDivider() {
  return <div className="border-e border-border self-stretch" />;
}

function ToolbarPlugin(): JSX.Element {
  const [editor] = useLexicalComposerContext();
  const [blockType, setBlockType] = useState<BlockType>("paragraph");
  const [fontSize, setFontSize] = useState("16");
  const [isBold, setIsBold] = useState(false);
  const [isItalic, setIsItalic] = useState(false);
  const [isUnderline, setIsUnderline] = useState(false);
  const [isStrikethrough, setIsStrikethrough] = useState(false);
  const [mediaDialog, setMediaDialog] = useState<{
    open: boolean;
    mode: "image" | "file";
  }>({ open: false, mode: "image" });

  const openMedia = (mode: "image" | "file") =>
    setMediaDialog({ open: true, mode });

  const updateToolbar = useCallback(() => {
    const sel = $getSelection();
    if (!$isRangeSelection(sel)) return;
    setIsBold(sel.hasFormat("bold"));
    setIsItalic(sel.hasFormat("italic"));
    setIsUnderline(sel.hasFormat("underline"));
    setIsStrikethrough(sel.hasFormat("strikethrough"));

    const anchor = sel.anchor.getNode();
    const el =
      anchor.getKey() === "root"
        ? anchor
        : ($findMatchingParent(anchor, (e) => {
            const p = e.getParent();
            return p !== null && $isRootOrShadowRoot(p);
          }) ?? anchor.getTopLevelElementOrThrow());

    if ($isHeadingNode(el)) {
      const tag = el.getTag();
      setBlockType(tag === "h1" || tag === "h2" || tag === "h3" ? tag : "paragraph");
    } else if ($isListNode(el)) {
      setBlockType("paragraph");
    } else {
      setBlockType(el.getType() === "quote" ? "quote" : "paragraph");
    }
  }, []);

  useEffect(
    () => editor.registerUpdateListener(({ editorState }) => editorState.read(() => updateToolbar())),
    [editor, updateToolbar]
  );

  const formatBlock = (v: BlockType) =>
    editor.update(() => {
      const sel = $getSelection();
      if (!$isRangeSelection(sel)) return;
      if (v === "paragraph") $setBlocksType(sel, () => $createParagraphNode());
      else if (v === "quote") $setBlocksType(sel, () => $createQuoteNode());
      else $setBlocksType(sel, () => $createHeadingNode(v as HeadingTagType));
    });

  const applyStyle = (style: Record<string, string>) =>
    editor.update(() => {
      const sel = $getSelection();
      if ($isRangeSelection(sel)) $patchStyleText(sel, style);
    });

  return (
    <>
      <div className="flex w-full items-stretch overflow-x-auto border-b border-border">
        <div className="flex min-w-max items-stretch">

          {/* Block type */}
          <div className="flex min-h-12 items-center gap-1 border-e border-border px-2">
            <Select value={blockType} onValueChange={(v) => formatBlock(v as BlockType)}>
              <SelectTrigger className="h-auto w-auto gap-1 border-0 bg-transparent px-0 shadow-none focus:ring-0 [&>svg:last-child]:hidden">
                <Type className="size-4 text-muted-foreground" />
                <span className="text-sm font-bold text-muted-foreground">{BLOCK_LABELS[blockType]}</span>
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="paragraph">Paragraph</SelectItem>
                <SelectItem value="h1">Heading 1</SelectItem>
                <SelectItem value="h2">Heading 2</SelectItem>
                <SelectItem value="h3">Heading 3</SelectItem>
                <SelectItem value="quote">Quote</SelectItem>
              </SelectContent>
            </Select>
          </div>

          {/* Font size */}
          <div className="flex min-h-12 items-center gap-1 border-e border-border px-2">
            <Select value={fontSize} onValueChange={(v) => { setFontSize(v); applyStyle({ "font-size": `${v}px` }); }}>
              <SelectTrigger className="h-auto w-auto gap-1 border-0 bg-transparent px-0 shadow-none focus:ring-0 [&>svg:last-child]:hidden">
                <span className="text-base font-bold text-muted-foreground">{fontSize}</span>
                <ChevronsUpDown className="size-3 text-muted-foreground" />
              </SelectTrigger>
              <SelectContent>
                {FONT_SIZES.map((s) => <SelectItem key={s} value={s}>{s}</SelectItem>)}
              </SelectContent>
            </Select>
          </div>

          {/* Text formatting */}
          <div className="flex items-center gap-1 border-e border-border p-2">
            <label title="Text color" className="relative inline-flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-muted-foreground hover:bg-muted">
              <Baseline className="size-5" />
              <input type="color" className="absolute inset-0 cursor-pointer opacity-0" onChange={(e) => applyStyle({ color: e.target.value })} />
            </label>
            <label title="Highlight" className="relative inline-flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-muted-foreground hover:bg-muted">
              <Highlighter className="size-5" />
              <input type="color" className="absolute inset-0 cursor-pointer opacity-0" onChange={(e) => applyStyle({ "background-color": e.target.value })} />
            </label>
            <ToolbarButton title="Bold" active={isBold} onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold")}><Bold className="size-5" /></ToolbarButton>
            <ToolbarButton title="Italic" active={isItalic} onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic")}><Italic className="size-5" /></ToolbarButton>
            <ToolbarButton title="Underline" active={isUnderline} onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "underline")}><Underline className="size-5" /></ToolbarButton>
            <ToolbarButton title="Strikethrough" active={isStrikethrough} onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "strikethrough")}><Strikethrough className="size-5" /></ToolbarButton>
          </div>

          {/* Alignment */}
          <div className="flex items-center gap-1 border-e border-border p-2">
            <ToolbarButton title="Align right" onClick={() => editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, "right")}><AlignRight className="size-5" /></ToolbarButton>
            <ToolbarButton title="Align center" onClick={() => editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, "center")}><AlignCenter className="size-5" /></ToolbarButton>
            <ToolbarButton title="Align left" onClick={() => editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, "left")}><AlignLeft className="size-5" /></ToolbarButton>
          </div>

          {/* Lists */}
          <div className="flex items-center gap-1 border-e border-border p-2">
            <ToolbarButton title="Numbered list" onClick={() => editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined)}><ListOrdered className="size-5" /></ToolbarButton>
            <ToolbarButton title="Bullet list" onClick={() => editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined)}><List className="size-5" /></ToolbarButton>
          </div>

          {/* Insert: table + horizontal rule */}
          <div className="flex items-center gap-1 border-e border-border p-2">
            <ToolbarButton
              title="Insert 3×3 table"
              onClick={() => editor.dispatchCommand(INSERT_TABLE_COMMAND, { rows: "3", columns: "3" })}
            >
              <Table className="size-5" />
            </ToolbarButton>
            <ToolbarButton
              title="Insert horizontal rule"
              onClick={() => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined)}
            >
              <Minus className="size-5" />
            </ToolbarButton>
          </div>

          {/* Media */}
          <div className="flex items-center gap-1 p-2">
            <ToolbarButton title="Insert image" variant="primary" onClick={() => openMedia("image")}><ImageIcon className="size-5" /></ToolbarButton>
            <ToolbarButton title="Insert file / PDF" variant="primary" onClick={() => openMedia("file")}><FileUp className="size-5" /></ToolbarButton>
          </div>

        </div>
      </div>

      <MediaInsertDialog
        open={mediaDialog.open}
        onOpenChange={(open) => setMediaDialog((s) => ({ ...s, open }))}
        mode={mediaDialog.mode}
        onInsertImage={(src, altText) => editor.dispatchCommand(INSERT_IMAGE_COMMAND, { src, altText })}
        onInsertFile={(url, name) => editor.dispatchCommand(INSERT_FILE_COMMAND, { url, name })}
      />
    </>
  );
}

/* -------------------------------------------------------------------------- */
/* Public component                                                           */
/* -------------------------------------------------------------------------- */

export interface LexicalRichTextEditorProps {
  value: string;
  onChange: (html: string) => void;
  placeholder?: string;
  dir?: "rtl" | "ltr";
  className?: string;
  minHeight?: number | string;
  namespace?: string;
}

export function LexicalRichTextEditor({
  value,
  onChange,
  placeholder,
  dir = "ltr",
  className,
  minHeight = 240,
  namespace = "BlogEditor",
}: LexicalRichTextEditorProps): JSX.Element {
  const initialConfig = {
    namespace,
    onError: (err: Error) => console.error("[Lexical]", err),
    nodes: [
      HeadingNode,
      QuoteNode,
      ListNode,
      ListItemNode,
      LinkNode,
      AutoLinkNode,
      CodeNode,
      CodeHighlightNode,
      ImageNode,
      TableNode,
      TableCellNode,
      TableRowNode,
      HorizontalRuleNode,
    ],
    theme: {
      paragraph: "mb-2",
      quote: "border-s-4 border-border ps-4 italic text-muted-foreground my-2",
      heading: {
        h1: "text-2xl font-bold mb-3",
        h2: "text-xl font-bold mb-2",
        h3: "text-lg font-semibold mb-2",
      },
      list: {
        ul: "list-disc ps-6 my-2",
        ol: "list-decimal ps-6 my-2",
        listitem: "my-1",
      },
      text: {
        bold: "font-bold",
        italic: "italic",
        underline: "underline",
        strikethrough: "line-through",
        code: "rounded bg-muted px-1 py-0.5 font-mono text-xs",
      },
      table: "border-collapse w-full my-4 table-fixed",
      tableCell: "border border-border px-3 py-2 min-w-[60px] align-top",
      tableCellHeader:
        "border border-border px-3 py-2 bg-muted font-bold align-top",
      tableRow: "",
      tableSelected: "outline outline-2 outline-primary",
      tableSelectedCells: "bg-primary/10",
      link: "text-primary underline cursor-pointer",
    },
  };

  const handleChange = useCallback(
    (_: unknown, editor: LexicalEditor) => {
      editor.read(() => onChange($generateHtmlFromNodes(editor)));
    },
    [onChange]
  );

  const minH = typeof minHeight === "number" ? `${minHeight}px` : minHeight;

  return (
    <div
      className={cn(
        "flex flex-col overflow-hidden rounded-2xl border border-border bg-card",
        className
      )}
    >
      <LexicalComposer initialConfig={initialConfig}>
        <ToolbarPlugin />

        <div className="relative flex-1">
          <RichTextPlugin
            contentEditable={
              <ContentEditable
                aria-placeholder={placeholder ?? ""}
                placeholder={
                  <div
                    className={cn(
                      "pointer-events-none absolute top-3 select-none text-sm text-muted-foreground sm:top-6",
                      dir === "rtl" ? "right-3 sm:right-6" : "left-3 sm:left-6"
                    )}
                  >
                    {placeholder}
                  </div>
                }
                className={cn(
                  "p-3 outline-none sm:p-6",
                  /* headings */
                  "[&_h1]:text-2xl [&_h1]:font-bold [&_h1]:mb-3",
                  "[&_h2]:text-xl [&_h2]:font-bold [&_h2]:mb-2",
                  "[&_h3]:text-lg [&_h3]:font-semibold [&_h3]:mb-2",
                  /* lists */
                  "[&_ul]:list-disc [&_ul]:ps-6 [&_ul]:my-2",
                  "[&_ol]:list-decimal [&_ol]:ps-6 [&_ol]:my-2",
                  /* quote */
                  "[&_blockquote]:border-s-4 [&_blockquote]:border-border [&_blockquote]:ps-4 [&_blockquote]:italic [&_blockquote]:text-muted-foreground",
                  /* code */
                  "[&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-xs",
                  /* links */
                  "[&_a]:text-primary [&_a]:underline",
                  /* images */
                  "[&_img]:max-w-full",
                  /* table */
                  "[&_table]:w-full [&_table]:border-collapse [&_table]:my-4",
                  "[&_td]:border [&_td]:border-border [&_td]:px-3 [&_td]:py-2 [&_td]:align-top [&_td]:min-w-[60px]",
                  "[&_th]:border [&_th]:border-border [&_th]:px-3 [&_th]:py-2 [&_th]:bg-muted [&_th]:font-bold [&_th]:align-top",
                  /* horizontal rule */
                  "[&_hr]:my-4 [&_hr]:border-border"
                )}
                style={{ minHeight: minH }}
              />
            }
            ErrorBoundary={LexicalErrorBoundary}
          />
        </div>

        <HistoryPlugin />
        <ListPlugin />
        <LinkPlugin />
        <AutoLinkPlugin matchers={LINK_MATCHERS} />
        <TablePlugin />
        <HorizontalRulePlugin />
        <MarkdownShortcutPlugin transformers={TRANSFORMERS} />
        <TabIndentationPlugin />
        <ImagePlugin />
        <OnChangePlugin onChange={handleChange} />
        <HtmlValuePlugin value={value} />
      </LexicalComposer>
    </div>
  );
}

export default LexicalRichTextEditor;
