/**
 * Fase 9.1 — theme controller.
 *
 * Source of truth for the user's UI theme. Three preference values:
 *   - 'light'  → force light mode
 *   - 'dark'   → force dark mode
 *   - 'system' → follow OS prefers-color-scheme (default for new users)
 *
 * Two stores:
 *   - `themePreference` — the raw user choice (one of the three above).
 *   - `resolvedTheme`   — what's actually painted: 'light' | 'dark'.
 *
 * Side effects (all browser-side, guarded for SSR):
 *   - writes `localStorage.theme` so app.html's inline anti-FOUC script
 *     can read it synchronously on the next load;
 *   - mirrors `resolvedTheme` onto <html data-theme="..."> so Tailwind's
 *     `dark:` utilities + the :root[data-theme=...] CSS tokens resolve.
 *
 * Persistence to public.users.theme is the caller's responsibility — see
 * `ThemeToggle.svelte` which fires the UPDATE in the background. We keep
 * the database write out of this module so unit tests don't need a
 * Supabase client.
 */
import { writable, derived, get } from 'svelte/store';

export type ThemePreference = 'light' | 'dark' | 'system';
export type ResolvedTheme = 'light' | 'dark';

const STORAGE_KEY = 'theme';
const VALID: readonly ThemePreference[] = ['light', 'dark', 'system'] as const;

function isValid(value: string | null | undefined): value is ThemePreference {
	return value === 'light' || value === 'dark' || value === 'system';
}

export const themePreference = writable<ThemePreference>('system');
export const resolvedTheme = writable<ResolvedTheme>('light');

let mql: MediaQueryList | null = null;
let mqlListener: ((e: MediaQueryListEvent) => void) | null = null;
let initialized = false;

function detachMqlListener() {
	if (mql && mqlListener) {
		mql.removeEventListener('change', mqlListener);
	}
	mql = null;
	mqlListener = null;
}

function attachMqlListener() {
	if (typeof window === 'undefined' || !window.matchMedia) return;
	detachMqlListener();
	mql = window.matchMedia('(prefers-color-scheme: dark)');
	mqlListener = () => {
		// Only react if the user is currently on "system".
		if (get(themePreference) === 'system') {
			applyResolved();
		}
	};
	mql.addEventListener('change', mqlListener);
}

function osPrefersDark(): boolean {
	if (typeof window === 'undefined' || !window.matchMedia) return false;
	try {
		return window.matchMedia('(prefers-color-scheme: dark)').matches;
	} catch {
		return false;
	}
}

function resolve(pref: ThemePreference): ResolvedTheme {
	if (pref === 'system') return osPrefersDark() ? 'dark' : 'light';
	return pref;
}

function applyResolved() {
	const r = resolve(get(themePreference));
	resolvedTheme.set(r);
	if (typeof document !== 'undefined') {
		document.documentElement.setAttribute('data-theme', r);
	}
}

/**
 * Initialise the stores from localStorage and start listening for OS
 * preference changes. Safe to call more than once — re-runs only refresh
 * the listener subscription, never duplicate it.
 */
export function initTheme(): void {
	const stored = typeof localStorage !== 'undefined' ? localStorage.getItem(STORAGE_KEY) : null;
	const pref: ThemePreference = isValid(stored) ? stored : 'system';
	themePreference.set(pref);
	applyResolved();
	attachMqlListener();
	initialized = true;
}

/**
 * Update the user's preference. Persists to localStorage, refreshes the
 * resolved theme, and updates <html data-theme>. Invalid input falls back
 * to 'system' so we never store junk.
 */
export function setThemePreference(value: ThemePreference): void {
	if (!initialized) initTheme();
	const next: ThemePreference = VALID.includes(value) ? value : 'system';
	themePreference.set(next);
	if (typeof localStorage !== 'undefined') {
		localStorage.setItem(STORAGE_KEY, next);
	}
	applyResolved();
}

/**
 * Derived store useful for components that only care about the live
 * resolved value — re-exported so callers can `$isDark`-style.
 */
export const isDark = derived(resolvedTheme, (r) => r === 'dark');
