/**
 * Regression tests for processModelData in components/settings.js
 *
 * Issue #3800: LM Studio model dropdown was empty because the frontend's
 * processModelData had hardcoded if-blocks for only ollama_models,
 * openai_models, anthropic_models, and openai_endpoint_models. The fix
 * (PR #3942) replaces the four hardcoded blocks with a generic loop
 * that lifts every <provider>_models array.
 *
 * Strategy
 * --------
 * processModelData is defined inside an IIFE in settings.js and never
 * exposed on window, so we cannot import settings.js and call it. Instead
 * we read the file as text, locate the loop block (the chunk between
 * `if (data.providers) {` and its matching `}`), and execute that exact
 * source against a synthetic `data` object inside a new Function. This
 * tests the literal shipped code, not a re-implementation.
 *
 * If the loop block can't be located (e.g. someone refactors the function
 * shape later), the locator throws and every test fails loudly — that is
 * the intended failure mode, not a fallback.
 */
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';

const SETTINGS_PATH = resolve(
    __dirname,
    '../../../src/local_deep_research/web/static/js/components/settings.js',
);

/**
 * Extract the full `if (data.providers) { ... }` statement (guard
 * included) from settings.js by brace-matching. We need the guard so
 * that the missing-providers test exercises the same null-safety as
 * production.
 */
function extractProvidersLoop(source) {
    const marker = 'if (data.providers) {';
    const start = source.indexOf(marker);
    if (start < 0) {
        throw new Error(
            'Could not find `if (data.providers) {` in settings.js — ' +
            'the fix may have been refactored. Update this test.',
        );
    }
    const openBrace = start + marker.length - 1; // index of `{`
    let depth = 0;
    let i = openBrace;
    for (; i < source.length; i++) {
        const ch = source[i];
        if (ch === '{') depth++;
        else if (ch === '}') {
            depth--;
            if (depth === 0) break;
        }
    }
    if (depth !== 0) {
        throw new Error('Brace mismatch when extracting providers loop.');
    }
    // Return the whole statement including the `if (...)` guard.
    return source.slice(start, i + 1);
}

const SOURCE = readFileSync(SETTINGS_PATH, 'utf8');
const LOOP_BODY = extractProvidersLoop(SOURCE);

/**
 * Run the extracted loop body against `data`, returning the populated
 * formattedModels array. Mirrors the surrounding processModelData
 * scaffolding (formattedModels declaration, SafeLogger stub).
 */
function runLoop(data) {
    const formattedModels = [];
    const SafeLogger = { log: () => {}, warn: () => {}, error: () => {} };
    // The loop body references `data`, `formattedModels`, and `SafeLogger`.
    // eslint-disable-next-line no-new-func
    const fn = new Function(
        'data', 'formattedModels', 'SafeLogger',
        LOOP_BODY,
    );
    fn(data, formattedModels, SafeLogger);
    return formattedModels;
}

describe('processModelData providers loop (issue #3800)', () => {
    it('lifts lmstudio_models tagged as LMSTUDIO (the bug)', () => {
        const data = {
            providers: {
                lmstudio_models: [
                    { value: 'qwen-7b', label: 'qwen-7b' },
                    { value: 'phi-3', label: 'Phi 3' },
                ],
            },
        };
        const out = runLoop(data);
        expect(out).toHaveLength(2);
        expect(out[0]).toEqual({
            value: 'qwen-7b',
            label: 'qwen-7b',
            provider: 'LMSTUDIO',
        });
        expect(out[1].provider).toBe('LMSTUDIO');
    });

    it('lifts llamacpp_models tagged as LLAMACPP', () => {
        const data = {
            providers: {
                llamacpp_models: [
                    { value: 'llama-3-8b.gguf', label: 'Llama 3 8B' },
                ],
            },
        };
        const out = runLoop(data);
        expect(out).toEqual([
            {
                value: 'llama-3-8b.gguf',
                label: 'Llama 3 8B',
                provider: 'LLAMACPP',
            },
        ]);
    });

    it('still handles ollama_models (backwards compat)', () => {
        const data = {
            providers: {
                ollama_models: [{ value: 'llama3', label: 'Llama 3' }],
            },
        };
        const out = runLoop(data);
        expect(out).toEqual([
            { value: 'llama3', label: 'Llama 3', provider: 'OLLAMA' },
        ]);
    });

    it('preserves OPENAI_ENDPOINT casing (underscore in tag)', () => {
        const data = {
            providers: {
                openai_endpoint_models: [
                    { value: 'gpt-x', label: 'gpt-x' },
                ],
            },
        };
        const out = runLoop(data);
        expect(out[0].provider).toBe('OPENAI_ENDPOINT');
    });

    it('lifts multiple providers in one response', () => {
        const data = {
            providers: {
                ollama_models: [{ value: 'llama3', label: 'Llama 3' }],
                lmstudio_models: [{ value: 'qwen-7b', label: 'qwen-7b' }],
                openai_models: [{ value: 'gpt-4', label: 'GPT-4' }],
            },
        };
        const out = runLoop(data);
        const providers = out.map(m => m.provider).sort();
        expect(providers).toEqual(['LMSTUDIO', 'OLLAMA', 'OPENAI']);
        expect(out).toHaveLength(3);
    });

    it('handles empty providers dict without errors', () => {
        const out = runLoop({ providers: {} });
        expect(out).toEqual([]);
    });

    it('handles missing providers (undefined) without errors', () => {
        const out = runLoop({});
        expect(out).toEqual([]);
    });

    it('skips empty arrays', () => {
        const out = runLoop({ providers: { lmstudio_models: [] } });
        expect(out).toEqual([]);
    });

    it('skips non-array values (defensive)', () => {
        const out = runLoop({
            providers: {
                lmstudio_models: 'not an array',
                ollama_models: [{ value: 'a', label: 'A' }],
            },
        });
        expect(out).toHaveLength(1);
        expect(out[0].provider).toBe('OLLAMA');
    });

    it('ignores keys without _models suffix', () => {
        const out = runLoop({
            providers: {
                some_other_key: [{ value: 'x', label: 'x' }],
                lmstudio_models: [{ value: 'qwen', label: 'qwen' }],
            },
        });
        expect(out).toHaveLength(1);
        expect(out[0].provider).toBe('LMSTUDIO');
    });
});
