// #region StoresModuleTest [C:3] [TYPE Module] [SEMANTICS store, atom, writable, fetch, test]
// @BRIEF Unit tests for main stores aggregator module — createWritableAtom, store atoms, fetch functions.
// @LAYER Tests
// @RELATION DEPENDS_ON -> [StoresModule]
// @RELATION DEPENDS_ON -> [ApiModule]
// @INVARIANT: Each test starts with fresh module state via vi.resetModules.

// #region stores_test:Function [TYPE Function]
// @SEMANTICS: test, stores, atom, writable, fetch, subscribe
// @PURPOSE: Validate store atom initial values, set/update/subscribe lifecycle, and fetch function success/error paths.
// @LAYER UI (Tests)
// @RELATION DEPENDS_ON -> [EXT:frontend:stores]
// @INVARIANT: Store atoms are reset between tests via dynamic import with resetModules.

import { describe, it, expect, vi, beforeEach } from 'vitest';

// Mock the api module to isolate fetch functions
const mockGetPlugins = vi.fn();
const mockGetTasks = vi.fn();

vi.mock('$lib/api.js', () => ({
	api: {
		getPlugins: mockGetPlugins,
		getTasks: mockGetTasks,
	},
}));

const mockLog = vi.fn();
vi.mock('$lib/cot-logger', () => ({ log: mockLog }));

describe('stores.svelte.ts — store atoms', () => {
	beforeEach(() => {
		vi.clearAllMocks();
	});

	it('should start with empty array for plugins', async () => {
		const { plugins } = await import('../stores.svelte.js');
		expect(plugins.current).toEqual([]);
	});

	it('should start with empty array for tasks', async () => {
		const { tasks } = await import('../stores.svelte.js');
		expect(tasks.current).toEqual([]);
	});

	it('should start with null for selectedPlugin', async () => {
		const { selectedPlugin } = await import('../stores.svelte.js');
		expect(selectedPlugin.current).toBeNull();
	});

	it('should start with null for selectedTask', async () => {
		const { selectedTask } = await import('../stores.svelte.js');
		expect(selectedTask.current).toBeNull();
	});

	it('should start with "dashboard" for currentPage', async () => {
		const { currentPage } = await import('../stores.svelte.js');
		expect(currentPage.current).toBe('dashboard');
	});

	it('should start with empty array for taskLogs', async () => {
		const { taskLogs } = await import('../stores.svelte.js');
		expect(taskLogs.current).toEqual([]);
	});

	it('should support set() and .current on plugins atom', async () => {
		const { plugins } = await import('../stores.svelte.js');
		const fixture = [{ id: 'p1', name: 'Alpha Plugin' }];
		plugins.set(fixture);
		expect(plugins.current).toEqual(fixture);
	});

	it('should support update() on plugins atom', async () => {
		const { plugins } = await import('../stores.svelte.js');
		const fixture = [{ id: 'p1', name: 'Alpha' }];
		plugins.set(fixture);
		plugins.update((list) => [...list, { id: 'p2', name: 'Beta' }]);
		expect(plugins.current).toHaveLength(2);
		expect(plugins.current[1].id).toBe('p2');
	});

	it('should support subscribe() and return unsubscribe', async () => {
		const { plugins } = await import('../stores.svelte.js');
		const received: unknown[][] = [];
		const unsub = plugins.subscribe((v) => received.push(v));
		plugins.set([{ id: 'p1' }]);
		expect(received).toHaveLength(2);
		expect(received[1]).toEqual([{ id: 'p1' }]);
		unsub();
		// Unsubscribed subscribers should not be called
		plugins.set([{ id: 'p2' }]);
		expect(received).toHaveLength(2);
	});

	it('should support subscribe via get() from svelte/store', async () => {
		const { get } = await import('svelte/store');
		const { plugins, selectedPlugin, currentPage } = await import('../stores.svelte.js');
		plugins.set([{ id: 'p1' }]);
		selectedPlugin.set({ id: 'p1' });
		currentPage.set('settings');
		expect(get(plugins)).toEqual([{ id: 'p1' }]);
		expect(get(selectedPlugin)).toEqual({ id: 'p1' });
		expect(get(currentPage)).toBe('settings');
	});

	it('should support set via assignment to .current', async () => {
		const { selectedTask, taskLogs } = await import('../stores.svelte.js');
		const taskFixture = { id: 't1', name: 'Migration' };
		selectedTask.current = taskFixture;
		expect(selectedTask.current).toEqual(taskFixture);
		taskLogs.current = [{ level: 'info', message: 'started' }];
		expect(taskLogs.current).toHaveLength(1);
	});
});

describe('stores.svelte.ts — fetch functions', () => {
	beforeEach(() => {
		vi.resetModules();
		vi.clearAllMocks();
	});

	it('fetchPlugins calls api.getPlugins and updates plugins store', async () => {
		const fixture = [{ id: 'p1', name: 'Fetched Plugin' }];
		mockGetPlugins.mockResolvedValue(fixture);
		const { fetchPlugins, plugins } = await import('../stores.svelte.js');
		await fetchPlugins();
		expect(mockGetPlugins).toHaveBeenCalledTimes(1);
		expect(plugins.current).toEqual(fixture);
	});

	it('fetchPlugins logs error and does not throw on API failure', async () => {
		mockGetPlugins.mockRejectedValue(new Error('Network error'));
		const { fetchPlugins } = await import('../stores.svelte.js');
		await expect(fetchPlugins()).resolves.toBeUndefined();
		expect(mockLog).toHaveBeenCalledWith(expect.any(String), 'EXPLORE', expect.any(String), undefined, expect.any(String));
	});

	it('fetchTasks calls api.getTasks and updates tasks store', async () => {
		const fixture = [{ id: 't1', type: 'migration', status: 'running' }];
		mockGetTasks.mockResolvedValue(fixture);
		const { fetchTasks, tasks } = await import('../stores.svelte.js');
		await fetchTasks();
		expect(mockGetTasks).toHaveBeenCalledTimes(1);
		expect(tasks.current).toEqual(fixture);
	});

	it('fetchTasks logs error and does not throw on API failure', async () => {
		mockGetTasks.mockRejectedValue(new Error('Server error'));
		const { fetchTasks } = await import('../stores.svelte.js');
		await expect(fetchTasks()).resolves.toBeUndefined();
		expect(mockLog).toHaveBeenCalledWith(expect.any(String), 'EXPLORE', expect.any(String), undefined, expect.any(String));
	});

	it('fetchPlugins preserves plugins store when API returns empty array', async () => {
		mockGetPlugins.mockResolvedValue([]);
		const { fetchPlugins, plugins } = await import('../stores.svelte.js');
		await fetchPlugins();
		expect(plugins.current).toEqual([]);
	});

	it('fetchTasks preserves tasks store when API returns empty array', async () => {
		mockGetTasks.mockResolvedValue([]);
		const { fetchTasks, tasks } = await import('../stores.svelte.js');
		await fetchTasks();
		expect(tasks.current).toEqual([]);
	});

	it('currentPage supports set() and subscribe()', async () => {
		const { currentPage } = await import('../stores.svelte.js');
		const received: string[] = [];
		const unsub = currentPage.subscribe((v) => received.push(v));
		expect(received[0]).toBe('dashboard');
		currentPage.set('settings');
		expect(received[1]).toBe('settings');
		unsub();
	});

	it('selectedPlugin.subscribe should unsubscribe correctly', async () => {
		const { selectedPlugin } = await import('../stores.svelte.js');
		const received: unknown[] = [];
		const unsub = selectedPlugin.subscribe((v) => received.push(v));
		selectedPlugin.set({ id: 'p1' });
		expect(received.length).toBe(2);
		unsub();
		selectedPlugin.set({ id: 'p2' });
		expect(received.length).toBe(2);
	});

	it('selectedTask.subscribe should unsubscribe correctly', async () => {
		const { selectedTask } = await import('../stores.svelte.js');
		const received: unknown[] = [];
		const unsub = selectedTask.subscribe((v) => received.push(v));
		selectedTask.set({ id: 't1' });
		expect(received.length).toBe(2);
		unsub();
		selectedTask.set({ id: 't2' });
		expect(received.length).toBe(2);
	});

	it('taskLogs supports update() to modify array', async () => {
		const { taskLogs } = await import('../stores.svelte.js');
		taskLogs.update((logs) => [...logs, { level: 'info', msg: 'test' }]);
		expect(taskLogs.current).toHaveLength(1);
		expect(taskLogs.current[0]).toEqual({ level: 'info', msg: 'test' });
	});
});

// #endregion stores_test:Function
// #endregion StoresModuleTest
