/* NAOMI — мини-рендер Markdown (стиль Gemini).
   Поддержка: заголовки, **жирный**, *курсив*, ~~зачёркнутый~, `инлайн-код`,
   нумерованные и маркированные списки, таблицы, цитаты, разделители, ссылки,
   блоки кода с языком, подсветкой синтаксиса и кнопкой копирования.
   Работает в браузере (window.NaomiMD) и в Node (module.exports) для тестов. */
(function (global) {
    'use strict';

    function esc(s) {
        return String(s)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;');
    }

    /* ─── подсветка синтаксиса ─── */
    const HASH_LANGS = new Set([
        'python', 'py', 'sh', 'bash', 'shell', 'zsh', 'yml', 'yaml', 'rb', 'ruby',
        'ini', 'conf', 'toml', 'dockerfile', 'makefile', 'r', 'pl', 'ps1',
    ]);

    const KEYWORDS = [
        'const', 'let', 'var', 'function', 'return', 'if', 'else', 'elif', 'for', 'while',
        'class', 'new', 'this', 'self', 'typeof', 'instanceof', 'import', 'export', 'from',
        'default', 'async', 'await', 'try', 'except', 'catch', 'finally', 'throw', 'raise',
        'switch', 'case', 'break', 'continue', 'pass', 'def', 'lambda', 'None', 'True',
        'False', 'null', 'undefined', 'true', 'false', 'and', 'or', 'not', 'in', 'is', 'of',
        'do', 'then', 'fi', 'done', 'esac', 'fn', 'func', 'mut', 'pub', 'impl', 'struct',
        'enum', 'match', 'use', 'mod', 'select', 'where', 'insert', 'into', 'values',
        'update', 'set', 'delete', 'join', 'left', 'right', 'inner', 'outer', 'group',
        'order', 'limit', 'echo', 'exit', 'global', 'yield', 'with', 'as', 'assert', 'del',
        'end', 'unless', 'require',
    ].join('|');

    function highlight(code, lang) {
        const hash = HASH_LANGS.has((lang || '').toLowerCase());
        const re = new RegExp(
            '(<!--[\\s\\S]*?-->|/\\*[\\s\\S]*?\\*/|//[^\\n]*' + (hash ? '|#[^\\n]*' : '') + ')' +
            '|("(?:\\\\.|[^"\\\\])*"|\'(?:\\\\.|[^\'\\\\])*\'|`(?:\\\\.|[^`\\\\])*`)' +
            '|\\b(0x[0-9a-fA-F]+|\\d+(?:\\.\\d+)?)\\b' +
            '|\\b(' + KEYWORDS + ')\\b',
            'g',
        );
        let out = '';
        let last = 0;
        let m;
        while ((m = re.exec(code))) {
            out += esc(code.slice(last, m.index));
            const cls = m[1] ? 'tok-com' : m[2] ? 'tok-str' : m[3] ? 'tok-num' : 'tok-kw';
            out += `<span class="${cls}">${esc(m[0])}</span>`;
            last = m.index + m[0].length;
            if (!m[0].length) re.lastIndex++;
        }
        return out + esc(code.slice(last));
    }

    const COPY_ICON = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';

    function codeBlockHtml(lang, code) {
        const label = lang
            ? lang.charAt(0).toUpperCase() + lang.slice(1)
            : 'Код';
        return `<div class="codeblock"><div class="cb-head"><span class="cb-lang">${esc(label)}</span>` +
            `<button class="cb-copy" type="button" title="Копировать код">${COPY_ICON}</button></div>` +
            `<pre><code>${highlight(code, lang)}</code></pre></div>`;
    }

    /* ─── инлайн-разметка ─── */
    function inline(s) {
        const codes = [];
        let t = esc(s);
        t = t.replace(/`([^`\n]+)`/g, (_, c) => {
            codes.push(c);
            return `\u0001${codes.length - 1}\u0001`;
        });
        t = t
            .replace(/\*\*\*([^*\n]+)\*\*\*/g, '<strong><em>$1</em></strong>')
            .replace(/\*\*([^*\n]+)\*\*/g, '<strong>$1</strong>')
            .replace(/(^|[^*\w])\*([^*\n]+)\*(?!\*)/g, '$1<em>$2</em>')
            .replace(/(^|[^\w\\])_([^_\n]+)_(?!\w)/g, '$1<em>$2</em>')
            .replace(/~~([^~\n]+)~~/g, '<del>$1</del>')
            .replace(/\[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
        return t.replace(/\u0001(\d+)\u0001/g, (_, i) => `<code>${codes[Number(i)]}</code>`);
    }

    /* ─── блочный парсер ─── */
    function render(text) {
        const lines = String(text ?? '').split(/\r?\n/);
        const blocks = [];
        let para = [];
        let i = 0;

        const flushPara = () => {
            if (para.length) {
                blocks.push(`<p class="md-p">${para.map(inline).join('<br>')}</p>`);
                para = [];
            }
        };

        while (i < lines.length) {
            const line = lines[i];

            const fence = line.match(/^```(\w*)\s*$/);
            if (fence) {
                flushPara();
                const codeLines = [];
                i++;
                while (i < lines.length && !/^```\s*$/.test(lines[i])) {
                    codeLines.push(lines[i]);
                    i++;
                }
                i++;
                blocks.push(codeBlockHtml(fence[1], codeLines.join('\n')));
                continue;
            }

            const h = line.match(/^(#{1,4})\s+(.+)$/);
            if (h) {
                flushPara();
                const lvl = Math.min(h[1].length, 3);
                blocks.push(`<span class="md-h md-h${lvl}">${inline(h[2])}</span>`);
                i++;
                continue;
            }

            if (/^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
                flushPara();
                blocks.push('<hr class="md-hr">');
                i++;
                continue;
            }

            if (/^\s*>\s?/.test(line)) {
                flushPara();
                const buf = [];
                while (i < lines.length && /^\s*>\s?/.test(lines[i])) {
                    buf.push(lines[i].replace(/^\s*>\s?/, ''));
                    i++;
                }
                blocks.push(`<blockquote class="md-quote">${render(buf.join('\n'))}</blockquote>`);
                continue;
            }

            const isSep = (l) => /^\s*\|?[\s:|-]*-[\s:|-]*\|?\s*$/.test(l);
            if (line.includes('|') && i + 1 < lines.length && isSep(lines[i + 1])) {
                flushPara();
                const parseRow = (l) => l.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim());
                const head = parseRow(line);
                i += 2;
                const rows = [];
                while (i < lines.length && lines[i].includes('|') && lines[i].trim()) {
                    rows.push(parseRow(lines[i]));
                    i++;
                }
                const th = head.map((c) => `<th>${inline(c)}</th>`).join('');
                const tb = rows
                    .map((r) => `<tr>${head.map((_, ci) => `<td>${inline(r[ci] ?? '')}</td>`).join('')}</tr>`)
                    .join('');
                blocks.push(`<div class="md-table-wrap"><table class="md-table"><thead><tr>${th}</tr></thead><tbody>${tb}</tbody></table></div>`);
                continue;
            }

            if (/^\s*\d+[.)]\s+/.test(line)) {
                flushPara();
                const first = line.match(/^\s*(\d+)[.)]\s+(.*)$/);
                const items = [first[2]];
                const start = Number(first[1]) || 1;
                i++;
                while (i < lines.length && /^\s*\d+[.)]\s+/.test(lines[i])) {
                    items.push(lines[i].replace(/^\s*\d+[.)]\s+/, ''));
                    i++;
                }
                const startAttr = start !== 1 ? ` start="${start}"` : '';
                blocks.push(`<ol class="md-ol"${startAttr}>${items.map((it) => `<li>${inline(it)}</li>`).join('')}</ol>`);
                continue;
            }

            if (/^\s*[-*•]\s+/.test(line)) {
                flushPara();
                const items = [];
                while (i < lines.length && /^\s*[-*•]\s+/.test(lines[i])) {
                    items.push(lines[i].replace(/^\s*[-*•]\s+/, ''));
                    i++;
                }
                blocks.push(`<ul class="md-ul">${items.map((it) => `<li>${inline(it)}</li>`).join('')}</ul>`);
                continue;
            }

            if (!line.trim()) {
                flushPara();
                i++;
                continue;
            }

            para.push(line);
            i++;
        }
        flushPara();
        return blocks.join('\n');
    }

    const NaomiMD = { render, highlight, inline };
    global.NaomiMD = NaomiMD;
    if (typeof module !== 'undefined' && module.exports) module.exports = NaomiMD;
})(typeof window !== 'undefined' ? window : globalThis);
