/**
 * Theme selection store.
 *
 * Holds the user's theme choice (system / light / dark), resolves it to a
 * concrete light-or-dark theme, persists the choice to `localStorage`,
 * and writes the resolved theme to `data-theme` on the document root so
 * the token overrides in `tokens.css` take effect.
 *
 * First paint is handled by the inline guard in `app.html`, which sets
 * `data-theme` from the same `localStorage` key before the app boots;
 * this store mirrors that logic and takes over once mounted, including
 * reacting to OS theme changes while the choice is `system`.
 *
 * On top of the persisted choice the store carries an ephemeral
 * `override` channel: while non-null it short-circuits `resolved` so the
 * in-game light/dark toggle can flip the document theme without touching
 * the lobby-side preference. The override lives in memory only — leaving
 * the game shell (or any other consumer calling `clearOverride()`)
 * re-projects the persisted choice.
 */

/** A user's theme preference; `system` follows the OS setting. */
export type ThemeChoice = "system" | "light" | "dark";

/** A concrete theme actually applied to the document. */
export type ResolvedTheme = "light" | "dark";

/** `localStorage` key shared with the pre-paint guard in `app.html`. */
export const THEME_STORAGE_KEY = "galaxy-theme";

const SYSTEM_LIGHT_QUERY = "(prefers-color-scheme: light)";

function readStoredChoice(): ThemeChoice {
	if (typeof localStorage === "undefined") return "system";
	const value = localStorage.getItem(THEME_STORAGE_KEY);
	return value === "light" || value === "dark" || value === "system"
		? value
		: "system";
}

function systemTheme(): ResolvedTheme {
	if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
		return "dark";
	}
	return window.matchMedia(SYSTEM_LIGHT_QUERY).matches ? "light" : "dark";
}

/**
 * Reactive theme store. `choice` is the persisted preference and
 * `resolved` is the concrete theme after applying `system`.
 */
class ThemeStore {
	#choice = $state<ThemeChoice>(readStoredChoice());
	#system = $state<ResolvedTheme>(systemTheme());
	#override = $state<ResolvedTheme | null>(null);

	constructor() {
		if (
			typeof window !== "undefined" &&
			typeof window.matchMedia === "function"
		) {
			window
				.matchMedia(SYSTEM_LIGHT_QUERY)
				.addEventListener("change", (event: MediaQueryListEvent) => {
					this.#system = event.matches ? "light" : "dark";
					if (this.#choice === "system" && this.#override === null) {
						this.#apply();
					}
				});
		}
		this.#apply();
	}

	/** The persisted user preference. */
	get choice(): ThemeChoice {
		return this.#choice;
	}

	/**
	 * The current ephemeral override (set by the in-game toggle) or
	 * `null` when no override is active.
	 */
	get override(): ResolvedTheme | null {
		return this.#override;
	}

	/** The concrete theme currently applied to the document. */
	get resolved(): ResolvedTheme {
		if (this.#override !== null) return this.#override;
		return this.#choice === "system" ? this.#system : this.#choice;
	}

	/** Persist a new preference and apply it to the document. */
	setChoice(choice: ThemeChoice): void {
		this.#choice = choice;
		if (typeof localStorage !== "undefined") {
			localStorage.setItem(THEME_STORAGE_KEY, choice);
		}
		this.#apply();
	}

	/**
	 * Set the ephemeral override. The override is not persisted; it lives
	 * until `clearOverride()` (or another `setOverride` call) replaces it.
	 */
	setOverride(value: ResolvedTheme): void {
		this.#override = value;
		this.#apply();
	}

	/**
	 * Drop the ephemeral override so the document re-projects the
	 * persisted preference. Cheap to call on every game-shell unmount —
	 * a no-op when no override was set.
	 */
	clearOverride(): void {
		if (this.#override === null) return;
		this.#override = null;
		this.#apply();
	}

	#apply(): void {
		if (typeof document !== "undefined") {
			document.documentElement.dataset.theme = this.resolved;
		}
	}
}

/** The application-wide theme store singleton. */
export const theme = new ThemeStore();
