<script lang="ts">
	import { Confetti } from "svelte-confetti";
	import { toast } from "svelte-sonner";
	import { getContext, onMount, onDestroy, tick } from "svelte";

	import {
		exportChatStats,
		exportSingleChatStats,
		downloadChatStats,
	} from "$lib/apis/chats";
	import { getVersion } from "$lib/apis";

	import Modal from "$lib/components/common/Modal.svelte";
	import Tooltip from "$lib/components/common/Tooltip.svelte";
	import XMark from "$lib/components/icons/XMark.svelte";
	import Spinner from "$lib/components/common/Spinner.svelte";

	const i18n = getContext("i18n");

	export let show = false;
	export let eventData = null;

	// Listen for verify:chat messages from opener
	const handleMessage = async (event: MessageEvent) => {
		// Community sends: { type: 'verify:chat', data: { id: ... } }
		const chatId = event.data?.data?.id ?? event.data?.id;
		if (event.data?.type === "verify:chat" && chatId) {
			try {
				const res = await exportSingleChatStats(
					localStorage.token,
					chatId,
				);
				if (res && window.opener) {
					window.opener.postMessage(
						{
							type: "verify:chat:response",
							data: res,
							chatId: chatId,
							requestId: event.data.requestId ?? null,
						},
						"*",
					);
				}
			} catch (err: any) {
				console.error("Failed to verify chat:", err);
				if (window.opener) {
					window.opener.postMessage(
						{
							type: "verify:chat:error",
							error:
								err?.detail ||
								err?.message ||
								"Failed to verify chat",
							chatId: chatId,
							requestId: event.data.requestId ?? null,
						},
						"*",
					);
				}
			}
		}
	};

	// Watch for modal visibility changes to notify opener
	$: if (show && window.opener) {
		window.opener.postMessage("loaded", "*");
	}

	onMount(() => {
		window.addEventListener("message", handleMessage);
	});

	onDestroy(() => {
		window.removeEventListener("message", handleMessage);
	});

	// State
	let syncing = false;
	let downloading = false;
	let completed = false;
	let error = false;
	let errorMessage = "";

	// Progress tracking
	let processedItemsCount = 0;
	let total = 0;

	// Download controller for cancellation
	let downloadController: AbortController | null = null;

	// Sync mode: 'incremental' or 'full'
	let syncMode: "incremental" | "full" = "incremental";

	// Reactive progress percentage
	$: progressPercent =
		total > 0
			? Math.min(Math.round((processedItemsCount / total) * 100), 100)
			: 0;

	// Helper to send postMessage to opener
	const postToOpener = (message: object) => {
		if (window.opener) {
			window.opener.postMessage(
				{ ...message, requestId: eventData?.requestId ?? null },
				"*",
			);
		}
	};

	// Reset all state
	const resetState = () => {
		syncing = false;
		downloading = false;
		completed = false;
		error = false;
		errorMessage = "";
		processedItemsCount = 0;
		total = 0;
		downloadController = null;
	};

	// Handle error state
	const handleError = (message: string) => {
		console.error("Sync error:", message);
		errorMessage = message;
		error = true;
		syncing = false;
		downloading = false;
		postToOpener({ type: "sync:error", error: message });
	};

	// Cancel ongoing operations
	const cancelOperation = () => {
		if (downloadController) {
			downloadController.abort();
			downloadController = null;
		}
		syncing = false;
		downloading = false;

		postToOpener({
			type: "sync:error",
			error: "User cancelled the operation",
		});
	};

	// Sync stats to opener window
	const syncStats = async () => {
		if (window.opener) {
			window.opener.focus();
		}
		postToOpener({ type: "sync:start" });

		syncing = true;
		error = false;
		errorMessage = "";
		processedItemsCount = 0;
		total = 0;

		try {
			// Get version info
			const versionRes = await getVersion(localStorage.token).catch(
				(err) => {
					console.error("Failed to get version:", err);
					return null;
				},
			);

			if (versionRes) {
				postToOpener({ type: "sync:version", data: versionRes });
			}

			// Paginate through stats
			let page = 1;
			let allItemsLoaded = false;

			while (!allItemsLoaded) {
				// Build search params, include updated_at for incremental mode
				const searchParams = { ...(eventData?.searchParams ?? {}) };
				if (
					syncMode === "incremental" &&
					eventData?.lastSyncedChatUpdatedAt
				) {
					searchParams.updated_at = eventData.lastSyncedChatUpdatedAt;
				}

				const res = await exportChatStats(
					localStorage.token,
					page,
					searchParams,
				).catch((err) => {
					throw new Error(
						err?.detail ||
							err?.message ||
							"Failed to export chat stats",
					);
				});

				if (!res) {
					throw new Error("Failed to fetch stats data");
				}

				processedItemsCount += res.items.length;
				total = res.total;

				// Allow UI to repaint
				await tick();

				if (window.opener && res.items.length > 0) {
					postToOpener({ type: "sync:stats:chats", data: res });
				}

				if (processedItemsCount >= total || res.items.length === 0) {
					allItemsLoaded = true;
				} else {
					page += 1;
				}
			}

			// Success
			postToOpener({ type: "sync:complete" });
			syncing = false;
			completed = true;
		} catch (err: any) {
			handleError(err?.message || "An unexpected error occurred");
		}
	};

	// Download stats as JSON file
	const downloadHandler = async () => {
		if (downloading) {
			cancelOperation();
			return;
		}

		downloading = true;
		syncing = true;
		error = false;
		errorMessage = "";
		processedItemsCount = 0;
		total = 0;

		try {
			// Get total count first (no filters for download - get all)
			const initialRes = await exportChatStats(
				localStorage.token,
				1,
				{},
			).catch(() => null);

			if (initialRes?.total) {
				total = initialRes.total;
			}

			// Allow UI to show the total
			await tick();

			// Get version for filename
			const versionRes = await getVersion(localStorage.token).catch(
				() => null,
			);
			const version = versionRes?.version ?? "0.0.0";
			const filename = `rexpro-ai-stats-${version}-${Date.now()}.json`;

			// Start streaming download
			const searchParams = eventData?.searchParams ?? {};
			const [res, controller] = await downloadChatStats(
				localStorage.token,
				searchParams.updated_at,
			).catch((err) => {
				throw new Error(
					err?.detail ||
						"Failed to connect to the server. Please check your connection.",
				);
			});

			if (!res) {
				throw new Error(
					"Failed to start download. The server may be unavailable.",
				);
			}

			downloadController = controller;
			const reader = res.body.getReader();
			const decoder = new TextDecoder();

			const items: any[] = [];
			let buffer = "";

			while (true) {
				const { done, value } = await reader.read();
				if (done) break;

				buffer += decoder.decode(value, { stream: true });
				const lines = buffer.split("\n");
				buffer = lines.pop() || "";

				for (const line of lines) {
					if (line.trim() !== "") {
						try {
							items.push(JSON.parse(line));
							processedItemsCount += 1;
						} catch (e) {
							console.error("Error parsing line:", e);
						}
					}
				}

				// Allow UI to repaint after each chunk
				await tick();
			}

			// Process remaining buffer
			if (buffer.trim() !== "") {
				try {
					items.push(JSON.parse(buffer));
					processedItemsCount += 1;
				} catch (e) {
					console.error("Error parsing buffer:", e);
				}
			}

			// Trigger download if not cancelled
			if (downloading) {
				const blob = new Blob([JSON.stringify(items)], {
					type: "application/json",
				});
				const url = window.URL.createObjectURL(blob);
				const a = document.createElement("a");
				a.href = url;
				a.download = filename;
				document.body.appendChild(a);
				a.click();
				document.body.removeChild(a);
				window.URL.revokeObjectURL(url);
			}
		} catch (err: any) {
			// Don't show error if user cancelled the download
			if (
				err?.name === "AbortError" ||
				err?.message?.includes("aborted")
			) {
				// User cancelled - just reset state silently
			} else {
				handleError(
					err?.message || "Download failed. Please try again.",
				);
				toast.error(errorMessage);
			}
		} finally {
			downloading = false;
			syncing = false;
			downloadController = null;
		}
	};

	// Close modal and reset state
	const closeModal = () => {
		show = false;
		resetState();
	};
</script>

<Modal bind:show size="md">
	<div class="w-full">
		{#if completed}
			<div class="px-5.5 py-5">
				<div class="mb-1 text-xl font-medium">
					{$i18n.t("Sync Complete!")}
				</div>
				<div class="mb-3 text-xs text-gray-500">
					{$i18n.t("Your usage stats have been successfully synced.")}
				</div>

				<Confetti x={[-0.5, 0.5]} y={[0.25, 1]} />

				<div class="flex justify-end">
					<button
						class="flex items-center justify-center gap-2 rounded-full bg-black px-4 py-2 text-sm text-white transition hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100"
						on:click={closeModal}
					>
						{$i18n.t("Done")}
					</button>
				</div>
			</div>
		{:else if error}
			<div class="px-5.5 py-5">
				<div class="mb-1 text-xl font-medium">
					{$i18n.t("Sync Failed")}
				</div>
				<div class="mb-3 text-xs text-gray-500">
					{errorMessage ||
						$i18n.t(
							"There was an error syncing your stats. Please try again.",
						)}
				</div>

				<div class="flex justify-end">
					<button
						class="flex items-center justify-center gap-2 rounded-full bg-black px-4 py-2 text-sm text-white transition hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100"
						on:click={() => {
							error = false;
							errorMessage = "";
						}}
					>
						{$i18n.t("Try Again")}
					</button>
				</div>
			</div>
		{:else}
			<div class="flex justify-between px-5 pt-4 pb-0.5">
				<div class="text-lg font-medium self-center">
					{$i18n.t("Sync Usage Stats")}
				</div>
				<button
					class="self-center"
					aria-label={$i18n.t("Close modal")}
					on:click={() => {
						show = false;
					}}
					disabled={syncing}
				>
					<XMark className={"size-5"} />
				</button>
			</div>

			<div class="px-5 pt-2 pb-5">
				<div class="text-sm text-gray-500 dark:text-gray-400">
					{$i18n.t(
						"Do you want to sync your usage stats with rexpro-ai Community?",
					)}
				</div>

				<div class="mt-2 text-xs text-gray-500">
					{$i18n.t(
						"Participate in community leaderboards and evaluations! Syncing aggregated usage stats helps drive research and improvements to rexpro-ai. Your privacy is paramount: no message content is ever shared.",
					)}
				</div>

				<div class="mt-3 text-xs text-gray-500">
					<div
						class="font-medium text-gray-900 dark:text-gray-100 mb-1"
					>
						{$i18n.t("What is shared:")}
					</div>
					<ul class="list-disc list-inside space-y-0.5 ml-1 mb-2">
						<li>{$i18n.t("rexpro-ai version")}</li>
						<li>{$i18n.t("Model names and usage frequency")}</li>
						<li>
							{$i18n.t("Message counts and response timestamps")}
						</li>
						<li>
							{$i18n.t("Content lengths (character counts only)")}
						</li>
						<li>{$i18n.t("User ratings (thumbs up/down)")}</li>
					</ul>

					<div
						class="font-medium text-gray-900 dark:text-gray-100 mb-1"
					>
						{$i18n.t("What is NOT shared:")}
					</div>
					<ul class="list-disc list-inside space-y-0.5 ml-1">
						<li>{$i18n.t("Your message text or inputs")}</li>
						<li>{$i18n.t("Model responses or outputs")}</li>
						<li>{$i18n.t("Uploaded files or images")}</li>
					</ul>
				</div>

				{#if eventData?.lastSyncedChatUpdatedAt}
					<div class="mt-3">
						<Tooltip
							content={$i18n.t(
								"Syncs only chats with updates after your last sync timestamp. Disable to re-sync all chats.",
							)}
							placement="top-start"
						>
							<label
								class="flex items-center gap-2 text-xs cursor-pointer"
							>
								<input
									type="checkbox"
									checked={syncMode === "incremental"}
									on:change={(e) =>
										(syncMode = e.target.checked
											? "incremental"
											: "full")}
									disabled={syncing}
									class="w-4 h-4 rounded border-gray-300 dark:border-gray-600"
								/>
								<span class="text-gray-700 dark:text-gray-300"
									>{$i18n.t(
										"Only sync new/updated chats",
									)}</span
								>
							</label>
						</Tooltip>
					</div>
				{/if}

				{#if syncing}
					<div class="mt-3 mx-1.5">
						<div
							class="text-xs text-gray-500 mb-1 flex justify-between"
						>
							<div>
								{downloading
									? $i18n.t("Downloading stats...")
									: $i18n.t("Syncing stats...")}
							</div>
							<div>
								{#if total > 0}
									{processedItemsCount}/{total}
								{/if}
							</div>
						</div>
						<div
							class="w-full bg-gray-200 rounded-full h-1.5 dark:bg-gray-700 overflow-hidden"
						>
							{#if total > 0}
								<div
									class="bg-gray-900 dark:bg-gray-100 h-1.5 rounded-full transition-all duration-300"
									style="width: {progressPercent}%"
								></div>
							{:else}
								<div
									class="bg-gray-900 dark:bg-gray-100 h-1.5 w-0 rounded-full animate-pulse"
								></div>
							{/if}
						</div>
					</div>
				{/if}

				<div class="mt-5 flex justify-between items-center gap-2">
					<div class="text-xs text-gray-400 text-center mr-auto">
						<button
							class="hover:underline px-2"
							type="button"
							on:click={downloadHandler}
							disabled={syncing && !downloading}
						>
							{downloading
								? $i18n.t("Stop Download")
								: $i18n.t("Download as JSON")}
						</button>
					</div>

					<button
						class="px-4 py-2 rounded-full text-sm font-medium bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100 transition disabled:cursor-not-allowed"
						on:click={() => {
							if (syncing) {
								cancelOperation();
							} else {
								show = false;
							}
						}}
					>
						{$i18n.t("Cancel")}
					</button>

					<button
						class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition-colors rounded-full"
						on:click={syncStats}
						disabled={syncing}
					>
						{#if syncing && !downloading}
							<div class="flex items-center gap-2">
								<Spinner className="size-3" />
								<span>{$i18n.t("Syncing...")}</span>
							</div>
						{:else}
							{$i18n.t("Sync")}
						{/if}
					</button>
				</div>
			</div>
		{/if}
	</div>
</Modal>
