import type { QueryClient } from "@tanstack/react-query";
import * as stylex from "@stylexjs/stylex";
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 { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";

export interface RouterContext {
	queryClient: QueryClient;
}

const styles = stylex.create({
	pending: {
		padding: "2rem",
		textAlign: "center",
		color: colors.textMuted,
	},
	errorBox: {
		margin: "1rem",
		borderRadius: "0.25rem",
		borderWidth: 1,
		borderStyle: "solid",
		borderColor: colors.dangerBorder,
		backgroundColor: colors.dangerSurface,
		padding: "1rem",
	},
	errorTitle: {
		fontWeight: 600,
		color: colors.dangerText,
	},
	errorDetail: {
		marginTop: "0.25rem",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.dangerText,
	},
});

function RoutePending() {
	return (
		<div {...stylex.props(styles.pending)} role="status">
			<span {...stylex.props(shared.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" {...stylex.props(styles.errorBox)}>
			<h2 {...stylex.props(styles.errorTitle)}>{title}</h2>
			<p {...stylex.props(styles.errorDetail)}>{detail}</p>
			<button
				type="button"
				onClick={() => void router.invalidate()}
				{...stylex.props(shared.retryButton, shared.focusRing)}
			>
				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>;
	}
}
