/**
 * Fase 9.1 — theme controller unit tests.
 *
 * `theme.ts` owns the source of truth for the user's UI theme. It exposes
 * a Svelte store + helpers that:
 *  - persist the chosen preference (`light` | `dark` | `system`) to
 *    localStorage so the anti-FOUC inline script in app.html reads it on
 *    next load;
 *  - reflect the *resolved* theme onto <html data-theme="..."> so Tailwind's
 *    `dark:` selectors and the :root[data-theme=...] CSS tokens both fire;
 *  - re-resolve when the user picks `system` and the OS preference flips.
 *
 * Test environment is jsdom (vitest config). We stub `matchMedia` per test.
 */
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { get } from 'svelte/store';
import {
	themePreference,
	resolvedTheme,
	setThemePreference,
	initTheme,
	type ThemePreference
} from './theme';

function mockMatchMedia(prefersDark: boolean) {
	const listeners = new Set<(e: MediaQueryListEvent) => void>();
	const mql = {
		matches: prefersDark,
		media: '(prefers-color-scheme: dark)',
		addEventListener: (_: string, fn: (e: MediaQueryListEvent) => void) => listeners.add(fn),
		removeEventListener: (_: string, fn: (e: MediaQueryListEvent) => void) => listeners.delete(fn),
		// Legacy APIs Svelte does not use but jsdom may probe
		addListener: (fn: (e: MediaQueryListEvent) => void) => listeners.add(fn),
		removeListener: (fn: (e: MediaQueryListEvent) => void) => listeners.delete(fn),
		dispatchEvent: (e: MediaQueryListEvent) => {
			listeners.forEach((l) => l(e));
			return true;
		},
		onchange: null
	};
	vi.stubGlobal('matchMedia', vi.fn().mockReturnValue(mql));
	(window as unknown as { matchMedia: typeof window.matchMedia }).matchMedia =
		globalThis.matchMedia;
	return mql;
}

beforeEach(() => {
	localStorage.clear();
	document.documentElement.removeAttribute('data-theme');
	// Default the matchMedia mock to "system prefers light" unless a test
	// overrides it. Without this, jsdom would throw on the first read.
	mockMatchMedia(false);
});

afterEach(() => {
	vi.unstubAllGlobals();
});

describe('themePreference store + setThemePreference', () => {
	it('TH-01: defaults to "system" when nothing is stored', () => {
		initTheme();
		expect(get(themePreference)).toBe('system');
	});

	it('TH-02: setting "light" writes localStorage and data-theme=light', () => {
		initTheme();
		setThemePreference('light');
		expect(localStorage.getItem('theme')).toBe('light');
		expect(document.documentElement.getAttribute('data-theme')).toBe('light');
		expect(get(themePreference)).toBe('light');
		expect(get(resolvedTheme)).toBe('light');
	});

	it('TH-03: setting "dark" writes localStorage and data-theme=dark', () => {
		initTheme();
		setThemePreference('dark');
		expect(localStorage.getItem('theme')).toBe('dark');
		expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
		expect(get(themePreference)).toBe('dark');
		expect(get(resolvedTheme)).toBe('dark');
	});

	it('TH-04: setting "system" resolves to dark when prefers-color-scheme: dark', () => {
		mockMatchMedia(true); // OS says dark
		initTheme();
		setThemePreference('system');
		expect(localStorage.getItem('theme')).toBe('system');
		expect(get(themePreference)).toBe('system');
		expect(get(resolvedTheme)).toBe('dark');
		expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
	});

	it('TH-05: setting "system" resolves to light when prefers-color-scheme: light', () => {
		mockMatchMedia(false); // OS says light
		initTheme();
		setThemePreference('system');
		expect(get(resolvedTheme)).toBe('light');
		expect(document.documentElement.getAttribute('data-theme')).toBe('light');
	});

	it('TH-06: initTheme picks up an existing localStorage value on mount', () => {
		localStorage.setItem('theme', 'dark');
		initTheme();
		expect(get(themePreference)).toBe('dark');
		expect(get(resolvedTheme)).toBe('dark');
		expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
	});

	it('TH-07: rejects unknown preference values (falls back to "system")', () => {
		initTheme();
		// @ts-expect-error testing the runtime guard
		setThemePreference('sepia' as ThemePreference);
		expect(get(themePreference)).toBe('system');
	});

	it('TH-08: "system" updates resolved theme when matchMedia fires "change"', () => {
		const mql = mockMatchMedia(false);
		initTheme();
		setThemePreference('system');
		expect(get(resolvedTheme)).toBe('light');

		// OS switches to dark.
		mql.matches = true;
		mql.dispatchEvent(new Event('change') as MediaQueryListEvent);
		expect(get(resolvedTheme)).toBe('dark');
		expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
	});
});
