/**
 * Minimal markdown → HTML converter used server-side when rendering
 * admin-authored DB posts. Supports the subset the blog editor produces:
 * headings, paragraphs, bold/italic, inline code, lists (ordered/unordered),
 * blockquotes, links, images, horizontal rules, and plain-line breaks.
 *
 * Kept intentionally minimal — any richer Markdown (tables, code fences
 * with syntax highlighting, etc.) would pull in a dependency. The WP
 * migrator stores HTML directly so it's unaffected by this.
 */

const HEADING_RE = /^(#{1,6})\s+(.*)$/;
const UL_RE = /^[-*+]\s+(.*)$/;
const OL_RE = /^\d+\.\s+(.*)$/;
const QUOTE_RE = /^>\s+(.*)$/;
const RULE_RE = /^---+$/;

export function markdownToHtml(src: string): string {
  const lines = src.replace(/\r\n/g, "\n").split("\n");
  const out: string[] = [];
  let listType: "ul" | "ol" | null = null;
  let paragraph: string[] = [];
  let inVideoBlock = false;

  const closeList = () => {
    if (listType) {
      out.push(`</${listType}>`);
      listType = null;
    }
  };
  const flushParagraph = () => {
    if (paragraph.length) {
      out.push(`<p>${inlineMd(paragraph.join(" "))}</p>`);
      paragraph = [];
    }
  };

  for (const raw of lines) {
    const line = raw.replace(/\s+$/g, "");

    // Preserve video shortcodes verbatim so renderVideoShortcodes() can
    // replace them later.
    if (/^\[\[video\b/i.test(line.trim())) {
      flushParagraph();
      closeList();
      out.push(line);
      continue;
    }

    if (!line.trim()) {
      flushParagraph();
      closeList();
      inVideoBlock = false;
      continue;
    }
    if (RULE_RE.test(line.trim())) {
      flushParagraph();
      closeList();
      out.push("<hr />");
      continue;
    }
    const heading = HEADING_RE.exec(line);
    if (heading) {
      flushParagraph();
      closeList();
      const level = heading[1].length;
      out.push(`<h${level}>${inlineMd(heading[2])}</h${level}>`);
      continue;
    }
    const quote = QUOTE_RE.exec(line);
    if (quote) {
      flushParagraph();
      closeList();
      out.push(`<blockquote><p>${inlineMd(quote[1])}</p></blockquote>`);
      continue;
    }
    const ul = UL_RE.exec(line);
    const ol = OL_RE.exec(line);
    if (ul) {
      flushParagraph();
      if (listType !== "ul") {
        closeList();
        out.push("<ul>");
        listType = "ul";
      }
      out.push(`<li>${inlineMd(ul[1])}</li>`);
      continue;
    }
    if (ol) {
      flushParagraph();
      if (listType !== "ol") {
        closeList();
        out.push("<ol>");
        listType = "ol";
      }
      out.push(`<li>${inlineMd(ol[1])}</li>`);
      continue;
    }
    closeList();
    paragraph.push(line);
  }
  flushParagraph();
  closeList();
  // Keep the linter happy — `inVideoBlock` reserved for future multi-line shortcodes.
  void inVideoBlock;
  return out.join("\n");
}

function escapeHtml(s: string): string {
  return s
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

function inlineMd(s: string): string {
  let out = escapeHtml(s);
  // Images (must run before links so ![...](...) doesn't get treated as link).
  out = out.replace(
    /!\[([^\]]*)\]\(([^)\s]+)\)/g,
    (_, alt: string, url: string) =>
      `<img src="${url}" alt="${alt}" />`,
  );
  // Links.
  out = out.replace(
    /\[([^\]]+)\]\(([^)\s]+)\)/g,
    (_, label: string, url: string) => `<a href="${url}">${label}</a>`,
  );
  // Inline code.
  out = out.replace(/`([^`]+)`/g, "<code>$1</code>");
  // Bold / italic.
  out = out.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
  out = out.replace(
    /(^|[\s(])_([^_]+)_(?=[\s).,!?;:]|$)/g,
    "$1<em>$2</em>",
  );
  return out;
}
