import { browser } from "$app/environment";
import { evaluateGuess, generateUUID, type Guess } from "$lib/utils/game";

// Returns a stable anonymous ID for this browser, creating one if it doesn't exist yet.
// Used to attribute stats to a player who hasn't signed in.
function getOrCreateAnonymousId(): string {
	if (!browser) return "";
	const key = "bibdle-anonymous-id";
	let id = localStorage.getItem(key);
	if (!id) {
		id = generateUUID();
		localStorage.setItem(key, id);
	}
	return id;
}

// Reactive store that keeps in-memory game state in sync with localStorage.
// Accepts getter functions (rather than plain values) so Svelte's reactivity
// system can track dependencies and re-run effects when they change.
type AuthUser = {
	id: string;
	firstName?: string | null;
	lastName?: string | null;
	email?: string | null;
};

export function createGamePersistence(
	getDate: () => string,
	getReference: () => string,
	getCorrectBookId: () => string,
	getUser: () => AuthUser | null | undefined,
) {
	let guesses = $state<Guess[]>([]);
	let anonymousId = $state("");
	let statsSubmitted = $state(false);
	let chapterGuessCompleted = $state(false);
	let chapterCorrect = $state(false);

	// On mount (and if the user logs in/out), resolve the player's identity and
	// restore per-day flags from localStorage.
	$effect(() => {
		if (!browser) return;

		const user = getUser();
		// CRITICAL: If user is logged in, ALWAYS use their user ID
		if (user) {
			anonymousId = user.id;
		} else {
			anonymousId = getOrCreateAnonymousId();
		}

		// Tell analytics which player this is so events are grouped correctly.
		if ((window as any).umami) {
			(window as any).umami.identify(anonymousId);
		}


		const date = getDate();
		const reference = getReference();

		// Restore whether today's completion was already submitted to the server.
		statsSubmitted = localStorage.getItem(`bibdle-stats-submitted-${date}`) === "true";

		// Restore the chapter bonus guess result. The stored value includes the
		// chapter the player selected, so we can re-derive whether it was correct.
		const chapterGuessKey = `bibdle-chapter-guess-${reference}`;
		chapterGuessCompleted = localStorage.getItem(chapterGuessKey) !== null;
		if (chapterGuessCompleted) {
			const saved = localStorage.getItem(chapterGuessKey);
			if (saved) {
				const data = JSON.parse(saved);
				const match = reference.match(/\s(\d+):/);
				const correctChapter = match ? parseInt(match[1], 10) : 1;
				chapterCorrect = data.selectedChapter === correctChapter;
			}
		}
	});

	// On mount (and if the date or correct answer changes), load today's guesses
	// from localStorage and reconstruct them as typed Guess objects by re-evaluating
	// each stored book ID against the correct answer.
	$effect(() => {
		if (!browser) return;

		const date = getDate();
		const correctBookId = getCorrectBookId();
		const key = `bibdle-guesses-${date}`;
		const saved = localStorage.getItem(key);
		if (!saved) {
			guesses = [];
			return;
		}

		let savedIds: string[] = JSON.parse(saved);
		savedIds = Array.from(new Set(savedIds)); // deduplicate, just in case
		guesses = savedIds
			.map((bookId) => evaluateGuess(bookId, correctBookId))
			.filter((g): g is Guess => g !== null);
	});

	// Persist guesses to localStorage whenever they change. Only the book IDs are
	// stored — the full Guess shape is re-derived on load (see effect above).
	$effect(() => {
		if (!browser) return;
		const date = getDate();
		localStorage.setItem(
			`bibdle-guesses-${date}`,
			JSON.stringify(guesses.map((g) => g.book.id)),
		);
	});

	// Called after stats are successfully submitted to the server so that
	// returning to the page doesn't trigger a duplicate submission.
	function markStatsSubmitted() {
		if (!browser) return;
		statsSubmitted = true;
		localStorage.setItem(`bibdle-stats-submitted-${getDate()}`, "true");
	}

	// Marks the win as tracked for analytics. Returns true the first time (new
	// win), false on subsequent calls so the analytics event fires exactly once.
	function markWinTracked() {
		if (!browser) return;
		const key = `bibdle-win-tracked-${getDate()}`;
		if (localStorage.getItem(key) === "true") return false;
		localStorage.setItem(key, "true");
		return true;
	}

	// Returns true if the win has already been tracked in a previous render/session.
	// Used to skip the animation delay when returning to an already-won game.
	function isWinAlreadyTracked(): boolean {
		if (!browser) return false;
		return localStorage.getItem(`bibdle-win-tracked-${getDate()}`) === "true";
	}

	// Overwrites local state with the server's authoritative guess record.
	// Called when a logged-in user opens the game on a new device so their
	// progress from another device is restored.
	function hydrateFromServer(guessIds: string[]) {
		if (!browser) return;
		const correctBookId = getCorrectBookId();
		const date = getDate();
		guesses = guessIds
			.map((bookId) => evaluateGuess(bookId, correctBookId))
			.filter((g): g is Guess => g !== null);
	}

	// Called by the WinScreen after the player submits their chapter bonus guess.
	// Reads the result written to localStorage by WinScreen and updates reactive state.
	function onChapterGuessCompleted() {
		if (!browser) return;
		chapterGuessCompleted = true;
		const reference = getReference();
		const chapterGuessKey = `bibdle-chapter-guess-${reference}`;
		const saved = localStorage.getItem(chapterGuessKey);
		if (saved) {
			const data = JSON.parse(saved);
			const match = reference.match(/\s(\d+):/);
			const correctChapter = match ? parseInt(match[1], 10) : 1;
			chapterCorrect = data.selectedChapter === correctChapter;
		}
	}

	return {
		get guesses() { return guesses; },
		set guesses(v: Guess[]) { guesses = v; },
		get anonymousId() { return anonymousId; },
		get statsSubmitted() { return statsSubmitted; },
		get chapterGuessCompleted() { return chapterGuessCompleted; },
		get chapterCorrect() { return chapterCorrect; },
		markStatsSubmitted,
		markWinTracked,
		isWinAlreadyTracked,
		onChapterGuessCompleted,
		hydrateFromServer,
	};
}
