import { jsx as _jsx } from "react/jsx-runtime";
// @vitest-environment happy-dom
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
/**
 * Settings > AI reads three backends: /ai/settings (defaults + masked
 * providers), /ai/models (catalog), /ai/usage/summary. Provider connection
 * actions hit /ai/providers/*. Keys are never returned — only a masked last4.
 */
const SETTINGS_RESPONSE = {
    settings: {
        defaultProviderId: 'p1',
        defaultModelId: 'anthropic:claude-sonnet-4-latest',
        features: [
            {
                key: 'ai.features.contentWriting',
                label: 'Content writing suggestions',
                description: 'Draft, rewrite, summarize content.',
                enabled: true,
                category: 'content',
                requiredCapabilities: ['text'],
            },
            {
                key: 'ai.features.redirectChainFlattening',
                label: 'Redirect chain flattening',
                description: 'Suggest single-hop redirects.',
                enabled: false,
                category: 'seo',
                requiredCapabilities: ['text'],
            },
        ],
        budget: { monthlyTokenLimit: 1000000 },
    },
    providers: [
        {
            id: 'p1',
            provider: 'anthropic',
            displayName: 'Anthropic (Claude)',
            enabled: true,
            status: 'connected',
            apiKeyLast4: 'f12a',
            apiKeySource: 'database',
            keyManagedExternally: false,
            hasKey: true,
            createdAt: '2026-01-01T00:00:00.000Z',
            updatedAt: '2026-01-01T00:00:00.000Z',
            updatedBy: 'u1',
        },
    ],
    defaultModel: null,
};
const MODELS = [
    {
        id: 'anthropic:claude-sonnet-4-latest',
        provider: 'anthropic',
        providerModelId: 'claude-sonnet-4-latest',
        displayName: 'Claude Sonnet (balanced)',
        status: 'available',
        capabilities: { text: true, vision: true, toolCalling: true },
        contextWindow: 200000,
        inputModalities: ['text', 'image'],
        outputModalities: ['text'],
        pricing: { inputPerMillion: 3, outputPerMillion: 15 },
        lastSyncedAt: '2026-01-01T00:00:00.000Z',
        source: 'config',
    },
    {
        id: 'anthropic:claude-haiku',
        provider: 'anthropic',
        providerModelId: 'claude-haiku',
        displayName: 'Claude Haiku (fast)',
        status: 'available',
        capabilities: { text: true },
        contextWindow: 200000,
        inputModalities: ['text'],
        outputModalities: ['text'],
        pricing: { inputPerMillion: 0.8, outputPerMillion: 4 },
        lastSyncedAt: '2026-01-01T00:00:00.000Z',
        source: 'config',
    },
];
const USAGE = {
    totalTokens: 412000,
    promptTokens: 0,
    completionTokens: 0,
    monthlyTokenLimit: 1000000,
    periodStart: '2026-06-01T00:00:00.000Z',
    periodEnd: '2026-07-01T00:00:00.000Z',
    runCount: 5,
};
// Mutable overrides so individual tests can tweak the loaded shape.
let settingsResponse = SETTINGS_RESPONSE;
const cmsApi = vi.fn(async (endpoint, options) => {
    const method = options?.method ?? 'GET';
    if (endpoint === '/ai/settings' && method === 'GET')
        return { data: settingsResponse, status: 200 };
    if (endpoint === '/ai/settings' && method === 'PUT')
        return { data: settingsResponse, status: 200 };
    if (endpoint === '/ai/models' && method === 'GET')
        return { data: { models: MODELS }, status: 200 };
    if (endpoint === '/ai/usage/summary')
        return { data: USAGE, status: 200 };
    if (endpoint === '/ai/providers/p1/test' && method === 'POST') {
        return {
            data: {
                provider: settingsResponse.providers[0],
                result: { ok: true, status: 'connected', message: 'Connection successful.' },
            },
            status: 200,
        };
    }
    if (endpoint === '/ai/providers/p1/sync-models' && method === 'POST') {
        return { data: { models: MODELS, fromFallback: false, warning: null }, status: 200 };
    }
    return { data: {}, status: 200 };
});
vi.mock('../../lib/api.js', () => ({
    cmsApi: (e, o) => cmsApi(e, o),
    ensureCsrfToken: vi.fn(async () => { }),
}));
vi.mock('sonner', () => ({
    toast: { success: vi.fn(), error: vi.fn(), warning: vi.fn(), loading: vi.fn() },
}));
const { AISettingsTab } = await import('../../views/settings/AISettingsTab.js');
function deepClone(v) {
    return JSON.parse(JSON.stringify(v));
}
beforeEach(() => {
    cmsApi.mockClear();
    settingsResponse = deepClone(SETTINGS_RESPONSE);
});
describe('AISettingsTab', () => {
    it('loads the default provider, model, and capability badges', async () => {
        render(_jsx(AISettingsTab, { role: "ADMIN" }));
        expect(await screen.findByText('AI Assistant')).toBeTruthy();
        const assistant = screen.getByRole('region', { name: 'AI Assistant' });
        expect(within(assistant).getByLabelText('Default model').value).toBe('anthropic:claude-sonnet-4-latest');
        // Capability badges render as text (not color-only).
        expect(within(assistant).getByText('Vision')).toBeTruthy();
        expect(within(assistant).getByText('200,000 token context')).toBeTruthy();
    });
    it('shows real monthly usage (never a fabricated number)', async () => {
        render(_jsx(AISettingsTab, { role: "ADMIN" }));
        expect(await screen.findByText('412K tokens used')).toBeTruthy();
        expect(screen.getByRole('progressbar').getAttribute('aria-valuenow')).toBe('41');
    });
    it('refreshes the model catalog via the provider sync endpoint', async () => {
        render(_jsx(AISettingsTab, { role: "ADMIN" }));
        const assistant = await screen.findByRole('region', { name: 'AI Assistant' });
        fireEvent.click(within(assistant).getByRole('button', { name: 'Refresh models' }));
        await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/ai/providers/p1/sync-models', expect.objectContaining({ method: 'POST' })));
    });
    it('tests a provider connection', async () => {
        render(_jsx(AISettingsTab, { role: "ADMIN" }));
        await screen.findByText('AI Assistant');
        fireEvent.click(screen.getByRole('button', { name: 'Test connection' }));
        await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/ai/providers/p1/test', expect.objectContaining({ method: 'POST' })));
    });
    it('toggles a feature and saves it to /ai/settings', async () => {
        render(_jsx(AISettingsTab, { role: "ADMIN" }));
        await screen.findByText('AI Features');
        fireEvent.click(screen.getByRole('switch', { name: 'Content writing suggestions' }));
        const save = screen.getByRole('button', { name: 'Save Changes' });
        expect(save.disabled).toBe(false);
        fireEvent.click(save);
        await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/ai/settings', expect.objectContaining({ method: 'PUT' })));
        const putCall = cmsApi.mock.calls.find(([e, o]) => e === '/ai/settings' && o?.method === 'PUT');
        const body = JSON.parse(putCall[1].body);
        const cw = body.features.find((f) => f.key === 'ai.features.contentWriting');
        expect(cw.enabled).toBe(false);
    });
    it('routes a feature to a specific model and persists the override', async () => {
        render(_jsx(AISettingsTab, { role: "ADMIN" }));
        await screen.findByText('AI Features');
        // Only the enabled content-writing feature exposes a per-feature Model select.
        const featuresRegion = screen.getByRole('region', { name: 'AI Features' });
        const select = within(featuresRegion).getByLabelText('Model');
        expect(select.value).toBe(''); // inherits the default
        fireEvent.change(select, { target: { value: 'anthropic:claude-haiku' } });
        const save = screen.getByRole('button', { name: 'Save Changes' });
        expect(save.disabled).toBe(false);
        fireEvent.click(save);
        await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/ai/settings', expect.objectContaining({ method: 'PUT' })));
        const putCall = cmsApi.mock.calls.find(([e, o]) => e === '/ai/settings' && o?.method === 'PUT');
        const body = JSON.parse(putCall[1].body);
        const cw = body.features.find((f) => f.key === 'ai.features.contentWriting');
        expect(cw.modelId).toBe('anthropic:claude-haiku');
    });
    it('warns when a feature would run on a model missing a required capability', async () => {
        settingsResponse = deepClone(SETTINGS_RESPONSE);
        // Alt-text needs vision; route it through the text-only Haiku model.
        settingsResponse.settings.features.push({
            key: 'ai.features.mediaAltText',
            label: 'Auto alt-text for media',
            description: 'Generate alt text for images.',
            enabled: true,
            category: 'media',
            requiredCapabilities: ['vision'],
            modelId: 'anthropic:claude-haiku',
        });
        render(_jsx(AISettingsTab, { role: "ADMIN" }));
        await screen.findByText('AI Features');
        expect(await screen.findByText(/can't do vision/i)).toBeTruthy();
    });
    it('edits the brand voice and persists it to /ai/settings', async () => {
        render(_jsx(AISettingsTab, { role: "ADMIN" }));
        const region = await screen.findByRole('region', { name: 'Brand voice' });
        fireEvent.click(within(region).getByRole('switch', { name: 'Apply brand voice' }));
        fireEvent.change(within(region).getByLabelText('Tone'), {
            target: { value: 'confident, warm' },
        });
        const save = screen.getByRole('button', { name: 'Save Changes' });
        expect(save.disabled).toBe(false);
        fireEvent.click(save);
        await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/ai/settings', expect.objectContaining({ method: 'PUT' })));
        const putCall = cmsApi.mock.calls.find(([e, o]) => e === '/ai/settings' && o?.method === 'PUT');
        const body = JSON.parse(putCall[1].body);
        expect(body.brandVoice).toMatchObject({ enabled: true, tone: 'confident, warm' });
    });
    it('disables brand voice fields for non-admins', async () => {
        render(_jsx(AISettingsTab, { role: "EDITOR" }));
        const region = await screen.findByRole('region', { name: 'Brand voice' });
        expect(within(region).getByRole('switch', { name: 'Apply brand voice' })
            .disabled).toBe(true);
        expect(within(region).getByLabelText('Tone').disabled).toBe(true);
    });
    it('is read-only for non-admins', async () => {
        render(_jsx(AISettingsTab, { role: "EDITOR" }));
        expect(await screen.findByText(/read-only access to AI settings/i)).toBeTruthy();
        const assistant = screen.getByRole('region', { name: 'AI Assistant' });
        expect(within(assistant).getByLabelText('Default model').disabled).toBe(true);
        expect(screen.getByRole('switch', { name: 'Content writing suggestions' })
            .disabled).toBe(true);
    });
    it('renders an env-managed key as read-only', async () => {
        settingsResponse = deepClone(SETTINGS_RESPONSE);
        settingsResponse.providers[0].keyManagedExternally = true;
        settingsResponse.providers[0].apiKeySource = 'environment';
        render(_jsx(AISettingsTab, { role: "ADMIN" }));
        expect(await screen.findByText('Managed by environment')).toBeTruthy();
        expect(screen.getByText(/set via an environment variable/i)).toBeTruthy();
    });
    it('warns when the configured default model is no longer available', async () => {
        settingsResponse = deepClone(SETTINGS_RESPONSE);
        settingsResponse.settings.defaultModelId = 'anthropic:ghost-model';
        render(_jsx(AISettingsTab, { role: "ADMIN" }));
        await screen.findByText('AI Assistant');
        expect(await screen.findByText(/selected model is no longer offered by the provider/i)).toBeTruthy();
    });
});
//# sourceMappingURL=ai-settings.render.test.js.map