const { describe, it } = require('node:test');
const assert = require('node:assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');

const SRC = path.join(__dirname, '..');

/**
 * Load the renderers module into a VM context and return the factory.
 */
function loadRenderers() {
    const source = fs.readFileSync(path.join(SRC, 'sw/sdk/renderers.js'), 'utf-8');
    // Strip ESM syntax for VM execution
    const cjs = source.replace(/^export /gm, '');
    const sandbox = { console, module: { exports: {} } };
    vm.createContext(sandbox);
    vm.runInContext(cjs, sandbox);
    return sandbox.createRenderers;
}

describe('sw.renderers', () => {
    const createRenderers = loadRenderers();

    it('register() adds a block renderer', () => {
        const events = [];
        const r = createRenderers((name, data, opts) => events.push({ name, data }));
        r.register('test-block', { type: 'block', pattern: 'mermaid', render() {} });
        const info = r.list();
        assert.strictEqual(info.block.length, 1);
        assert.strictEqual(info.block[0].name, 'test-block');
        assert.strictEqual(events.length, 1);
        assert.strictEqual(events[0].data.action, 'register');
    });

    it('register() adds a post renderer', () => {
        const r = createRenderers(() => {});
        r.register('test-post', { type: 'post', render() {} });
        const info = r.list();
        assert.strictEqual(info.post.length, 1);
        assert.strictEqual(info.post[0].name, 'test-post');
    });

    it('register() replaces duplicate by name', () => {
        const r = createRenderers(() => {});
        r.register('dup', { type: 'block', pattern: 'x', render() {} });
        r.register('dup', { type: 'block', pattern: 'y', render() {} });
        const info = r.list();
        assert.strictEqual(info.block.length, 1);
        assert.strictEqual(info.block[0].pattern, 'y');
    });

    it('unregister() removes a renderer', () => {
        const r = createRenderers(() => {});
        r.register('rm-me', { type: 'block', pattern: 'x', render() {} });
        r.unregister('rm-me');
        assert.strictEqual(r.list().block.length, 0);
    });

    it('register() returns an unregister function', () => {
        const r = createRenderers(() => {});
        const unreg = r.register('auto-unreg', { type: 'post', render() {} });
        assert.strictEqual(r.list().post.length, 1);
        unreg();
        assert.strictEqual(r.list().post.length, 0);
    });

    it('runBlockRenderers() matches by pattern', () => {
        const r = createRenderers(() => {});
        let called = false;
        r.register('m', { type: 'block', pattern: 'mermaid', render() { called = true; } });
        const result = r.runBlockRenderers('mermaid', 'code', {});
        assert.ok(result);
        assert.ok(called);
    });

    it('runBlockRenderers() matches by match function', () => {
        const r = createRenderers(() => {});
        let called = false;
        r.register('k', {
            type: 'block',
            match(lang) { return lang === 'latex' || lang === 'math'; },
            render() { called = true; },
        });
        assert.ok(r.runBlockRenderers('latex', 'code', {}));
        assert.ok(called);
    });

    it('runBlockRenderers() returns false when no match', () => {
        const r = createRenderers(() => {});
        r.register('m', { type: 'block', pattern: 'mermaid', render() {} });
        assert.strictEqual(r.runBlockRenderers('javascript', 'code', {}), false);
    });

    it('runBlockRenderers() case-insensitive on lang', () => {
        const r = createRenderers(() => {});
        let called = false;
        r.register('m', { type: 'block', pattern: 'mermaid', render() { called = true; } });
        assert.ok(r.runBlockRenderers('Mermaid', 'code', {}));
        assert.ok(called);
    });

    it('runPostRenderers() calls all post renderers', () => {
        const r = createRenderers(() => {});
        const order = [];
        r.register('a', { type: 'post', priority: 20, render() { order.push('a'); } });
        r.register('b', { type: 'post', priority: 10, render() { order.push('b'); } });
        r.runPostRenderers({});
        assert.deepStrictEqual(order, ['b', 'a']); // priority 10 first
    });

    it('block renderers respect priority (first match wins)', () => {
        const r = createRenderers(() => {});
        let winner = '';
        r.register('low', { type: 'block', pattern: 'test', priority: 50, render() { winner = 'low'; } });
        r.register('high', { type: 'block', pattern: 'test', priority: 10, render() { winner = 'high'; } });
        r.runBlockRenderers('test', '', {});
        assert.strictEqual(winner, 'high');
    });

    it('runBlockRenderers() catches render errors gracefully', () => {
        const r = createRenderers(() => {});
        r.register('bad', { type: 'block', pattern: 'err', render() { throw new Error('boom'); } });
        const result = r.runBlockRenderers('err', 'code', {});
        assert.strictEqual(result, false); // error → returns false
    });

    it('runPostRenderers() catches errors and continues', () => {
        const r = createRenderers(() => {});
        const order = [];
        r.register('bad', { type: 'post', priority: 10, render() { throw new Error('boom'); } });
        r.register('good', { type: 'post', priority: 20, render() { order.push('good'); } });
        r.runPostRenderers({});
        assert.deepStrictEqual(order, ['good']); // continues after error
    });
});
