"use client";

import * as React from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import rehypeHighlight from "rehype-highlight";
import rehypeKatex from "rehype-katex";
import rehypeRaw from "rehype-raw";
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
import rehypeSlug from "rehype-slug";

import { MermaidBlock } from "@/components/editor/mermaid-block";
import { applyEmojiShortcodes } from "@/lib/emoji";
import { cn } from "@/lib/utils";

// KaTeX styles are imported lazily the first time a math block is detected so
// the ~25KB CSS file never lands on documents that don't use math.
let katexCssLoaded = false;
function ensureKatexCss() {
  if (katexCssLoaded || typeof document === "undefined") return;
  katexCssLoaded = true;
  import("katex/dist/katex.min.css").catch(() => {
    katexCssLoaded = false;
  });
}

// Sanitization allowlist tuned for markdown — keeps the preview safe from XSS
// while still allowing the rich output produced by GFM, KaTeX, and slug IDs.
const sanitizeSchema = {
  ...defaultSchema,
  attributes: {
    ...defaultSchema.attributes,
    "*": [
      ...(defaultSchema.attributes?.["*"] ?? []),
      ["className"],
      ["style"],
    ],
    code: [...(defaultSchema.attributes?.code ?? []), ["className"]],
    span: [...(defaultSchema.attributes?.span ?? []), ["className"], ["style"]],
    div: [...(defaultSchema.attributes?.div ?? []), ["className"]],
    a: [...(defaultSchema.attributes?.a ?? []), ["target"], ["rel"]],
    input: [["type"], ["checked"], ["disabled"]],
    // KaTeX renders MathML and inline SVG — allow the structural pieces.
    annotation: [["encoding"]],
    math: [["xmlns"], ["display"]],
    semantics: [],
    mo: [], mi: [], mn: [], mtext: [], mfrac: [], msup: [], msub: [], msqrt: [],
    mroot: [], mrow: [], mover: [], munder: [], munderover: [], mpadded: [],
    mphantom: [], mspace: [], mtable: [], mtr: [], mtd: [], mlabeledtr: [],
  },
  tagNames: [
    ...(defaultSchema.tagNames ?? []),
    "details", "summary", "kbd", "sub", "sup", "mark",
    // KaTeX output (when output: htmlAndMathml)
    "math", "semantics", "mrow", "mi", "mn", "mo", "ms", "mtext",
    "mfrac", "msup", "msub", "msubsup", "msqrt", "mroot", "munder", "mover",
    "munderover", "mtable", "mtr", "mtd", "mlabeledtr", "annotation", "mpadded",
    "mphantom", "mspace",
  ],
};

type Props = {
  source: string;
  className?: string;
  /** Add `target="_blank" rel="noopener noreferrer"` to external anchors. */
  safeLinks?: boolean;
  /** Set false to skip mermaid/katex auto-loading for previews on landing pages. */
  enableExtras?: boolean;
};

export function MarkdownPreview({
  source,
  className,
  safeLinks = true,
  enableExtras = true,
}: Props) {
  // Detect math up-front so we can lazy-load KaTeX styles only when needed.
  const hasMath = React.useMemo(
    () => enableExtras && /(\$\$[\s\S]+?\$\$|\$[^$\n]+\$)/.test(source),
    [source, enableExtras],
  );

  React.useEffect(() => {
    if (hasMath) ensureKatexCss();
  }, [hasMath]);

  // Apply emoji shortcodes (`:smile:` → 😄) before remark runs so they render
  // both inline and in headings without polluting the AST.
  const prepared = React.useMemo(
    () => (enableExtras ? applyEmojiShortcodes(source) : source),
    [source, enableExtras],
  );

  const remarkPlugins = React.useMemo(
    () => (hasMath ? [remarkGfm, remarkMath] : [remarkGfm]),
    [hasMath],
  );
  const rehypePlugins = React.useMemo(
    () =>
      hasMath
        ? [
            rehypeRaw,
            [rehypeSanitize, sanitizeSchema],
            rehypeSlug,
            [rehypeKatex, { strict: false, output: "html", throwOnError: false }],
            rehypeHighlight,
          ]
        : [
            rehypeRaw,
            [rehypeSanitize, sanitizeSchema],
            rehypeSlug,
            rehypeHighlight,
          ],
    [hasMath],
  );

  return (
    <div className={cn("markdown-preview", className)}>
      <ReactMarkdown
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        remarkPlugins={remarkPlugins as any}
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        rehypePlugins={rehypePlugins as any}
        components={{
          a({ node: _node, href, children, ...rest }) {
            const isExternal = typeof href === "string" && /^https?:\/\//i.test(href);
            return (
              <a
                href={href}
                {...(safeLinks && isExternal
                  ? { target: "_blank", rel: "noopener noreferrer nofollow" }
                  : {})}
                {...rest}
              >
                {children}
              </a>
            );
          },
          // Intercept ```mermaid``` fenced blocks and render them as SVG.
          code({ node: _node, className, children, ...rest }) {
            const text = String(children ?? "");
            const match = /language-(\w+)/.exec(className ?? "");
            const lang = match?.[1];
            if (enableExtras && lang === "mermaid") {
              return <MermaidBlock code={text.trimEnd()} />;
            }
            return (
              <code className={className} {...rest}>
                {children}
              </code>
            );
          },
        }}
      >
        {prepared}
      </ReactMarkdown>
    </div>
  );
}
