import * as React from "react";
import { cn } from "@/lib/utils";

/**
 * Renders admin-authored instruction HTML (PaymentMethod.instructionsHtml) as real
 * React elements through a strict allow-list — no innerHTML anywhere, so a stray
 * script tag, event handler or javascript: URL simply cannot reach the DOM.
 * Disallowed tags are dropped but their text content is kept.
 */

type TextNode = { type: "text"; text: string };
type ElementNode = { type: "el"; tag: string; href?: string; children: HtmlNode[] };
type HtmlNode = TextNode | ElementNode;

const ALLOWED_TAGS = new Set([
  "p",
  "br",
  "hr",
  "strong",
  "b",
  "em",
  "i",
  "u",
  "small",
  "span",
  "div",
  "h3",
  "h4",
  "h5",
  "ul",
  "ol",
  "li",
  "code",
  "pre",
  "blockquote",
  "a",
  "table",
  "thead",
  "tbody",
  "tr",
  "th",
  "td",
]);

const VOID_TAGS = new Set(["br", "hr"]);

const TAG_RE = /<\/?([a-zA-Z][a-zA-Z0-9]*)\b([^>]*)>/g;
const HREF_RE = /href\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))/i;

const ENTITIES: Record<string, string> = {
  amp: "&",
  lt: "<",
  gt: ">",
  quot: '"',
  apos: "'",
  nbsp: " ",
  hellip: "…",
  mdash: "—",
  ndash: "–",
  euro: "€",
  copy: "©",
};

function decodeEntities(input: string): string {
  return input.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (whole, body: string) => {
    if (body.startsWith("#x") || body.startsWith("#X")) {
      const code = Number.parseInt(body.slice(2), 16);
      return Number.isFinite(code) && code > 0 ? String.fromCodePoint(code) : whole;
    }
    if (body.startsWith("#")) {
      const code = Number.parseInt(body.slice(1), 10);
      return Number.isFinite(code) && code > 0 ? String.fromCodePoint(code) : whole;
    }
    return ENTITIES[body.toLowerCase()] ?? whole;
  });
}

/** Only absolute http(s), mailto and site-relative links survive. */
function safeHref(raw: string | undefined): string | undefined {
  if (!raw) return undefined;
  const value = decodeEntities(raw).trim();
  if (!value) return undefined;
  if (/^https?:\/\//i.test(value)) return value;
  if (/^mailto:[^\s]+@[^\s]+$/i.test(value)) return value;
  if (value.startsWith("/") && !value.startsWith("//")) return value;
  return undefined;
}

function parse(html: string): HtmlNode[] {
  const root: ElementNode = { type: "el", tag: "#root", children: [] };
  const stack: ElementNode[] = [root];
  let cursor = 0;

  const pushText = (raw: string) => {
    if (!raw) return;
    const text = decodeEntities(raw);
    if (!text.trim()) return;
    stack[stack.length - 1].children.push({ type: "text", text });
  };

  const matches = Array.from(html.matchAll(TAG_RE));

  for (const match of matches) {
    const start = match.index ?? 0;
    pushText(html.slice(cursor, start));
    cursor = start + match[0].length;

    const tag = match[1].toLowerCase();
    const attrs = match[2] ?? "";
    const closing = match[0].startsWith("</");

    if (!ALLOWED_TAGS.has(tag)) continue;

    if (closing) {
      for (let i = stack.length - 1; i > 0; i--) {
        if (stack[i].tag === tag) {
          stack.length = i;
          break;
        }
      }
      continue;
    }

    const selfClosing = VOID_TAGS.has(tag) || /\/\s*$/.test(attrs);
    const node: ElementNode = { type: "el", tag, children: [] };
    if (tag === "a") {
      const hrefMatch = attrs.match(HREF_RE);
      node.href = safeHref(hrefMatch?.[1] ?? hrefMatch?.[2] ?? hrefMatch?.[3]);
    }

    stack[stack.length - 1].children.push(node);
    if (!selfClosing) stack.push(node);
  }

  pushText(html.slice(cursor));
  return root.children;
}

const TAG_CLASS: Record<string, string> = {
  h3: "font-display text-sm font-semibold text-foreground",
  h4: "font-display text-sm font-semibold text-foreground",
  h5: "font-display text-xs font-semibold uppercase tracking-wide text-foreground",
  ul: "list-disc space-y-1 pl-5",
  ol: "list-decimal space-y-1 pl-5",
  strong: "font-medium text-foreground",
  b: "font-medium text-foreground",
  em: "italic",
  i: "italic",
  code: "rounded bg-muted px-1 py-0.5 font-mono text-xs text-foreground",
  pre: "overflow-x-auto rounded-lg bg-muted p-3 font-mono text-xs text-foreground",
  blockquote: "border-l-2 border-primary/40 pl-3 italic",
  a: "text-primary underline underline-offset-2 transition-colors hover:text-primary/80",
  table: "w-full border-collapse text-left",
  th: "border-b py-1.5 pr-3 text-xs font-semibold text-foreground",
  td: "border-b py-1.5 pr-3",
  hr: "my-3 border-t",
};

/** Every allowed tag except a / br / hr, which are rendered explicitly. */
type BlockTag =
  | "p"
  | "strong"
  | "b"
  | "em"
  | "i"
  | "u"
  | "small"
  | "span"
  | "div"
  | "h3"
  | "h4"
  | "h5"
  | "ul"
  | "ol"
  | "li"
  | "code"
  | "pre"
  | "blockquote"
  | "table"
  | "thead"
  | "tbody"
  | "tr"
  | "th"
  | "td";

function render(nodes: HtmlNode[], keyPrefix: string): React.ReactNode[] {
  return nodes.map((node, index) => {
    const key = `${keyPrefix}-${index}`;
    if (node.type === "text") return <React.Fragment key={key}>{node.text}</React.Fragment>;

    if (node.tag === "br") return <br key={key} />;
    if (node.tag === "hr") return <hr key={key} className={TAG_CLASS.hr} />;

    const children = render(node.children, key);

    if (node.tag === "a") {
      if (!node.href) return <React.Fragment key={key}>{children}</React.Fragment>;
      return (
        <a
          key={key}
          href={node.href}
          className={TAG_CLASS.a}
          target={node.href.startsWith("/") ? undefined : "_blank"}
          rel="noopener noreferrer nofollow"
        >
          {children}
        </a>
      );
    }

    const Tag = node.tag as BlockTag;
    return (
      <Tag key={key} className={TAG_CLASS[node.tag] || undefined}>
        {children}
      </Tag>
    );
  });
}

export function AdminHtml({
  html,
  className,
}: {
  html: string | null | undefined;
  className?: string;
}) {
  if (!html) return null;
  const nodes = parse(html);
  if (!nodes.length) return null;
  return (
    <div className={cn("space-y-2 text-sm leading-relaxed text-muted-foreground", className)}>
      {render(nodes, "h")}
    </div>
  );
}

export default AdminHtml;
