import { i18nReady } from "@/lib/i18n";
import { boundedWait } from "@/lib/i18n/boundedWait";
import { clearAutoReloadGuard, getSkewMode, installVersionSkewListeners, subscribeSkew } from "@/lib/utils/versionSkew";
import { RouterProvider, createRouter, parseSearchWith, stringifySearchWith } from "@tanstack/react-router";
import { StrictMode, useEffect, useSyncExternalStore } from "react";
import { createRoot } from "react-dom/client";

// Tailwind + global styles (also declares @font-face for local Geist fonts).
import "@/app/globals.css";

import { ErrorComponent } from "./__error";
import { NotFoundComponent } from "./__notFound";
import { SkewBanner, SkewScreen } from "./__updating";
import { routeTree } from "./routeTree.gen";
import { getBasePath } from "@/lib/utils/basePath";

installVersionSkewListeners();

// Only JSON.parse structured values (objects/arrays). Plain strings and numbers
// stay as-is so large numeric IDs don't lose precision through Number coercion.
function safeJsonParse(value: string): unknown {
	const c = value[0];
	if (c === "{" || c === "[") {
		try {
			return JSON.parse(value);
		} catch {
			return value;
		}
	}
	return value;
}

// Keep query params compatible with nuqs parsers: primitive strings stay raw
// (so numeric-looking IDs are not JSON-quoted), while arrays use comma lists.
function stringifySearchValue(value: unknown): string {
	if (Array.isArray(value)) return value.map(String).join(",");
	if (value === null || value === undefined) return "";
	if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
		return String(value);
	}
	return JSON.stringify(value);
}

const router = createRouter({
	routeTree,
	basepath: getBasePath() || "/",
	defaultPreload: "intent",
	scrollRestoration: true,
	notFoundMode: "root",
	defaultNotFoundComponent: NotFoundComponent,
	defaultErrorComponent: ErrorComponent,
	parseSearch: parseSearchWith(safeJsonParse),
	stringifySearch: stringifySearchWith(stringifySearchValue),
});

declare module "@tanstack/react-router" {
	interface Register {
		router: typeof router;
	}
}

const HEALTHY_UPTIME_MS = 30_000;

function Root() {
	const skewMode = useSyncExternalStore(subscribeSkew, getSkewMode, getSkewMode);

	useEffect(() => {
		if (skewMode !== "none") return;
		const timer = setTimeout(clearAutoReloadGuard, HEALTHY_UPTIME_MS);
		return () => clearTimeout(timer);
	}, [skewMode]);

	if (skewMode === "hard") return <SkewScreen />;

	return (
		<>
			<RouterProvider router={router} />
			{skewMode === "soft" && <SkewBanner />}
		</>
	);
}

const I18N_READY_TIMEOUT_MS = 3000;

declare global {
	interface Window {
		__bifrostBooted?: boolean;
	}
}
window.__bifrostBooted = true;

const rootEl = document.getElementById("root");
if (!rootEl) throw new Error("Root element #root not found");

// A stalled catalog request must not hold the app on the loading shell; English renders and the language fills in on arrival.
void boundedWait(i18nReady, I18N_READY_TIMEOUT_MS).then(() =>
	createRoot(rootEl).render(
		<StrictMode>
			<Root />
		</StrictMode>,
	),
);