import type { QueryClient } from "@tanstack/react-query";
import {
	createRootRouteWithContext,
	createRoute,
	createRouter,
	lazyRouteComponent,
	useRouter,
	type ErrorComponentProps,
	type RouterHistory,
} from "@tanstack/react-router";
import AppShell from "@/shell/AppShell";
import { ApiError } from "@/lib/api";
import { createQueryClient } from "@/lib/queryClient";
import {
	blocklistsQuery,
	clientPrefixesQuery,
	clientsQuery,
	forwardZonesQuery,
	groupsQuery,
	healthQuery,
	localRecordsQuery,
	queriesInfiniteQuery,
	rulesQuery,
	settingsQuery,
	statsQuery,
	timeseriesQuery,
	upstreamHealthQuery,
	upstreamsQuery,
} from "@/lib/queries";
import { retryButtonClass } from "@/ui/classes";

export interface RouterContext {
	queryClient: QueryClient;
}

function RoutePending() {
	return (
		<div className="p-8 text-center text-zinc-500" role="status">
			<span className="animate-pulse">Loading…</span>
		</div>
	);
}

function RouteError({ error }: ErrorComponentProps) {
	const router = useRouter();
	let title = "Something went wrong";
	let detail = error.message;
	if (error instanceof ApiError) {
		if (error.status === 503) {
			title = "Server starting or degraded";
			detail = error.message;
		} else if (error.status === 429) {
			title = "Rate limited";
			detail = error.retryAfter !== undefined ? `Try again in ${error.retryAfter}s.` : "Try again shortly.";
		} else if (error.status >= 500) {
			title = "Internal error";
		} else {
			title = `Request failed (${error.status})`;
		}
	}
	return (
		<div
			role="alert"
			className="m-4 rounded border border-red-300 bg-red-50 p-4 dark:border-red-900 dark:bg-red-950"
		>
			<h2 className="font-semibold text-red-800 dark:text-red-200">{title}</h2>
			<p className="mt-1 text-sm text-red-700 dark:text-red-300">{detail}</p>
			<button type="button" onClick={() => void router.invalidate()} className={retryButtonClass}>
				Retry
			</button>
		</div>
	);
}

const rootRoute = createRootRouteWithContext<RouterContext>()();

const loginRoute = createRoute({
	getParentRoute: () => rootRoute,
	path: "/login",
	validateSearch: (search: Record<string, unknown>): { redirect?: string } => ({
		redirect: typeof search["redirect"] === "string" ? search["redirect"] : undefined,
	}),
	component: lazyRouteComponent(() => import("@/auth/LoginPage")),
});

const shellRoute = createRoute({
	getParentRoute: () => rootRoute,
	id: "shell",
	component: AppShell,
});

const dashboardRoute = createRoute({
	getParentRoute: () => shellRoute,
	path: "/",
	// allSettled, not all: DashboardPage reads these with useQuery so each widget
	// can render its own error. A rejecting loader would replace the whole page
	// with RouteError and take the three healthy widgets down with the failed one.
	loader: ({ context }) =>
		Promise.allSettled([
			context.queryClient.ensureQueryData(statsQuery("24h")),
			context.queryClient.ensureQueryData(timeseriesQuery("24h")),
			context.queryClient.ensureQueryData(healthQuery()),
			context.queryClient.ensureQueryData(upstreamHealthQuery()),
		]),
	component: lazyRouteComponent(() => import("@/features/dashboard/DashboardPage")),
});

const queriesRoute = createRoute({
	getParentRoute: () => shellRoute,
	path: "/queries",
	loader: ({ context }) => context.queryClient.ensureInfiniteQueryData(queriesInfiniteQuery({})),
	component: lazyRouteComponent(() => import("@/features/queries/QueryLogPage")),
});

const liveRoute = createRoute({
	getParentRoute: () => shellRoute,
	path: "/live",
	component: lazyRouteComponent(() => import("@/features/live/LiveLogPage")),
});

const clientsRoute = createRoute({
	getParentRoute: () => shellRoute,
	path: "/clients",
	loader: ({ context }) =>
		Promise.all([
			context.queryClient.ensureQueryData(clientsQuery()),
			context.queryClient.ensureQueryData(clientPrefixesQuery()),
			context.queryClient.ensureQueryData(groupsQuery()),
		]),
	component: lazyRouteComponent(() => import("@/features/clients/ClientsPage")),
});

const groupsRoute = createRoute({
	getParentRoute: () => shellRoute,
	path: "/groups",
	loader: ({ context }) =>
		Promise.all([
			context.queryClient.ensureQueryData(groupsQuery()),
			context.queryClient.ensureQueryData(blocklistsQuery()),
		]),
	component: lazyRouteComponent(() => import("@/features/groups/GroupsPage")),
});

const blocklistsRoute = createRoute({
	getParentRoute: () => shellRoute,
	path: "/blocklists",
	loader: ({ context }) => context.queryClient.ensureQueryData(blocklistsQuery()),
	component: lazyRouteComponent(() => import("@/features/blocklists/BlocklistsPage")),
});

const rulesRoute = createRoute({
	getParentRoute: () => shellRoute,
	path: "/rules",
	loader: ({ context }) =>
		Promise.all([
			context.queryClient.ensureQueryData(rulesQuery()),
			context.queryClient.ensureQueryData(groupsQuery()),
		]),
	component: lazyRouteComponent(() => import("@/features/rules/RulesPage")),
});

const localDnsRoute = createRoute({
	getParentRoute: () => shellRoute,
	path: "/local-dns",
	loader: ({ context }) =>
		Promise.all([
			context.queryClient.ensureQueryData(localRecordsQuery()),
			context.queryClient.ensureQueryData(forwardZonesQuery()),
		]),
	component: lazyRouteComponent(() => import("@/features/local/LocalDnsPage")),
});

const upstreamsRoute = createRoute({
	getParentRoute: () => shellRoute,
	path: "/upstreams",
	loader: ({ context }) => context.queryClient.ensureQueryData(upstreamsQuery()),
	component: lazyRouteComponent(() => import("@/features/upstreams/UpstreamsPage")),
});

const lookupRoute = createRoute({
	getParentRoute: () => shellRoute,
	path: "/lookup",
	loader: ({ context }) => context.queryClient.ensureQueryData(groupsQuery()),
	component: lazyRouteComponent(() => import("@/features/lookup/LookupPage")),
});

const settingsRoute = createRoute({
	getParentRoute: () => shellRoute,
	path: "/settings",
	loader: ({ context }) => context.queryClient.ensureQueryData(settingsQuery()),
	component: lazyRouteComponent(() => import("@/features/settings/SettingsPage")),
});

const routeTree = rootRoute.addChildren([
	loginRoute,
	shellRoute.addChildren([
		dashboardRoute,
		queriesRoute,
		liveRoute,
		clientsRoute,
		groupsRoute,
		blocklistsRoute,
		rulesRoute,
		localDnsRoute,
		upstreamsRoute,
		lookupRoute,
		settingsRoute,
	]),
]);

export function createAppRouter(history?: RouterHistory, queryClient: QueryClient = createQueryClient()) {
	return createRouter({
		routeTree,
		history,
		context: { queryClient },
		defaultPreload: "intent",
		defaultPreloadStaleTime: 0,
		defaultPendingComponent: RoutePending,
		defaultErrorComponent: RouteError,
	});
}

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