// OrbitCosmo markdown — a deliberately tiny, safe renderer for LLM-written wiki
// pages. Two halves: parseMarkdown (pure, Node-testable) produces a block
// AST; renderMarkdownInto builds real DOM nodes from it with createElement +
// textContent only. LLM output derives from untrusted web content, so this
// is the XSS boundary: no innerHTML anywhere, raw HTML in the source renders
// as literal text, and only http(s) link targets become anchors.
//
// Supported: # ## ### headings, - / * bullets, 1. ordered items, **bold**,
// *italic*, `code`, [text](url), [[slug]] wiki-links, paragraphs, blank-line
// separation. Everything else is plain text — by design.

(function (root) {
  "use strict";

  // --- inline parsing -------------------------------------------------------
  // Returns [{ t: "text"|"bold"|"italic"|"code"|"link"|"wiki", s, href?, slug? }]
  const INLINE_RE =
    /(\*\*([^*]+)\*\*)|(\*([^*]+)\*)|(`([^`]+)`)|(\[\[([a-z0-9-]+)\]\])|(\[([^\]]+)\]\(([^)\s]+)\))/g;

  function safeHref(url) {
    return /^https?:\/\//i.test(url) ? url : null;
  }

  function parseInline(text) {
    const out = [];
    let last = 0;
    let m;
    INLINE_RE.lastIndex = 0;
    while ((m = INLINE_RE.exec(text)) !== null) {
      if (m.index > last) out.push({ t: "text", s: text.slice(last, m.index) });
      if (m[1]) out.push({ t: "bold", s: m[2] });
      else if (m[3]) out.push({ t: "italic", s: m[4] });
      else if (m[5]) out.push({ t: "code", s: m[6] });
      else if (m[7]) out.push({ t: "wiki", s: m[8], slug: m[8] });
      else if (m[9]) {
        const href = safeHref(m[11]);
        // Unsafe scheme → the whole thing stays visible literal text.
        if (href) out.push({ t: "link", s: m[10], href });
        else out.push({ t: "text", s: m[0] });
      }
      last = m.index + m[0].length;
    }
    if (last < text.length) out.push({ t: "text", s: text.slice(last) });
    return out;
  }

  // --- block parsing --------------------------------------------------------
  // Returns [{ t: "heading", level, inline } | { t: "list", ordered, items:[inline] } | { t: "para", inline }]
  function parseMarkdown(md) {
    const blocks = [];
    const lines = String(md || "").split("\n");
    let para = [];
    let list = null;

    function flushPara() {
      if (para.length) {
        blocks.push({ t: "para", inline: parseInline(para.join(" ")) });
        para = [];
      }
    }
    function flushList() {
      if (list) {
        blocks.push(list);
        list = null;
      }
    }

    for (const raw of lines) {
      const line = raw.trimEnd();
      const h = /^(#{1,3})\s+(.*)$/.exec(line);
      const ul = /^[-*]\s+(.*)$/.exec(line);
      const ol = /^\d+[.)]\s+(.*)$/.exec(line);
      if (!line.trim()) {
        flushPara();
        flushList();
      } else if (h) {
        flushPara();
        flushList();
        blocks.push({ t: "heading", level: h[1].length, inline: parseInline(h[2]) });
      } else if (ul || ol) {
        flushPara();
        const ordered = !!ol;
        const item = parseInline((ul || ol)[1]);
        if (!list || list.ordered !== ordered) {
          flushList();
          list = { t: "list", ordered, items: [] };
        }
        list.items.push(item);
      } else {
        flushList();
        para.push(line.trim());
      }
    }
    flushPara();
    flushList();
    return blocks;
  }

  // --- DOM rendering (browser only) ----------------------------------------
  // `onWikiLink(slug)` handles [[slug]] clicks; absent, wiki-links render as
  // plain emphasized text. External links open in a new tab.
  function renderMarkdownInto(el, md, { onWikiLink } = {}) {
    const doc = el.ownerDocument;

    function inlineInto(parent, inline) {
      for (const seg of inline) {
        if (seg.t === "text") {
          parent.appendChild(doc.createTextNode(seg.s));
        } else if (seg.t === "bold") {
          const b = doc.createElement("strong");
          b.textContent = seg.s;
          parent.appendChild(b);
        } else if (seg.t === "italic") {
          const i = doc.createElement("em");
          i.textContent = seg.s;
          parent.appendChild(i);
        } else if (seg.t === "code") {
          const c = doc.createElement("code");
          c.textContent = seg.s;
          parent.appendChild(c);
        } else if (seg.t === "wiki") {
          if (onWikiLink) {
            const a = doc.createElement("a");
            a.href = "#";
            a.className = "wikilink";
            a.textContent = seg.s;
            a.addEventListener("click", (e) => {
              e.preventDefault();
              onWikiLink(seg.slug);
            });
            parent.appendChild(a);
          } else {
            const em = doc.createElement("em");
            em.textContent = seg.s;
            parent.appendChild(em);
          }
        } else if (seg.t === "link") {
          const a = doc.createElement("a");
          a.href = seg.href;
          a.target = "_blank";
          a.rel = "noopener noreferrer";
          a.textContent = seg.s;
          parent.appendChild(a);
        }
      }
    }

    for (const block of parseMarkdown(md)) {
      if (block.t === "heading") {
        const h = doc.createElement("h" + (block.level + 2)); // h3..h5 inside the sheet
        inlineInto(h, block.inline);
        el.appendChild(h);
      } else if (block.t === "list") {
        const l = doc.createElement(block.ordered ? "ol" : "ul");
        for (const item of block.items) {
          const li = doc.createElement("li");
          inlineInto(li, item);
          l.appendChild(li);
        }
        el.appendChild(l);
      } else {
        const p = doc.createElement("p");
        inlineInto(p, block.inline);
        el.appendChild(p);
      }
    }
  }

  const api = { parseMarkdown, parseInline, safeHref, renderMarkdownInto };

  // Dual export: CommonJS for Node tests, global attach for extension pages.
  if (typeof module !== "undefined" && module.exports) {
    module.exports = api;
  } else {
    Object.assign(root, api);
  }
})(typeof globalThis !== "undefined" ? globalThis : self);
