// ==========================================
// Armature — SDK: Markdown Renderer
// ==========================================
// Unified markdown rendering via `marked` (v16, vendored).
// Delegates fenced code blocks to sw.renderers and
// sanitizes output with DOMPurify.
//
// Factory: createMarkdown(renderers)
// ==========================================

/**
 * Create the markdown module.
 *
 * @param {object} renderers — sw.renderers instance
 * @returns {object} markdown
 */
export function createMarkdown(renderers) {
    let _marked = null;
    let _purify = null;
    let _markedLoading = null;
    let _purifyLoading = null;

    const BASE = () => window.__BASE__ || '';

    // ── Lazy loaders ──────────────────────────

    function _loadMarked() {
        if (_marked) return Promise.resolve(_marked);
        if (_markedLoading) return _markedLoading;

        _markedLoading = new Promise((resolve, reject) => {
            if (window.marked) {
                _marked = window.marked;
                _configure();
                resolve(_marked);
                return;
            }

            const script = document.createElement('script');
            script.src = `${BASE()}/vendor/marked.min.js`;
            script.onload = () => {
                _marked = window.marked;
                _configure();
                resolve(_marked);
            };
            script.onerror = () => {
                reject(new Error('[sw.markdown] Failed to load marked'));
            };
            document.head.appendChild(script);
        });
        return _markedLoading;
    }

    function _loadPurify() {
        if (_purify) return Promise.resolve(_purify);
        if (_purifyLoading) return _purifyLoading;

        _purifyLoading = new Promise((resolve, reject) => {
            if (window.DOMPurify) {
                _purify = window.DOMPurify;
                resolve(_purify);
                return;
            }

            const script = document.createElement('script');
            script.src = `${BASE()}/vendor/purify.min.js`;
            script.onload = () => {
                _purify = window.DOMPurify;
                resolve(_purify);
            };
            script.onerror = () => {
                reject(new Error('[sw.markdown] Failed to load DOMPurify'));
            };
            document.head.appendChild(script);
        });
        return _purifyLoading;
    }

    // ── Marked configuration ──────────────────

    function _configure() {
        if (!_marked) return;

        _marked.use({
            // Wikilink tokenizer extension: [[Page Name]] and [[Page Name|Display]]
            extensions: [{
                name: 'wikilink',
                level: 'inline',
                start(src) {
                    return src.indexOf('[[');
                },
                tokenizer(src) {
                    const match = src.match(/^\[\[([^\]|]+?)(?:\|([^\]]+?))?\]\]/);
                    if (match) {
                        return {
                            type: 'wikilink',
                            raw: match[0],
                            target: match[1].trim(),
                            display: (match[2] || match[1]).trim(),
                        };
                    }
                },
                renderer(token) {
                    const target = _escapeAttr(token.target);
                    const display = _escapeHtml(token.display);
                    return `<a class="ext-notes-wikilink" data-wikilink="${target}" href="javascript:void(0)">${display}</a>`;
                },
            }],

            // Custom code block renderer — delegates to sw.renderers
            renderer: {
                code({ text, lang }) {
                    if (lang && renderers) {
                        const container = document.createElement('div');
                        if (renderers.runBlockRenderers(lang, text, container)) {
                            return container.innerHTML;
                        }
                    }
                    // Default: standard <pre><code>
                    const langClass = lang ? ` class="language-${_escapeAttr(lang)}"` : '';
                    return `<pre><code${langClass}>${_escapeHtml(text)}</code></pre>\n`;
                },
            },
        });
    }

    // ── DOMPurify config ──────────────────────
    // Allow SVG elements (for mermaid) + data attributes (for all renderers)

    const PURIFY_CONFIG = {
        ADD_TAGS: [
            'svg', 'g', 'path', 'rect', 'circle', 'ellipse', 'line',
            'polyline', 'polygon', 'text', 'tspan', 'defs', 'marker',
            'foreignObject', 'use', 'clipPath', 'mask', 'pattern',
            'linearGradient', 'radialGradient', 'stop', 'title', 'desc',
        ],
        ADD_ATTR: [
            'viewBox', 'd', 'fill', 'stroke', 'stroke-width', 'stroke-dasharray',
            'xmlns', 'xmlns:xlink', 'transform', 'cx', 'cy', 'r', 'rx', 'ry',
            'x', 'y', 'x1', 'y1', 'x2', 'y2', 'width', 'height',
            'preserveAspectRatio', 'points', 'font-size', 'font-family',
            'text-anchor', 'dominant-baseline', 'dy', 'dx',
            'marker-start', 'marker-mid', 'marker-end',
            'refX', 'refY', 'markerWidth', 'markerHeight', 'orient',
            'gradientTransform', 'gradientUnits', 'offset', 'stop-color', 'stop-opacity',
            'clip-path', 'opacity', 'fill-opacity', 'stroke-opacity',
        ],
        ALLOW_DATA_ATTR: true,
    };

    // ── Helpers ───────────────────────────────

    function _escapeHtml(str) {
        if (!str) return '';
        return str
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;');
    }

    function _escapeAttr(str) {
        if (!str) return '';
        return str
            .replace(/&/g, '&amp;')
            .replace(/"/g, '&quot;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;');
    }

    // ── Public API ────────────────────────────

    return {
        /**
         * Render markdown source to HTML string.
         * Loads marked lazily on first call.
         *
         * @param {string} src — markdown source
         * @param {object} [opts]
         * @param {boolean} [opts.sanitize=true] — run DOMPurify on output
         * @returns {Promise<string>} HTML
         */
        async render(src, opts = {}) {
            const sanitize = opts.sanitize !== false;

            await _loadMarked();
            let html = _marked.parse(src || '');

            if (sanitize) {
                await _loadPurify();
                html = _purify.sanitize(html, PURIFY_CONFIG);
            }

            return html;
        },

        /**
         * Synchronous render — only works after marked has been loaded.
         * Falls back to escaping if marked isn't ready.
         *
         * @param {string} src
         * @param {object} [opts]
         * @param {boolean} [opts.sanitize=true]
         * @returns {string} HTML
         */
        renderSync(src, opts = {}) {
            const sanitize = opts.sanitize !== false;

            if (!_marked) {
                return _escapeHtml(src || '');
            }

            let html = _marked.parse(src || '');

            if (sanitize && _purify) {
                html = _purify.sanitize(html, PURIFY_CONFIG);
            }

            return html;
        },

        /**
         * Render markdown into a DOM element, then run post renderers.
         *
         * @param {HTMLElement} container
         * @param {string} src
         * @param {object} [opts]
         * @param {boolean} [opts.sanitize=true]
         */
        async renderToElement(container, src, opts = {}) {
            const html = await this.render(src, opts);
            container.innerHTML = html;

            if (renderers) {
                renderers.runPostRenderers(container);
            }
        },

        /**
         * Preload both marked and DOMPurify.
         * Call early to avoid latency on first render.
         */
        async preload() {
            await Promise.all([_loadMarked(), _loadPurify()]);
        },

        /** True if marked is loaded and ready for sync use. */
        get ready() {
            return !!_marked;
        },
    };
}
