// ==========================================
// Test Helper — Mock Browser Context
// ==========================================
// Loads the vanilla JS source files into a
// simulated browser environment so tests run
// against the ACTUAL frontend code.
//
// v0.37.10: Removed loadAppModules (api.js + app.js deleted).
// ==========================================

const fs = require('fs');
const path = require('path');
const vm = require('vm');

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

/**
 * Creates a minimal browser-like context with stubs for DOM, localStorage, etc.
 * Returns the context sandbox so tests can inspect globals like API, App, UI.
 */
function createBrowserContext(overrides = {}) {
    const storage = {};
    const elements = {};

    const sandbox = {
        // Window / globals
        window: {},
        document: {
            getElementById(id) {
                return elements[id] || null;
            },
            querySelector() { return null; },
            querySelectorAll() { return []; },
            addEventListener() {},
            documentElement: { style: { setProperty() {} } },
            createElement(tag) {
                return {
                    tagName: tag.toUpperCase(),
                    style: {}, classList: { add() {}, remove() {}, toggle() {}, contains() { return false; } },
                    setAttribute() {}, getAttribute() { return null; },
                    appendChild() {}, removeChild() {}, replaceChildren() {},
                    addEventListener() {},
                    innerHTML: '', textContent: '', value: '',
                    children: [], childNodes: [],
                    querySelector() { return null; },
                    querySelectorAll() { return []; },
                };
            },
        },
        localStorage: {
            _data: storage,
            getItem(k) { return storage[k] || null; },
            setItem(k, v) { storage[k] = String(v); },
            removeItem(k) { delete storage[k]; },
        },
        console,
        setTimeout: globalThis.setTimeout,
        clearTimeout: globalThis.clearTimeout,
        setInterval: globalThis.setInterval,
        clearInterval: globalThis.clearInterval,
        requestAnimationFrame(cb) { cb(); },
        fetch: async () => ({ ok: true, status: 200, json: async () => ({}) }),
        alert() {},
        confirm() { return true; },
        prompt() { return ''; },
        navigator: { userAgent: 'test', clipboard: { writeText() {} } },
        location: { href: 'http://localhost/dev/', pathname: '/dev/' },
        URL: globalThis.URL,
        Blob: globalThis.Blob,
        FileReader: class { readAsDataURL() {} readAsText() {} },
        WebSocket: class { addEventListener() {} send() {} close() {} },
        Image: class { set src(v) {} },
        MutationObserver: class { observe() {} disconnect() {} },
        IntersectionObserver: class { observe() {} disconnect() {} },
        // DOMPurify stub
        DOMPurify: { sanitize: (s) => s },
        // marked stub
        marked: { parse: (s) => s, setOptions() {} },
        // hljs stub
        hljs: { highlightElement() {}, getLanguage() { return null; } },

        __BASE__: '/dev',

        ...overrides,
    };

    // Self-references
    sandbox.window = sandbox;
    sandbox.globalThis = sandbox;
    sandbox.self = sandbox;

    // Register mock elements
    sandbox._setElement = (id, props = {}) => {
        const el = sandbox.document.createElement('div');
        Object.assign(el, props);
        el.id = id;
        elements[id] = el;
        return el;
    };

    return sandbox;
}

/**
 * Loads a source JS file into the sandbox context.
 * Returns the sandbox for inspection.
 */
function loadSource(sandbox, filename) {
    const filepath = path.join(SRC, filename);
    if (!fs.existsSync(filepath)) {
        throw new Error(`Source file not found: ${filename} (deleted in v0.37.10?)`);
    }
    const code = fs.readFileSync(filepath, 'utf-8');
    const ctx = vm.createContext(sandbox);
    vm.runInContext(code, ctx, { filename });
    return sandbox;
}

/**
 * Safely read a source file, returning empty string if deleted.
 * For tests that audit source code content.
 */
function readSourceSafe(filename) {
    const filepath = path.join(SRC, filename);
    if (!fs.existsSync(filepath)) return '';
    return fs.readFileSync(filepath, 'utf-8');
}

/**
 * Extract the model-processing transform from fetchModels (app-state.js).
 * This is the core mapping logic that converts API response → App.models.
 * v0.22.8: unified persona naming — no more preset aliases.
 */
function processModelsResponse(data, hiddenModels = new Set()) {
    return (data.data || data.models || []).map(m => {
        const isPersona = !!m.is_persona;
        const baseModelId = m.model_id || m.id;
        const cfgId = m.config_id || m.provider_config_id || '';
        const id = isPersona
            ? (m.persona_id || m.id)
            : (cfgId ? `${cfgId}:${baseModelId}` : baseModelId);
        return {
            id,
            baseModelId,
            name: m.display_name || baseModelId,
            provider: m.provider_name || m.provider || '',
            configId: cfgId || null,
            isPersona,
            personaId: m.persona_id || null,
            personaHandle: m.persona_handle || null,
            personaScope: m.persona_scope || (isPersona ? m.scope : null) || null,
            personaAvatar: m.persona_avatar || (isPersona ? m.avatar : null) || null,
            personaTeamName: m.persona_team_name || (isPersona ? m.team_name : null) || null,
            source: m.source || (isPersona ? 'persona' : 'global'),
            teamName: m.persona_team_name || null,
            hidden: !isPersona && hiddenModels.has((cfgId || '') + ':' + baseModelId),
        };
    });
}

module.exports = {
    createBrowserContext,
    loadSource,
    readSourceSafe,
    processModelsResponse,
    SRC,
};
