<script lang="ts">
	import type { PageProps } from "./$types";
	import { browser } from "$app/environment";
	import { enhance } from "$app/forms";
	import { onMount } from "svelte";

	import VerseDisplay from "$lib/components/VerseDisplay.svelte";
	import SearchInput from "$lib/components/SearchInput.svelte";
	import GuessesTable from "$lib/components/GuessesTable.svelte";
	import WinScreen from "$lib/components/WinScreen.svelte";
	import Credits from "$lib/components/Credits.svelte";

	import GamePrompt from "$lib/components/GamePrompt.svelte";
	import DevButtons from "$lib/components/DevButtons.svelte";
	import AuthModal from "$lib/components/AuthModal.svelte";

	import { evaluateGuess, getFirstLetter } from "$lib/utils/game";
	import {
		generateShareText,
		shareResult,
		copyToClipboard as clipboardCopy,
	} from "$lib/utils/share";
	import { fetchStreak, fetchStreakPercentile } from "$lib/utils/streak";
	import {
		submitCompletion,
		fetchExistingStats,
		type StatsData,
	} from "$lib/utils/stats-client";
	import { createGamePersistence } from "$lib/stores/game-persistence.svelte";
	import { SvelteSet } from "svelte/reactivity";

	let { data }: PageProps = $props();

	let dailyVerse = $derived(data.dailyVerse);
	let correctBookId = $derived(data.correctBookId);
	let correctBook = $derived(data.correctBook);
	let user = $derived(data.user);
	let session = $derived(data.session);

	const currentDate = $derived(
		new Date().toLocaleDateString("en-US", {
			weekday: "long",
			year: "numeric",
			month: "long",
			day: "numeric",
		}),
	);

	let searchQuery = $state("");
	let copied = $state(false);
	let isDev = $state(false);
	let authModalOpen = $state(false);
	let showWinScreen = $state(false);
	let statsData = $state<StatsData | null>(null);
	let streak = $state(0);
	let streakPercentile = $state<number | null>(null);
	let guessesMinimized = $state(false);

	const persistence = createGamePersistence(
		() => dailyVerse.date,
		() => dailyVerse.reference,
		() => correctBookId,
		() => user,
	);

	let guessedIds = $derived(
		new SvelteSet(persistence.guesses.map((g) => g.book.id)),
	);

	let isWon = $derived(
		persistence.guesses.some((g) => g.book.id === correctBookId),
	);
	let blurChapter = $derived(
		isWon &&
			persistence.guesses.length === 1 &&
			!persistence.chapterGuessCompleted,
	);

	let knownTestament = $derived(
		persistence.guesses.some((g) => g.testamentMatch)
			? correctBook?.testament
			: null,
	);
	let knownSection = $derived(
		persistence.guesses.some((g) => g.sectionMatch)
			? correctBook?.section
			: null,
	);
	let knownFirstLetter = $derived(
		persistence.guesses.some((g) => g.firstLetterMatch)
			? getFirstLetter(correctBook?.name ?? "").toUpperCase()
			: null,
	);

	let testamentVisible = $state(false);
	let sectionVisible = $state(false);
	let firstLetterVisible = $state(false);
	let showHints = $state(false);

	// On page load, show hints that are already known without animation
	onMount(() => {
		if (knownTestament) testamentVisible = true;
		if (knownSection) sectionVisible = true;
		if (knownFirstLetter) firstLetterVisible = true;

		const winCount = Object.keys(localStorage).filter(
			(k) => k.startsWith("bibdle-win-tracked-") && localStorage.getItem(k) === "true"
		).length;
		showHints = winCount < 3;
	});

	// Fade in newly revealed hints after the guess animation completes
	$effect(() => {
		if (!knownTestament || testamentVisible) return;
		const id = setTimeout(() => {
			testamentVisible = true;
		}, 2800);
		return () => clearTimeout(id);
	});
	$effect(() => {
		if (!knownSection || sectionVisible) return;
		const id = setTimeout(() => {
			sectionVisible = true;
		}, 2800);
		return () => clearTimeout(id);
	});
	$effect(() => {
		if (!knownFirstLetter || firstLetterVisible) return;
		const id = setTimeout(() => {
			firstLetterVisible = true;
		}, 2800);
		return () => clearTimeout(id);
	});

	async function submitGuess(bookId: string) {
		if (persistence.guesses.some((g) => g.book.id === bookId)) return;

		const guess = evaluateGuess(bookId, correctBookId);
		if (!guess) return;

		if (persistence.guesses.length === 0) {
			const key = `bibdle-first-guess-${dailyVerse.date}`;
			if (
				browser &&
				localStorage.getItem(key) !== "true" &&
				(window as any).umami
			) {
				(window as any).umami.track("First guess");
				localStorage.setItem(key, "true");
			}
		}

		persistence.guesses = [guess, ...persistence.guesses];
		searchQuery = "";

		if (
			guess.book.id === correctBookId &&
			browser &&
			persistence.anonymousId
		) {
			statsData = await submitCompletion({
				anonymousId: persistence.anonymousId,
				date: dailyVerse.date,
				guessCount: persistence.guesses.length,
				guesses: persistence.guesses.map((g) => g.book.id),
			});
			if (statsData) {
				persistence.markStatsSubmitted();
			}
		}
	}

	// Reload when the user returns to a stale tab on a new calendar day
	$effect(() => {
		if (!browser) return;

		const loadedDate = new Date().toLocaleDateString("en-CA");

		function onVisibilityChange() {
			if (document.hidden) return;
			const now = new Date().toLocaleDateString("en-CA");
			if (now !== loadedDate) {
				window.location.reload();
			}
		}

		document.addEventListener("visibilitychange", onVisibilityChange);
		return () =>
			document.removeEventListener(
				"visibilitychange",
				onVisibilityChange,
			);
	});

	$effect(() => {
		if (!browser) return;
		isDev =
			window.location.host === "localhost:5173" ||
			window.location.host === "test.bibdle.com";
	});

	// Fetch stats on page load if user already won in a previous session (same device)
	$effect(() => {
		if (
			!browser ||
			!isWon ||
			!persistence.anonymousId ||
			statsData ||
			!persistence.statsSubmitted
		)
			return;
		fetchExistingStats({
			anonymousId: persistence.anonymousId,
			date: dailyVerse.date,
		}).then((data) => {
			statsData = data;
		});
	});

	// For logged-in users on a new device: restore today's game state from the server.
	// Runs even when isWon is true so that logging in after completing the game on another
	// device always replaces local localStorage with the authoritative DB record.
	let crossDeviceCheckDate = $state<string | null>(null);
	$effect(() => {
		if (
			!browser ||
			!user ||
			!dailyVerse?.date ||
			crossDeviceCheckDate === dailyVerse.date ||
			!persistence.anonymousId
		)
			return;
		crossDeviceCheckDate = dailyVerse.date;
		fetchExistingStats({
			anonymousId: persistence.anonymousId,
			date: dailyVerse.date,
		}).then((data) => {
			if (data?.guesses?.length) {
				persistence.hydrateFromServer(data.guesses);
				statsData = data;
				persistence.markStatsSubmitted();
			}
		});
	});

	// Delay showing win screen until GuessesTable animation completes
	$effect(() => {
		if (!isWon) {
			showWinScreen = false;
			return;
		}

		if (persistence.isWinAlreadyTracked()) {
			showWinScreen = true;
		} else {
			const animationDelay = 1800;
			const timeoutId = setTimeout(() => {
				showWinScreen = true;
			}, animationDelay);
			return () => clearTimeout(timeoutId);
		}
	});

	// Delay collapsing the guesses grid until animations complete (mirrors showWinScreen delay)
	$effect(() => {
		if (!isWon || persistence.guesses.length <= 3) {
			guessesMinimized = false;
			return;
		}
		if (persistence.isWinAlreadyTracked()) {
			guessesMinimized = true;
		} else {
			const animationDelay = 1800;
			const timeoutId = setTimeout(() => {
				guessesMinimized = true;
			}, animationDelay);
			return () => clearTimeout(timeoutId);
		}
	});

	// Track win analytics
	$effect(() => {
		if (!browser || !isWon) return;
		const isNew = persistence.markWinTracked();
		if (isNew && (window as any).umami) {
			(window as any).umami.track("Guessed correctly", {
				totalGuesses: persistence.guesses.length,
			});
		}
	});

	// Fetch streak when the player wins
	$effect(() => {
		if (!browser || !isWon || !persistence.anonymousId) return;
		const localDate = new Date().toLocaleDateString("en-CA");
		fetchStreak(persistence.anonymousId, localDate).then((result) => {
			streak = result;
			if (result >= 2) {
				fetchStreakPercentile(result, localDate).then((p) => {
					streakPercentile = p;
				});
			}
		});
	});

	function getShareText(): string {
		return generateShareText({
			guesses: persistence.guesses,
			correctBookId,
			dailyVerseDate: dailyVerse.date,
			chapterCorrect: persistence.chapterCorrect,
			isLoggedIn: !!user,
			streak,
			origin: window.location.origin,
			verseText: dailyVerse.verseText,
		});
	}

	function handleShare() {
		if (copied || !browser) return;
		const useClipboard = !("share" in navigator);
		if (useClipboard) {
			copied = true;
		}
		shareResult(getShareText())
			.then(() => {
				if (useClipboard) {
					setTimeout(() => {
						copied = false;
					}, 5000);
				}
			})
			.catch(() => {
				if (useClipboard) {
					copied = false;
				}
			});
	}

	async function handleCopyToClipboard() {
		if (!browser) return;
		try {
			await clipboardCopy(getShareText());
			copied = true;
			setTimeout(() => {
				copied = false;
			}, 5000);
		} catch (err) {
			console.error("Copy to clipboard failed:", err);
		}
	}
</script>

<svelte:head>
	<title>A daily bible game{isDev ? " (dev)" : ""}</title>
</svelte:head>

<div class="pb-8">
	<div class="w-full max-w-3xl mx-auto px-4">
		<div class="text-center mb-8 animate-fade-in-up animate-delay-200">
			<span class="big-text"
				>{isDev ? "Dev Edition  |  " : ""}{currentDate}</span
			>
		</div>
		<div class="flex flex-col gap-6">
			<div class="animate-fade-in-up animate-delay-200">
				<VerseDisplay {data} {isWon} {blurChapter} />
			</div>

			{#if !isWon}
				<div class="animate-fade-in-up animate-delay-400">
					<GamePrompt guessCount={persistence.guesses.length} />

					{#if showHints && (knownTestament || knownSection || knownFirstLetter)}
						<div
							class="text-xs uppercase tracking-widest font-bold text-center text-gray-500 dark:text-gray-400 flex flex-col gap-1 mb-4 -mt-2"
						>
							{#if knownTestament}
								<p
									style="transition: opacity 0.5s ease; opacity: {testamentVisible
										? 1
										: 0};"
								>
									It is in the {knownTestament === "old"
										? "Old"
										: "New"} Testament.
								</p>
							{/if}
							{#if knownSection}
								<p
									style="transition: opacity 0.5s ease; opacity: {sectionVisible
										? 1
										: 0};"
								>
									It is in the {knownSection} section.
								</p>
							{/if}
							{#if knownFirstLetter}
								<p
									style="transition: opacity 0.5s ease; opacity: {firstLetterVisible
										? 1
										: 0};"
								>
									The book's name starts with "{knownFirstLetter}".
								</p>
							{/if}
						</div>
					{/if}

					<SearchInput
						bind:searchQuery
						{guessedIds}
						{submitGuess}
						guessCount={persistence.guesses.length}
					/>
				</div>
			{:else if showWinScreen}
				<div class="animate-fade-in-up animate-delay-400">
					<WinScreen
						{statsData}
						{correctBookId}
						{handleShare}
						copyToClipboard={handleCopyToClipboard}
						bind:copied
						statsSubmitted={persistence.statsSubmitted}
						guessCount={persistence.guesses.length}
						reference={dailyVerse.reference}
						onChapterGuessCompleted={persistence.onChapterGuessCompleted}
						shareText={getShareText()}
						verseText={dailyVerse.verseText}
						{streak}
						{streakPercentile}
						isLoggedIn={!!user}
						anonymousId={persistence.anonymousId}
						localDate={new Date().toLocaleDateString("en-CA")}
					/>
				</div>
			{/if}

			<div class="animate-fade-in-up animate-delay-600">
				<GuessesTable
					guesses={persistence.guesses}
					{correctBookId}
					minimized={guessesMinimized}
				/>
			</div>

			{#if isWon}
				<hr
					class="animate-fade-in-up animate-delay-800 border-gray-300 dark:border-gray-600"
				/>
				<div class="animate-fade-in-up animate-delay-800">
					<Credits />
				</div>
			{/if}
		</div>
		{#if isDev}
			<div class="mt-8 flex flex-col items-stretch md:items-center gap-3">
				<div
					class="text-xs text-gray-600 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 px-3 py-2 rounded border dark:border-gray-700"
				>
					<div><strong>Debug Info:</strong></div>
					<div>
						User: {user
							? `${user.email} (ID: ${user.id})`
							: "Not signed in"}
					</div>
					<div>
						Session: {session
							? `Expires ${session.expiresAt.toLocaleDateString()}`
							: "No session"}
					</div>
					<div>
						Anonymous ID: {persistence.anonymousId || "Not set"}
					</div>
					<div>
						Client Local Time: {new Date().toLocaleString("en-US", {
							timeZone:
								Intl.DateTimeFormat().resolvedOptions()
									.timeZone,
							timeZoneName: "short",
						})}
					</div>
					<div>
						Client Local Date: {new Date().toLocaleDateString(
							"en-CA",
						)}
					</div>
					<div>Daily Verse Date: {dailyVerse.date}</div>
					<div>Streak: {streak}</div>
				</div>
				<DevButtons
					anonymousId={persistence.anonymousId}
					{user}
					onSignIn={() => (authModalOpen = true)}
				/>
			</div>
		{/if}

		{#if user && session}
			<div
				class="mt-6 pt-4 border-t border-gray-200 dark:border-gray-700 text-center text-xs text-gray-400 dark:text-gray-500"
			>
				Signed in as {[user.firstName, user.lastName]
					.filter(Boolean)
					.join(" ")}{user.email
					? ` (${user.email})`
					: ""}{user.appleId ? " using Apple" : user.googleId ? " using Google" : ""} |

				<form
					method="POST"
					action="/auth/logout"
					use:enhance
					class="inline"
				>
					<button
						type="submit"
						class="ml-2 underline hover:text-gray-600 transition-colors cursor-pointer"
						>Sign out</button
					>
				</form>
			</div>
		{/if}
	</div>
</div>

<AuthModal bind:isOpen={authModalOpen} anonymousId={persistence.anonymousId} />
