import * as stylex from "@stylexjs/stylex";
import { Group } from "@visx/group";
import { BarStack } from "@visx/shape";
import { formatTime } from "@/lib/format";
import type { Bucket } from "@/lib/types";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
import {
	BucketOverlay,
	CHART_HEIGHT,
	ChartFrame,
	ChartRoot,
	ChartTooltip,
	EmptyChart,
	StackSegment,
	bandScale,
	labelTickValues,
	plotArea,
	slotCenter,
	useActiveIndex,
	useMeasuredWidth,
	valueScale,
	valueTicks,
	type TooltipContent,
} from "./chartKit";

// Series colors validated for CVD separation and 3:1 surface contrast in both
// modes (Tailwind red-500 / blue-500 / emerald-600; same hex light and dark).
// The third key stays `other` — it is the colour key and the response field, and
// renaming it would repaint the series. Only what the reader sees is "Allowed".
const SERIES = [
	{ key: "blocked", label: "Blocked", color: "#ef4444" },
	{ key: "cached", label: "Cached", color: "#059669" },
	{ key: "other", label: "Allowed", color: "#3b82f6" },
] as const;

type Series = (typeof SERIES)[number];
type SeriesKey = Series["key"];

const SERIES_COLOR: Record<SeriesKey, string> = {
	blocked: SERIES[0].color,
	cached: SERIES[1].color,
	other: SERIES[2].color,
};

const styles = stylex.create({
	legend: {
		marginTop: "0.5rem",
		display: "flex",
		flexWrap: "wrap",
		columnGap: "1rem",
		rowGap: "0.25rem",
		fontSize: "0.75rem",
		lineHeight: "1rem",
		color: colors.textSecondary,
	},
	legendItem: {
		display: "flex",
		alignItems: "center",
		gap: "0.375rem",
	},
	swatch: {
		display: "inline-block",
		width: "0.625rem",
		height: "0.625rem",
		borderRadius: "0.125rem",
	},
	/** Dynamic: the swatch takes the series colour the SVG bars are drawn in. */
	swatchColor: (color: string) => ({ backgroundColor: color }),
});

interface Column {
	ts: number;
	queries: number;
	blocked: number;
	cached: number;
	/** queries - blocked - cached, clamped at 0. */
	other: number;
}

function columnsOf(buckets: Bucket[]): Column[] {
	return buckets.map((bucket) => ({
		ts: bucket.ts,
		queries: bucket.queries,
		blocked: bucket.blocked,
		cached: bucket.cached,
		other: Math.max(0, bucket.queries - bucket.blocked - bucket.cached),
	}));
}

function tooltipOf(column: Column): TooltipContent {
	return {
		title: formatTime(column.ts),
		rows: [
			{ key: "queries", label: "Queries", value: String(column.queries) },
			...SERIES.map((series) => ({
				key: series.key,
				label: series.label,
				color: series.color,
				value: String(column[series.key]),
			})),
		],
	};
}

/**
 * The slice of the Overview body this chart draws. Declared here rather than
 * taken whole, so what the chart reads is stated where it is read.
 */
export interface TimeseriesData {
	since: number;
	bucket_seconds: number;
	buckets: Bucket[];
}

export default function TimeseriesChart({ data }: { data: TimeseriesData }) {
	const [containerRef, width] = useMeasuredWidth();
	// A hover survives a re-render only while it still names the same bucket at
	// the same place: a poll that rolls the window, or a resize, retires it.
	const hovered = useActiveIndex(`${data.since}:${data.bucket_seconds}:${data.buckets.length}:${width}`);

	if (data.buckets.length === 0 || data.buckets.every((bucket) => bucket.queries === 0)) {
		return <EmptyChart containerRef={containerRef} />;
	}

	const columns = columnsOf(data.buckets);
	const timestamps = columns.map((column) => column.ts);
	const plot = plotArea(width);
	const xScale = bandScale(timestamps, plot);
	// The scale is the reported total rather than the stack's own sum: blocked
	// and cached are parts of `queries`, which a clamped `other` can undercount.
	const yScale = valueScale(Math.max(...columns.map((column) => column.queries)), [plot.bottom, plot.y]);
	const yTicks = valueTicks(yScale);

	return (
		<ChartRoot containerRef={containerRef}>
			<svg
				role="img"
				aria-label={`Queries over time, ${data.buckets.length} buckets: ${SERIES.map((series) => series.label.toLowerCase()).join(", ")} queries per bucket`}
				width="100%"
				height={CHART_HEIGHT}
				viewBox={`0 0 ${width} ${CHART_HEIGHT}`}
				onMouseLeave={hovered.clear}
			>
				<ChartFrame
					plot={plot}
					yScale={yScale}
					yTicks={yTicks}
					xScale={xScale}
					xTickValues={labelTickValues(timestamps, plot.width)}
					bucketSeconds={data.bucket_seconds}
				/>
				<BarStack<Column, SeriesKey>
					data={columns}
					keys={SERIES.map((series) => series.key)}
					x={(column) => column.ts}
					xScale={xScale}
					yScale={yScale}
					color={(key) => SERIES_COLOR[key]}
				>
					{(stacks) =>
						columns.map((column, index) => (
							<Group
								key={column.ts}
								opacity={hovered.index === null || hovered.index === index ? 1 : 0.55}
							>
								{stacks.map((stack) => {
									const bar = stack.bars[index];
									return (
										<StackSegment
											key={stack.key}
											x={bar.x}
											y={bar.y}
											width={bar.width}
											height={bar.height}
											fill={bar.color}
										/>
									);
								})}
							</Group>
						))
					}
				</BarStack>
				<BucketOverlay plot={plot} values={timestamps} xScale={xScale} onEnter={hovered.show} />
			</svg>
			{hovered.index !== null && (
				<ChartTooltip
					index={hovered.index}
					content={tooltipOf(columns[hovered.index])}
					left={slotCenter(xScale, timestamps[hovered.index], plot)}
				/>
			)}
			<ul {...stylex.props(styles.legend)}>
				{SERIES.map((series) => (
					<li key={series.key} {...stylex.props(styles.legendItem)}>
						<span aria-hidden="true" {...stylex.props(styles.swatch, styles.swatchColor(series.color))} />
						{series.label}
					</li>
				))}
			</ul>
			<div {...stylex.props(shared.srOnly)}>
				<table>
					<caption>Queries per time bucket</caption>
					<thead>
						<tr>
							<th scope="col">Time</th>
							<th scope="col">Queries</th>
							{SERIES.map((series) => (
								<th key={series.key} scope="col">
									{series.label}
								</th>
							))}
						</tr>
					</thead>
					<tbody>
						{columns.map((column) => (
							<tr key={column.ts}>
								<th scope="row">{formatTime(column.ts)}</th>
								<td>{column.queries}</td>
								{SERIES.map((series) => (
									<td key={series.key}>{column[series.key]}</td>
								))}
							</tr>
						))}
					</tbody>
				</table>
			</div>
		</ChartRoot>
	);
}
