/**
 * Markdown-aware paste for the compose editors.
 *
 * Pasting text copied from AI chats, READMEs, or notes apps lands as flat
 * plain text (or worse, gets shoved into a single <pre><code> blob by the
 * editor's paste normalizer) — headings, lists, bold, quotes all lost
 * (Bob 2026-07-28: "there should be a blockquote option rather than
 * treating all text as pre/code — even better if it could paste in as html
 * with markup"). This module detects markdown-looking PLAIN-text pastes and
 * converts them to clean email HTML: real <blockquote> for quoted lines,
 * <ul>/<ol>, headings, bold, code spans, fenced blocks, links, pipe tables.
 *
 * Deliberately conservative: only fires when the clipboard has NO text/html
 * flavor (rich sources keep their own markup) and the plain text scores as
 * markdown (fence pair or pipe table alone qualify; otherwise two distinct
 * signals). Ordinary prose pastes are untouched.
 *
 * Output is email-safe: semantic tags with minimal inline styles, no CSS
 * variables, no classes — it must survive every recipient's renderer.
 */

/** Conservative "is this markdown?" score. False for ordinary prose. */
export function looksLikeMarkdown(text: string): boolean {
    if (!text || text.length > 200_000) return false;
    const lines = text.split(/\r?\n/);
    // A fenced code block pair is unambiguous.
    const fences = text.match(/^\s*```/gm);
    if (fences && fences.length >= 2) return true;
    // A pipe table (header row + |---| separator) is unambiguous.
    for (let i = 1; i < lines.length; i++) {
        if (/^\s*\|/.test(lines[i - 1]) && /^\s*\|[\s\-:|]+\|?\s*$/.test(lines[i]) && lines[i].includes("-")) return true;
    }
    let signals = 0;
    if (lines.some(l => /^#{1,6}\s+\S/.test(l))) signals++;
    if (lines.filter(l => /^\s*[-*+]\s+\S/.test(l)).length >= 2) signals++;
    if (lines.filter(l => /^\s*\d+[.)]\s+\S/.test(l)).length >= 2) signals++;
    if (/\*\*[^*\n]+\*\*/.test(text)) signals++;
    if (/\[[^\]\n]+\]\([^)\s]+\)/.test(text)) signals++;
    if (lines.filter(l => /^>\s?/.test(l)).length >= 2) signals++;
    return signals >= 2;
}

function esc(s: string): string {
    return s.replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;", "'": "&#39;" }[c]!));
}

/** Inline markdown: code spans, bold, italic, [text](url), bare URLs.
 *  Code spans swap out first behind a control-char token (esc() already
 *  ran, and U+0000 never survives a real clipboard) so their contents are
 *  exempt from the styling rules below. */
function inlineMd(s: string): string {
    const codeSpans: string[] = [];
    const TOK = " ";
    let t = esc(s).replace(/`([^`]+)`/g, (_m, code: string) => {
        codeSpans.push(`<code style="font-family:Consolas,monospace;background:#f4f4f4;padding:1px 4px;border-radius:3px;">${code}</code>`);
        return TOK + (codeSpans.length - 1) + TOK;
    });
    t = t
        .replace(/\*\*([^*\n]+)\*\*/g, "<b>$1</b>")
        .replace(/(?<![\w*])\*([^*\n]+)\*(?![\w*])/g, "<i>$1</i>")
        .replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g, (_m, txt: string, url: string) =>
            /^(https?|mailto):/i.test(url) ? `<a href="${url}">${txt}</a>` : `${txt} (${url})`)
        .replace(/(?<!["'=>[\w])(https?:\/\/[^\s<>"')\]]+)/g, "<a href=\"$1\">$1</a>");
    return t.replace(/ (\d+) /g, (_m, i: string) => codeSpans[Number(i)] ?? "");
}

const BLOCKQUOTE_STYLE = "margin:4px 0 4px 0;padding:2px 0 2px 12px;border-left:3px solid #b8b8b8;";
const PRE_STYLE = "font-family:Consolas,monospace;font-size:0.95em;background:#f4f4f4;padding:8px 10px;border-radius:4px;overflow-x:auto;";

/** Convert a markdown string to email-safe HTML. */
export function markdownToEmailHtml(md: string): string {
    const lines = md.split(/\r?\n/);
    const out: string[] = [];
    let i = 0;
    while (i < lines.length) {
        const line = lines[i];
        // Fenced code block → <pre> (verbatim, escaped).
        if (/^\s*```/.test(line)) {
            const buf: string[] = [];
            i++;
            while (i < lines.length && !/^\s*```/.test(lines[i])) { buf.push(lines[i]); i++; }
            i++; // closing fence (or EOF)
            out.push(`<pre style="${PRE_STYLE}">${esc(buf.join("\n"))}</pre>`);
            continue;
        }
        // Heading — mapped down to h3..h6 so a pasted "# Title" doesn't
        // shout in an email.
        const h = /^(#{1,6})\s+(.*)$/.exec(line);
        if (h) {
            const level = Math.min(6, Math.max(3, h[1].length + 2));
            out.push(`<h${level} style="margin:10px 0 4px 0;">${inlineMd(h[2])}</h${level}>`);
            i++;
            continue;
        }
        // Blockquote run — strip one ">" level, recurse for the inner text
        // so nested markdown (lists, bold, deeper quotes) still renders.
        if (/^>\s?/.test(line)) {
            const buf: string[] = [];
            while (i < lines.length && /^>\s?/.test(lines[i])) {
                buf.push(lines[i].replace(/^>\s?/, ""));
                i++;
            }
            out.push(`<blockquote style="${BLOCKQUOTE_STYLE}">${markdownToEmailHtml(buf.join("\n"))}</blockquote>`);
            continue;
        }
        // Pipe table.
        if (/^\s*\|/.test(line) && i + 1 < lines.length && /^\s*\|[\s\-:|]+\|?\s*$/.test(lines[i + 1])) {
            const rows: string[][] = [];
            while (i < lines.length && /^\s*\|/.test(lines[i])) {
                rows.push(lines[i].trim().replace(/^\||\|$/g, "").split("|").map(c => c.trim()));
                i++;
            }
            const head = rows[0];
            const data = rows.slice(2);
            const cellStyle = "border:1px solid #ccc;padding:4px 10px;text-align:left;";
            out.push(`<table style="border-collapse:collapse;margin:6px 0;">` +
                `<thead><tr>${head.map(c => `<th style="${cellStyle}">${inlineMd(c)}</th>`).join("")}</tr></thead>` +
                `<tbody>${data.map(r => `<tr>${r.map(c => `<td style="${cellStyle}">${inlineMd(c)}</td>`).join("")}</tr>`).join("")}</tbody></table>`);
            continue;
        }
        // Bullet list.
        if (/^\s*[-*+]\s+\S/.test(line)) {
            const items: string[] = [];
            while (i < lines.length && /^\s*[-*+]\s+/.test(lines[i])) {
                items.push(lines[i].replace(/^\s*[-*+]\s+/, ""));
                i++;
            }
            out.push(`<ul style="margin:4px 0 4px 24px;padding:0;">${items.map(it => `<li>${inlineMd(it)}</li>`).join("")}</ul>`);
            continue;
        }
        // Numbered list.
        if (/^\s*\d+[.)]\s+\S/.test(line)) {
            const items: string[] = [];
            while (i < lines.length && /^\s*\d+[.)]\s+/.test(lines[i])) {
                items.push(lines[i].replace(/^\s*\d+[.)]\s+/, ""));
                i++;
            }
            out.push(`<ol style="margin:4px 0 4px 24px;padding:0;">${items.map(it => `<li>${inlineMd(it)}</li>`).join("")}</ol>`);
            continue;
        }
        // Horizontal rule.
        if (/^\s*(---+|\*\*\*+|___+)\s*$/.test(line)) {
            out.push(`<hr style="border:none;border-top:1px solid #ccc;margin:8px 0;">`);
            i++;
            continue;
        }
        // Blank line — paragraph separator.
        if (/^\s*$/.test(line)) { i++; continue; }
        // Paragraph: consecutive plain lines, hard breaks preserved.
        {
            const buf: string[] = [];
            while (i < lines.length && !/^\s*$/.test(lines[i])
                && !/^\s*```|^#{1,6}\s+\S|^>\s?|^\s*[-*+]\s+\S|^\s*\d+[.)]\s+\S/.test(lines[i])
                && !(/^\s*\|/.test(lines[i]) && i + 1 < lines.length && /^\s*\|[\s\-:|]+\|?\s*$/.test(lines[i + 1]))) {
                buf.push(lines[i]);
                i++;
            }
            out.push(`<p style="margin:4px 0;">${buf.map(inlineMd).join("<br>")}</p>`);
        }
    }
    return out.join("\n");
}

/** Conservative "is this raw HTML source?" test. Fires only for plain-text
 *  clipboards that BEGIN with a recognizable tag and contain at least one
 *  closing tag — prose that merely mentions "<something>" stays prose.
 *  (Bob 2026-07-30: inserting HTML source must render as markup — divs and
 *  all — never get flattened to text or shoved into pre/code.) */
export function looksLikeHtml(text: string): boolean {
    if (!text || text.length > 500_000) return false;
    const t = text.trim();
    if (!t.startsWith("<")) return false;
    if (!/^<(!doctype|html|head|body|div|p|table|ul|ol|li|h[1-6]|blockquote|section|article|header|footer|span|b|i|strong|em|a|img|br|hr|pre|code|font|center)\b/i.test(t)) return false;
    return /<\/[a-z][a-z0-9]*\s*>/i.test(t) || /^<(img|br|hr|!doctype)\b/i.test(t);
}

/** Sanitize pasted HTML source for insertion into compose: unwrap a full
 *  document to its <body>, drop script/style blocks, strip inline event
 *  handlers and javascript: URLs. Layout markup passes through untouched —
 *  the point is to render the user's HTML, not rewrite it. */
export function sanitizeHtmlSource(html: string): string {
    let t = html;
    const bm = t.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
    if (bm) t = bm[1];
    return t
        .replace(/<script\b[\s\S]*?<\/script\s*>/gi, "")
        .replace(/<style\b[\s\S]*?<\/style\s*>/gi, "")
        .replace(/\son[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, "")
        .replace(/\s(href|src)\s*=\s*(["']?)\s*javascript:[^"'\s>]*\2/gi, "");
}

/** Wire the markdown-paste behavior onto an editable element. Capture-phase
 *  so it runs before the editor engine's own paste pipeline; only consumes
 *  the event when it actually converts (no text/html flavor + text scores
 *  as markdown), so every other paste flows through untouched. */
export function wireMarkdownPaste(target: HTMLElement, insertHtml: (html: string) => void): void {
    target.addEventListener("paste", (e: ClipboardEvent) => {
        try {
            const cb = e.clipboardData;
            if (!cb) return;
            if (cb.getData("text/html")) return;             // rich source — keep its markup
            // Text-family FILE pastes (.md/.html/.txt copied in Explorer)
            // convert like their text equivalents; any other file kind
            // (images etc.) stays with the engine's own paste handling.
            const fileItem = Array.from(cb.items).find(it => it.kind === "file");
            if (fileItem) {
                const file = fileItem.getAsFile();
                const name = (file?.name || "").toLowerCase();
                const isMd = fileItem.type === "text/markdown" || name.endsWith(".md") || name.endsWith(".markdown");
                const isHtmlFile = fileItem.type === "text/html" || name.endsWith(".html") || name.endsWith(".htm");
                const isTxt = fileItem.type === "text/plain" || name.endsWith(".txt");
                if (file && (isMd || isHtmlFile || isTxt)) {
                    e.preventDefault();
                    e.stopImmediatePropagation();
                    file.text().then((text) => {
                        if (isMd) insertHtml(markdownToEmailHtml(text));
                        else if (isHtmlFile || looksLikeHtml(text)) insertHtml(sanitizeHtmlSource(text));
                        else if (looksLikeMarkdown(text)) insertHtml(markdownToEmailHtml(text));
                        else insertHtml(esc(text).replace(/\r?\n/g, "<br>"));
                    }).catch(() => { /* unreadable file — nothing inserted */ });
                }
                return;
            }
            const plain = cb.getData("text/plain");
            if (!plain) return;
            // HTML source first (it can contain markdown-looking runs);
            // markdown second. Neither matched → normal paste.
            let converted: string | null = null;
            if (looksLikeHtml(plain)) converted = sanitizeHtmlSource(plain);
            else if (looksLikeMarkdown(plain)) converted = markdownToEmailHtml(plain);
            if (converted === null) return;
            e.preventDefault();
            e.stopImmediatePropagation();
            insertHtml(converted);
        } catch { /* fall through to the engine's normal paste */ }
    }, true);
}
