/**
 * Embedding Settings — Model Dropdown Preservation Tests
 *
 * Regression coverage for issue #3863 (PR #3940). The bug was that
 * `updateModelOptions()` cleared and repopulated the model dropdown without
 * preserving the user's selection, then dispatched a synthetic `change`
 * event which auto-saved whatever model landed at index 0. The reporter
 * triggered it via the (now-removed) "Save Default Settings" button; the
 * Ollama-URL-change path triggered the same reset.
 *
 * Also covers the follow-up behavior added in PR #3940 commit 40678b2:
 * clearing the text_separators textarea persists the default array so a
 * stale customization in the DB is replaced.
 *
 * The mock infrastructure lives in `helpers/embedding-settings-mocks.js`
 * so additional embedding-page specs can reuse it.
 *
 * Desktop-only: this is a form-state regression test, not a layout test.
 * Running it on every mobile project just consumes CI cycles.
 */

import { test, expect } from '@playwright/test';
const {
    BASE_MODELS_PAYLOAD,
    DEFAULT_TEXT_SEPARATORS,
    defaultSettings,
    mockEmbeddingApis,
} = require('./helpers/embedding-settings-mocks');

test.describe('Embedding settings — model dropdown preservation (#3863)', () => {
    test.skip(({ isMobile }) => isMobile, 'desktop-only form-state test');

    test('per-field auto-save fires on model change and persists across reload', async ({ page }) => {
        // This is a happy-path smoke test for the per-field auto-save
        // contract that replaced the deleted batch-save button. It does not
        // exercise the dropdown-rebuild path (see the Ollama-URL test for
        // that), but it pins the basic save-and-restore flow that the rest
        // of the spec relies on.
        const state = await mockEmbeddingApis(page, defaultSettings());

        await page.goto('/library/embedding-settings');
        // Wait until loadCurrentSettings has populated the dropdown with the
        // mocked initial value — guarantees attachAutoSaveListeners has run.
        await expect(page.locator('#embedding-model')).toHaveValue('all-MiniLM-L6-v2');

        // Pick the third (non-top) Transformers model.
        await page.locator('#embedding-model').selectOption('multi-qa-MiniLM-L6-cos-v1');

        // Auto-save fires on `change`; wait for the PUT to land.
        await expect.poll(() => state.modelSaves).toContain('multi-qa-MiniLM-L6-cos-v1');
        const finalSave = state.modelSaves[state.modelSaves.length - 1];
        expect(finalSave).toBe('multi-qa-MiniLM-L6-cos-v1');

        // Reload — the mock returns the now-saved model from /rag/settings.
        await page.reload();
        await expect(page.locator('#embedding-model')).toHaveValue('multi-qa-MiniLM-L6-cos-v1');
    });

    test('Ollama URL change does not reset the selected model (#3863 regression)', async ({ page }) => {
        // Start with Ollama already chosen and a non-top Ollama model picked.
        const state = await mockEmbeddingApis(
            page,
            defaultSettings({
                embedding_provider: 'ollama',
                embedding_model: 'mxbai-embed-large',
            }),
        );

        await page.goto('/library/embedding-settings');
        await expect(page.locator('#embedding-model')).toHaveValue('mxbai-embed-large');

        const savesBeforeUrlEdit = state.modelSaves.length;
        const fetchesBeforeUrlEdit = state.modelsFetches;

        // Edit the Ollama URL and tab away — this triggers
        // saveOllamaUrlAuto -> loadAvailableModels -> updateModelOptions,
        // which is the path that historically reset the dropdown.
        const urlField = page.locator('#ollama-url');
        await urlField.fill('http://localhost:11500');
        await urlField.press('Tab');

        // Wait for the URL PUT to land.
        await expect.poll(() => state.ollamaUrl).toBe('http://localhost:11500');
        // Then wait for the post-save loadAvailableModels() call to complete.
        // Once the GET /library/api/rag/models response is processed,
        // updateModelOptions() has run synchronously and any spurious save
        // that the bug would trigger has already fired. This is the
        // deterministic anchor that replaces a `waitForTimeout`.
        await expect
            .poll(() => state.modelsFetches, { timeout: 5000 })
            .toBeGreaterThan(fetchesBeforeUrlEdit);

        // The dropdown must still show the user's pick.
        await expect(page.locator('#embedding-model')).toHaveValue('mxbai-embed-large');
        // And no save with the index-0 model ('nomic-embed-text') should
        // have fired during the URL-change refresh.
        const newSaves = state.modelSaves.slice(savesBeforeUrlEdit);
        expect(newSaves).not.toContain('nomic-embed-text');
    });

    test('provider change preserves selection when the model exists in both lists', async ({ page }) => {
        // Add a model that exists for both providers so we can verify
        // updateModelOptions() restores the previous value when it's still
        // present in the rebuilt option list.
        const sharedPayload = JSON.parse(JSON.stringify(BASE_MODELS_PAYLOAD));
        sharedPayload.providers.ollama.push({
            value: 'all-MiniLM-L6-v2',
            label: 'all-MiniLM-L6-v2 (shared)',
            is_embedding: true,
        });

        const state = await mockEmbeddingApis(
            page,
            defaultSettings({
                embedding_provider: 'sentence_transformers',
                embedding_model: 'all-MiniLM-L6-v2',
            }),
        );
        // Override the rag/models response AFTER mockEmbeddingApis so this
        // last-registered route wins Playwright's match precedence.
        await page.route('**/library/api/rag/models', async (route) => {
            state.modelsFetches += 1;
            await route.fulfill({
                status: 200,
                contentType: 'application/json',
                body: JSON.stringify(sharedPayload),
            });
        });

        await page.goto('/library/embedding-settings');
        await expect(page.locator('#embedding-model')).toHaveValue('all-MiniLM-L6-v2');

        const fetchesBefore = state.modelsFetches;

        // Switch provider to ollama; updateModelOptions rebuilds the dropdown
        // and (with the fix) restores the shared model since it exists in
        // both lists.
        await page.locator('#embedding-provider').selectOption('ollama');
        await expect.poll(() => state.providerSaves).toContain('ollama');
        // Wait for the post-save loadAvailableModels() refresh to complete
        // before asserting the dropdown value.
        await expect
            .poll(() => state.modelsFetches, { timeout: 5000 })
            .toBeGreaterThan(fetchesBefore);
        await expect(page.locator('#embedding-model')).toHaveValue('all-MiniLM-L6-v2');
    });

    test('clearing the text_separators textarea saves the default array', async ({ page }) => {
        // Covers the follow-up fix in PR #3940 commit 40678b2: the auto-save
        // blur listener used to early-return on an empty textarea, leaving a
        // stale customization in the DB. Now an empty textarea is treated as
        // "reset to defaults" and persists the default array.
        const customSeparators = ['||'];
        const state = await mockEmbeddingApis(
            page,
            defaultSettings({ text_separators: customSeparators }),
        );

        await page.goto('/library/embedding-settings');
        const textarea = page.locator('#text-separators');
        // Loading the page populates the textarea with the saved custom value.
        await expect(textarea).toHaveValue(JSON.stringify(customSeparators));

        // Clear the textarea, then blur to fire the auto-save handler.
        await textarea.fill('');
        await textarea.blur();

        // The blur listener should save the default separators array.
        await expect
            .poll(() => state.textSeparatorsSaves, { timeout: 5000 })
            .toContainEqual(DEFAULT_TEXT_SEPARATORS);
        // And the persisted state mirrors the default, overwriting the
        // earlier custom value.
        expect(state.settings.text_separators).toEqual(DEFAULT_TEXT_SEPARATORS);
    });

    test('"Save Default Settings" button is gone — auto-save is the only path', async ({ page }) => {
        await mockEmbeddingApis(page, defaultSettings());
        await page.goto('/library/embedding-settings');
        await expect(page.locator('#embedding-model')).toBeVisible();

        // The button was the visible trigger of the bug; if it ever comes
        // back, this regression test forces a fresh look at #3863.
        await expect(page.getByRole('button', { name: /save default settings/i })).toHaveCount(0);
        await expect(page.locator('#rag-config-form')).toHaveCount(0);
    });
});
