/**
 * Overview: what the resolver did over a period the reader chooses, in the
 * layout Pi-hole's dashboard established — four totals, two full-width charts,
 * two breakdown donuts. Nothing on this page is a current-state readout; the
 * five health conditions live on Diagnostics, and protection lives in the
 * sidebar beside its control.
 *
 * The period is URL state, so a view is a link: `/overview?period=1h` opens
 * exactly what the sender was reading.
 *
 * Every panel reads the same window (`overviewWindow.ts`) and renders on its
 * own. A donut whose request failed shows its own error while the charts keep
 * their data, and no two panels ever describe different spans.
 */

import * as stylex from "@stylexjs/stylex";
import { useNavigate, useSearch } from "@tanstack/react-router";
import CoverageNotice from "@/lib/CoverageNotice";
import InlineError from "@/lib/InlineError";
import { qtypeName } from "@/features/provenance/qtype";
import type { Period, StatsRoutes, StatsTypes } from "@/lib/types";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
import ClientChart from "./ClientChart";
import Donut from "./Donut";
import StatTiles from "./StatTiles";
import TimeseriesChart from "./TimeseriesChart";
import type { DonutSlice } from "./donutLayout";
import { useOverviewWindow, type Panel } from "./overviewWindow";
import { DEFAULT_PERIOD, PERIODS } from "./period";
import { qtypeKey, routeKey, seriesColor } from "./seriesColors";

/**
 * Where the two donuts stop competing for width and sit side by side. `Donut`
 * carries the same query for the alignment it switches at that width; StyleX
 * requires the string to be a literal in the module that uses it, so the two
 * agree by inspection rather than by sharing a constant.
 */
const TWO_COLUMN = "@media (min-width: 1280px)";

const ROUTE_LABELS = {
	blocked: "Blocked",
	cache: "Cache",
	local: "Local",
	rejected: "Rejected",
	upstream: "Upstream",
	forward_zone: "Forward zone",
} as const;

const styles = stylex.create({
	page: {
		display: "flex",
		flexDirection: "column",
		gap: "1rem",
	},
	headingRow: {
		display: "flex",
		flexWrap: "wrap",
		alignItems: "center",
		justifyContent: "space-between",
		gap: "0.75rem",
	},
	heading: {
		fontSize: "1.5rem",
		lineHeight: "2rem",
		fontWeight: 600,
	},
	periodGroup: {
		display: "flex",
		gap: "0.25rem",
	},
	period: {
		borderStyle: "none",
		borderRadius: "0.25rem",
		paddingInline: "0.625rem",
		paddingBlock: "0.25rem",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
	},
	/** The pressed fill is heavier than `surfaceHover`, so a hover cannot mimic it. */
	periodSelected: {
		backgroundColor: {
			default: "oklch(92% 0.004 286.32)",
			"@media (prefers-color-scheme: dark)": "oklch(37% 0.013 285.805)",
		},
		color: colors.text,
		fontWeight: 500,
	},
	periodIdle: {
		backgroundColor: { default: "transparent", ":hover": colors.surfaceHover },
		color: colors.textSecondary,
	},
	panel: {
		borderRadius: "0.25rem",
		borderWidth: 1,
		borderStyle: "solid",
		borderColor: colors.border,
		backgroundColor: colors.surfaceRaised,
		paddingInline: "1rem",
		paddingBlock: "0.75rem",
	},
	panelHeading: {
		marginBottom: "0.75rem",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		fontWeight: 600,
	},
	donutRow: {
		display: "grid",
		gap: "1rem",
		gridTemplateColumns: { default: "minmax(0, 1fr)", [TWO_COLUMN]: "repeat(2, minmax(0, 1fr))" },
	},
	loading: {
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.textMuted,
	},
});

function PeriodPicker({ period, onChange }: { period: Period; onChange: (period: Period) => void }) {
	return (
		<div role="group" aria-label="Period" {...stylex.props(styles.periodGroup)}>
			{PERIODS.map((option) => (
				<button
					key={option}
					type="button"
					aria-pressed={option === period}
					onClick={() => onChange(option)}
					{...stylex.props(
						styles.period,
						option === period ? styles.periodSelected : styles.periodIdle,
						shared.focusRing,
					)}
				>
					{option}
				</button>
			))}
		</div>
	);
}

/**
 * One panel's three states. Loading and error are the panel's own: a failure
 * here never reaches past this box, which is what keeps a failed donut from
 * blanking the charts beside it.
 */
function PanelBody<T>({ panel, children }: { panel: Panel<T>; children: (data: T) => React.ReactNode }) {
	if (panel.status === "error") return <InlineError error={panel.error} onRetry={panel.retry} />;
	if (panel.status === "loading") {
		return (
			<p role="status" {...stylex.props(styles.loading, shared.pulse)}>
				Loading…
			</p>
		);
	}
	return <>{children(panel.data)}</>;
}

function typeSlices(data: StatsTypes): DonutSlice[] {
	return data.types.map((row) => ({
		key: qtypeKey(row.qtype),
		label: row.qtype === null ? "Unknown" : qtypeName(row.qtype),
		value: row.count,
		color: seriesColor(qtypeKey(row.qtype)),
	}));
}

/**
 * Route slices. A row that names a resolver or a zone is labelled by that name
 * with the route kind as secondary text, because one name can legitimately
 * appear under two kinds and two rows can both be "Unknown". The four
 * source-less kinds are their own label and need no qualifier.
 */
function routeSlices(data: StatsRoutes): DonutSlice[] {
	return data.routes.map((row) => {
		const named = row.route === "upstream" || row.route === "forward_zone";
		return {
			key: routeKey(row.route, row.source),
			label: named ? (row.source ?? "Unknown") : ROUTE_LABELS[row.route],
			...(named ? { secondary: ROUTE_LABELS[row.route] } : {}),
			value: row.count,
			color: seriesColor(routeKey(row.route, row.source)),
		};
	});
}

export default function OverviewPage() {
	const period = useSearch({ from: "/shell/overview" }).period ?? DEFAULT_PERIOD;
	const navigate = useNavigate({ from: "/overview" });
	const overview = useOverviewWindow(period);

	return (
		<div {...stylex.props(styles.page)}>
			<div {...stylex.props(styles.headingRow)}>
				<h1 {...stylex.props(styles.heading)}>Overview</h1>
				<PeriodPicker
					period={period}
					onChange={(next) => void navigate({ search: (prev) => ({ ...prev, period: next }) })}
				/>
			</div>

			<PanelBody panel={overview.totals}>{(totals) => <StatTiles stats={totals} />}</PanelBody>

			{/* One notice for the page: every panel is judged against the same window,
			    so a second copy would only repeat this sentence. */}
			{overview.coverage !== null && <CoverageNotice coverage={overview.coverage} />}

			<section aria-labelledby="overview-queries" {...stylex.props(styles.panel)}>
				<h2 id="overview-queries" {...stylex.props(styles.panelHeading)}>
					Queries over time
				</h2>
				<PanelBody panel={overview.timeseries}>{(data) => <TimeseriesChart data={data} />}</PanelBody>
			</section>

			<section aria-labelledby="overview-clients" {...stylex.props(styles.panel)}>
				<h2 id="overview-clients" {...stylex.props(styles.panelHeading)}>
					Client activity over time
				</h2>
				<PanelBody panel={overview.clients}>{(data) => <ClientChart data={data} />}</PanelBody>
			</section>

			<div {...stylex.props(styles.donutRow)}>
				<section aria-labelledby="overview-types" {...stylex.props(styles.panel)}>
					<h2 id="overview-types" {...stylex.props(styles.panelHeading)}>
						Query types
					</h2>
					<PanelBody panel={overview.types}>
						{(data) => <Donut slices={typeSlices(data)} caption="Queries by DNS type" unit="Queries" />}
					</PanelBody>
				</section>
				<section aria-labelledby="overview-routes" {...stylex.props(styles.panel)}>
					<h2 id="overview-routes" {...stylex.props(styles.panelHeading)}>
						Upstream servers
					</h2>
					<PanelBody panel={overview.routes}>
						{(data) => (
							<Donut
								slices={routeSlices(data)}
								caption="Queries by how they were answered"
								unit="Queries"
							/>
						)}
					</PanelBody>
				</section>
			</div>
		</div>
	);
}
