import React from "react";

const h = React.createElement;

type AnyRecord = Record<string, any>;

interface MarkdownBodyProps {
  markdown: any;
  className?: string;
  testId?: string;
}

interface FenceInfo {
  marker: "`" | "~";
  length: number;
  language: string;
}

interface ListItemInfo {
  ordered: boolean;
  text: string;
  start?: number;
}

interface InlineRenderOptions {
  linkify?: boolean;
}

export function MarkdownBody({ markdown, className, testId }: MarkdownBodyProps) {
  const text = String(markdown ?? "").trimEnd();
  const classes = ["markdown-body", className].filter(Boolean).join(" ");
  return h("div", { className: classes, "data-testid": testId }, renderMarkdownBlocks(text, "md"));
}

function renderMarkdownBlocks(markdown: string, keyPrefix: string): any[] {
  const lines = normalizeMarkdown(markdown).split("\n");
  const nodes: any[] = [];
  let index = 0;

  while (index < lines.length) {
    const line = lines[index] ?? "";
    if (line.trim().length === 0) {
      index += 1;
      continue;
    }

    const fence = matchFence(line);
    if (fence !== null) {
      const codeLines: string[] = [];
      index += 1;
      while (index < lines.length && !isClosingFence(lines[index] ?? "", fence)) {
        codeLines.push(lines[index] ?? "");
        index += 1;
      }
      if (index < lines.length) index += 1;
      nodes.push(renderCodeBlock(codeLines.join("\n"), fence.language, `${keyPrefix}-fence-${index}`));
      continue;
    }

    if (isIndentedCodeLine(line)) {
      const codeLines: string[] = [];
      while (index < lines.length && (isIndentedCodeLine(lines[index] ?? "") || (lines[index] ?? "").trim().length === 0)) {
        const current = lines[index] ?? "";
        codeLines.push(current.replace(/^(?: {4}|\t)/u, ""));
        index += 1;
      }
      nodes.push(renderCodeBlock(codeLines.join("\n").trimEnd(), "", `${keyPrefix}-indent-${index}`));
      continue;
    }

    const heading = line.match(/^(#{1,6})\s+(.+)$/u);
    if (heading !== null) {
      const sourceLevel = heading[1].length;
      const tag = `h${Math.min(6, sourceLevel + 2)}`;
      nodes.push(h(tag, { key: `${keyPrefix}-heading-${index}` }, renderInline(heading[2].trim(), `${keyPrefix}-heading-${index}`)));
      index += 1;
      continue;
    }

    if (/^\s*(?:---+|\*\*\*+|___+)\s*$/u.test(line)) {
      nodes.push(h("hr", { key: `${keyPrefix}-hr-${index}` }));
      index += 1;
      continue;
    }

    if (/^\s*>\s?/u.test(line)) {
      const quoteLines: string[] = [];
      while (index < lines.length) {
        const current = lines[index] ?? "";
        const quote = current.match(/^\s*>\s?(.*)$/u);
        if (quote !== null) {
          quoteLines.push(quote[1]);
          index += 1;
          continue;
        }
        if (current.trim().length === 0) {
          quoteLines.push("");
          index += 1;
          continue;
        }
        break;
      }
      nodes.push(h("blockquote", { key: `${keyPrefix}-quote-${index}` }, renderMarkdownBlocks(quoteLines.join("\n"), `${keyPrefix}-quote-${index}`)));
      continue;
    }

    if (isTableStart(lines, index)) {
      const tableStart = index;
      const header = splitTableRow(lines[index] ?? "");
      const separators = splitTableRow(lines[index + 1] ?? "");
      index += 2;
      const rows: string[][] = [];
      while (index < lines.length && (lines[index] ?? "").includes("|") && (lines[index] ?? "").trim().length > 0) {
        rows.push(splitTableRow(lines[index] ?? ""));
        index += 1;
      }
      nodes.push(renderTable(header, separators, rows, `${keyPrefix}-table-${tableStart}`));
      continue;
    }

    const listItem = matchListItem(line);
    if (listItem !== null) {
      const listStart = index;
      const ordered = listItem.ordered;
      const start = listItem.start;
      const items: string[] = [];
      while (index < lines.length) {
        const currentItem = matchListItem(lines[index] ?? "");
        if (currentItem === null || currentItem.ordered !== ordered) break;
        const itemLines = [currentItem.text];
        index += 1;
        while (index < lines.length) {
          const continuation = lines[index] ?? "";
          if (continuation.trim().length === 0) break;
          if (matchListItem(continuation) !== null) break;
          if (!/^\s{2,}/u.test(continuation)) break;
          itemLines.push(continuation.replace(/^\s{2,4}/u, ""));
          index += 1;
        }
        items.push(itemLines.join("\n").trimEnd());
      }
      const listProps: AnyRecord = { key: `${keyPrefix}-list-${listStart}` };
      if (ordered && start !== undefined && start !== 1) listProps.start = start;
      nodes.push(h(ordered ? "ol" : "ul", listProps, items.map((item, itemIndex) => renderListItem(item, `${keyPrefix}-list-${listStart}-${itemIndex}`))));
      continue;
    }

    const paragraphStart = index;
    const paragraphLines: string[] = [];
    while (index < lines.length && lines[index] !== undefined && lines[index].trim().length > 0 && !isBlockStart(lines, index)) {
      paragraphLines.push(lines[index].trim());
      index += 1;
    }
    if (paragraphLines.length === 0) {
      paragraphLines.push(line.trim());
      index += 1;
    }
    nodes.push(h("p", { key: `${keyPrefix}-p-${paragraphStart}` }, renderInline(paragraphLines.join("\n"), `${keyPrefix}-p-${paragraphStart}`)));
  }

  return nodes;
}

function normalizeMarkdown(markdown: string): string {
  return String(markdown || "").replace(/\r\n/gu, "\n").replace(/\r/gu, "\n").trimEnd();
}

function matchFence(line: string): FenceInfo | null {
  const match = line.match(/^\s*(```+|~~~+)\s*([A-Za-z0-9_-]+)?\s*$/u);
  if (match === null) return null;
  const fence = match[1];
  return {
    marker: fence.startsWith("`") ? "`" : "~",
    length: fence.length,
    language: match[2] || "",
  };
}

function isClosingFence(line: string, fence: FenceInfo): boolean {
  const trimmed = line.trim();
  return trimmed.length >= fence.length && trimmed.split("").every((char) => char === fence.marker);
}

function isIndentedCodeLine(line: string): boolean {
  return /^(?: {4}|\t)/u.test(line);
}

function renderCodeBlock(code: string, language: string, key: string): any {
  const className = language.trim().length > 0 ? `language-${safeClassToken(language)}` : undefined;
  return h("pre", { key, className: "markdown-code-block" }, h("code", { className }, code));
}

function isBlockStart(lines: string[], index: number): boolean {
  const line = lines[index] ?? "";
  if (line.trim().length === 0) return true;
  return matchFence(line) !== null
    || isIndentedCodeLine(line)
    || /^(#{1,6})\s+.+$/u.test(line)
    || /^\s*(?:---+|\*\*\*+|___+)\s*$/u.test(line)
    || /^\s*>\s?/u.test(line)
    || isTableStart(lines, index)
    || matchListItem(line) !== null;
}

function matchListItem(line: string): ListItemInfo | null {
  const unordered = line.match(/^\s{0,3}[-*+]\s+(.+)$/u);
  if (unordered !== null) return { ordered: false, text: unordered[1] };
  const ordered = line.match(/^\s{0,3}(\d+)[.)]\s+(.+)$/u);
  if (ordered !== null) return { ordered: true, start: Number(ordered[1]), text: ordered[2] };
  return null;
}

function renderListItem(text: string, key: string): any {
  const task = text.match(/^\[([ xX])\]\s+(.+)$/u);
  if (task !== null) {
    const checked = task[1].toLowerCase() === "x";
    return h("li", { key, className: "task-list-item" },
      h("input", { type: "checkbox", checked, readOnly: true, tabIndex: -1 }),
      h("span", null, renderInline(task[2], `${key}-task`)),
    );
  }
  return h("li", { key }, renderInline(text, key));
}

function isTableStart(lines: string[], index: number): boolean {
  const header = lines[index] ?? "";
  const separator = lines[index + 1] ?? "";
  if (!header.includes("|") || !separator.includes("|")) return false;
  const headerCells = splitTableRow(header);
  const separatorCells = splitTableRow(separator);
  return headerCells.length > 1
    && separatorCells.length === headerCells.length
    && separatorCells.every((cell) => /^:?-{3,}:?$/u.test(cell.trim()));
}

function splitTableRow(line: string): string[] {
  let source = line.trim();
  if (source.startsWith("|")) source = source.slice(1);
  if (source.endsWith("|")) source = source.slice(0, -1);
  return source.split("|").map((cell) => cell.trim());
}

function tableAlign(separator: string): "left" | "center" | "right" | undefined {
  const value = separator.trim();
  if (value.startsWith(":") && value.endsWith(":")) return "center";
  if (value.endsWith(":")) return "right";
  if (value.startsWith(":")) return "left";
  return undefined;
}

function renderTable(headers: string[], separators: string[], rows: string[][], key: string): any {
  const aligns = separators.map(tableAlign);
  return h("div", { key, className: "markdown-table-wrap" },
    h("table", null,
      h("thead", null,
        h("tr", null, headers.map((cell, index) => h("th", { key: `${key}-h-${index}`, style: aligns[index] ? { textAlign: aligns[index] } : undefined }, renderInline(cell, `${key}-h-${index}`)))),
      ),
      h("tbody", null, rows.map((row, rowIndex) => h("tr", { key: `${key}-r-${rowIndex}` },
        headers.map((_, cellIndex) => h("td", {
          key: `${key}-r-${rowIndex}-${cellIndex}`,
          style: aligns[cellIndex] ? { textAlign: aligns[cellIndex] } : undefined,
        }, renderInline(row[cellIndex] || "", `${key}-r-${rowIndex}-${cellIndex}`))),
      ))),
    ),
  );
}

function renderInline(text: string, keyPrefix: string, options: InlineRenderOptions = {}): any[] {
  const children: any[] = [];
  const pattern = /`([^`\n]+)`|\[([^\]\n]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)|(https?:\/\/[^\s<>)]+)|\*\*([^*\n]+)\*\*|__([^_\n]+)__|~~([^~\n]+)~~|\*([^*\n]+)\*|_([^_\n]+)_/gu;
  const linkify = options.linkify !== false;
  let cursor = 0;
  let tokenIndex = 0;
  for (const match of text.matchAll(pattern)) {
    const token = match[0];
    const offset = match.index ?? 0;
    appendText(children, text.slice(cursor, offset), `${keyPrefix}-text-${tokenIndex}`);
    cursor = offset + token.length;
    const key = `${keyPrefix}-inline-${tokenIndex}`;
    tokenIndex += 1;

    if (match[1] !== undefined) {
      children.push(h("code", { key }, match[1]));
      continue;
    }
    if (match[2] !== undefined && match[3] !== undefined) {
      if (!linkify) {
        appendText(children, token, `${key}-literal`);
        continue;
      }
      children.push(renderLink(match[2], match[3], key));
      continue;
    }
    if (match[4] !== undefined) {
      if (!linkify) {
        appendText(children, token, `${key}-literal`);
        continue;
      }
      children.push(renderLink(match[4], match[4], key));
      continue;
    }
    const strong = match[5] ?? match[6];
    if (strong !== undefined) {
      children.push(h("strong", { key }, renderInline(strong, `${key}-strong`)));
      continue;
    }
    if (match[7] !== undefined) {
      children.push(h("del", { key }, renderInline(match[7], `${key}-del`)));
      continue;
    }
    const emphasis = match[8] ?? match[9];
    if (emphasis !== undefined) {
      children.push(h("em", { key }, renderInline(emphasis, `${key}-em`)));
    }
  }
  appendText(children, text.slice(cursor), `${keyPrefix}-text-tail`);
  return children;
}

function appendText(children: any[], text: string, keyPrefix: string): void {
  if (text.length === 0) return;
  const parts = text.split("\n");
  parts.forEach((part, index) => {
    if (index > 0) children.push(h("br", { key: `${keyPrefix}-br-${index}` }));
    if (part.length > 0) children.push(part);
  });
}

function renderLink(label: string, href: string, key: string): any {
  const safeHref = safeMarkdownHref(href);
  if (safeHref === null) return h("span", { key }, label);
  const external = /^(?:https?:|mailto:)/iu.test(safeHref);
  return h("a", {
    key,
    href: safeHref,
    target: external ? "_blank" : undefined,
    rel: external ? "noreferrer" : undefined,
  }, renderInline(label, `${key}-label`, { linkify: false }));
}

function safeMarkdownHref(raw: string): string | null {
  const href = String(raw || "").trim();
  if (/^(?:https?:|mailto:)/iu.test(href)) return href;
  if (href.startsWith("/") && !href.startsWith("//")) return href;
  if (href.startsWith("#")) return href;
  return null;
}

function safeClassToken(value: string): string {
  return String(value || "").toLowerCase().replace(/[^a-z0-9_-]+/gu, "-").replace(/^-+|-+$/gu, "") || "text";
}
