// Verify the alpha-build heads-up banner: markup is present in
// static/index.html and `_updateAlphaWarningBanner(version)` in
// static/app.js toggles its visibility correctly per the version string.

const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');

const INDEX_HTML = path.join(__dirname, '..', '..', 'static', 'index.html');
const APP_JS = path.join(__dirname, '..', '..', 'static', 'app.js');

test('index.html ships the alpha-warning banner inside the library section', () => {
    const html = fs.readFileSync(INDEX_HTML, 'utf8');

    // Locate the library section so we can prove the banner lives there
    // and not stuck somewhere it would render off-screen.
    const libStart = html.indexOf('id="library-section"');
    assert.ok(libStart !== -1, 'library-section anchor not found in index.html');
    const libEnd = html.indexOf('</section>', libStart);
    assert.ok(libEnd !== -1, 'library-section closing tag not found');
    const librarySection = html.slice(libStart, libEnd);

    assert.match(
        librarySection,
        /id="alpha-warning-banner"/,
        'alpha-warning-banner must live inside library-section',
    );
    // `hidden` Tailwind class must be present so the banner stays invisible
    // until JS opts it in via classList.toggle('hidden', false).
    assert.match(
        librarySection,
        /id="alpha-warning-banner"[^>]*\bhidden\b/,
        'alpha-warning-banner must start with the `hidden` class',
    );
    // role="status" gives screen readers a non-interrupting announcement
    // rather than treating it as decorative.
    assert.match(
        librarySection,
        /id="alpha-warning-banner"[^>]*role="status"/,
        'alpha-warning-banner must declare role="status"',
    );
});

// Brace-balanced extraction so the function body — including nested
// object literals, template strings, or future guards — survives a
// naive regex stopping at the first `}`.
function extractFunctionSource(src, name) {
    const sig = `function ${name}`;
    const start = src.indexOf(sig);
    assert.ok(start !== -1, `function declaration '${name}' not found`);
    const openBrace = src.indexOf('{', start);
    assert.ok(openBrace !== -1, `opening brace after '${name}' not found`);
    let depth = 1;
    let i = openBrace + 1;
    while (i < src.length && depth > 0) {
        const ch = src[i];
        if (ch === '{') depth++;
        else if (ch === '}') depth--;
        i++;
    }
    assert.ok(depth === 0, `unbalanced braces in function '${name}'`);
    return src.slice(start, i);
}

// Build a fresh sandbox per test so cross-case state can't leak —
// classList.toggle mutations on one fake banner can't bias the next case.
function setupSandbox({ bannerExists = true } = {}) {
    const classes = new Set(['hidden']);
    const banner = bannerExists ? {
        classList: {
            // Mirror DOMTokenList.toggle: when `force` is provided, it
            // sets/removes deterministically; the production code relies
            // on that signature, so test it the same way.
            toggle: (cls, force) => {
                if (force === true) classes.add(cls);
                else if (force === false) classes.delete(cls);
                else if (classes.has(cls)) classes.delete(cls);
                else classes.add(cls);
            },
        },
    } : null;
    const sandbox = {
        document: {
            getElementById: (id) => (id === 'alpha-warning-banner' ? banner : null),
        },
    };
    vm.createContext(sandbox);
    const src = fs.readFileSync(APP_JS, 'utf8');
    const fnSrc = extractFunctionSource(src, '_updateAlphaWarningBanner');
    // Hoist into the sandbox global so the test can call it like a
    // regular function. The production declaration is inside an IIFE,
    // but the function body itself is independent of that closure.
    vm.runInContext(`${fnSrc}\nglobalThis.__update = _updateAlphaWarningBanner;`, sandbox);
    return { update: sandbox.__update, classes };
}

test('unhides the banner on an alpha version string', () => {
    const { update, classes } = setupSandbox();
    update('0.2.9-alpha.5');
    assert.equal(classes.has('hidden'), false, 'banner should be visible on alpha versions');
});

test('keeps the banner hidden on a stable version string', () => {
    const { update, classes } = setupSandbox();
    update('0.2.9');
    assert.equal(classes.has('hidden'), true, 'banner must stay hidden on stable versions');
});

test('alpha detection is case-insensitive', () => {
    const { update, classes } = setupSandbox();
    update('0.2.9-ALPHA.5');
    assert.equal(classes.has('hidden'), false, 'uppercase ALPHA should also trigger the banner');
});

test('does not confuse beta or rc with alpha', () => {
    for (const v of ['0.2.9-beta.1', '0.2.9-rc.2', '1.0.0']) {
        const { update, classes } = setupSandbox();
        update(v);
        assert.equal(
            classes.has('hidden'),
            true,
            `banner must remain hidden on non-alpha version '${v}'`,
        );
    }
});

test('handles non-string or missing version input gracefully', () => {
    for (const v of [null, undefined, 42, {}, '']) {
        const { update, classes } = setupSandbox();
        update(v);
        assert.equal(
            classes.has('hidden'),
            true,
            `banner must stay hidden when version is ${JSON.stringify(v)}`,
        );
    }
});

test('no-ops without throwing when the banner element is absent', () => {
    const { update } = setupSandbox({ bannerExists: false });
    assert.doesNotThrow(() => update('0.2.9-alpha.5'));
});
