import { useEffect, useRef, useState } from "react";
import * as stylex from "@stylexjs/stylex";
import { formatTime } from "@/lib/format";
import type { StatsTimeseries } from "@/lib/types";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
import { isEmptyTimeseries, layoutTimeseries, type BarLayout } from "./chartLayout";

// 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).
const SERIES = [
	{ key: "blocked", label: "Blocked", color: "#ef4444" },
	{ key: "cached", label: "Cached", color: "#059669" },
	{ key: "other", label: "Other", color: "#3b82f6" },
] as const;

const CHART_HEIGHT = 240;
const FALLBACK_WIDTH = 640;

const styles = stylex.create({
	empty: {
		display: "flex",
		alignItems: "center",
		justifyContent: "center",
		height: CHART_HEIGHT,
		borderRadius: "0.25rem",
		borderWidth: 1,
		borderStyle: "dashed",
		borderColor: colors.borderStrong,
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.textMuted,
	},
	chartRoot: {
		position: "relative",
	},
	tooltip: {
		pointerEvents: "none",
		position: "absolute",
		top: "0.5rem",
		zIndex: 10,
		borderRadius: "0.25rem",
		borderWidth: 1,
		borderStyle: "solid",
		borderColor: colors.borderStrong,
		backgroundColor: colors.surfaceRaised,
		paddingInline: "0.75rem",
		paddingBlock: "0.5rem",
		fontSize: "0.75rem",
		lineHeight: "1rem",
		boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
	},
	/** Dynamic: the tooltip flips to whichever side of the bar has room. */
	tooltipLeft: (left: number) => ({ left, right: null }),
	tooltipRight: (right: number) => ({ left: null, right }),
	tooltipTitle: {
		fontWeight: 500,
	},
	tooltipList: {
		display: "flex",
		flexDirection: "column",
		gap: "0.125rem",
		marginTop: "0.25rem",
	},
	tooltipRow: {
		display: "flex",
		alignItems: "center",
		justifyContent: "space-between",
		gap: "1rem",
	},
	tooltipTerm: {
		display: "flex",
		alignItems: "center",
		gap: "0.375rem",
		color: colors.textMuted,
	},
	swatch: {
		display: "inline-block",
		borderRadius: "0.125rem",
	},
	/** Dynamic: the swatch takes the series colour the SVG bars are drawn in. */
	swatchColor: (color: string) => ({ backgroundColor: color }),
	swatchSmall: {
		width: "0.5rem",
		height: "0.5rem",
	},
	swatchLarge: {
		width: "0.625rem",
		height: "0.625rem",
	},
	gridLine: {
		stroke: colors.border,
	},
	axisLine: {
		stroke: colors.borderStrong,
	},
	axisLabel: {
		fill: colors.textMuted,
		fontSize: "10px",
	},
	/** The hairline separating touching segments is the page ground, not a colour. */
	segment: {
		stroke: colors.surface,
	},
	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",
	},
});

function useContainerWidth(): [React.RefObject<HTMLDivElement | null>, number] {
	const ref = useRef<HTMLDivElement>(null);
	const [width, setWidth] = useState(0);
	useEffect(() => {
		const el = ref.current;
		if (el === null) return;
		setWidth(el.clientWidth);
		if (typeof ResizeObserver === "undefined") return;
		const observer = new ResizeObserver(() => setWidth(el.clientWidth));
		observer.observe(el);
		return () => observer.disconnect();
	}, []);
	return [ref, width];
}

const compact = new Intl.NumberFormat(undefined, { notation: "compact" });

function formatTick(ts: number, bucketSeconds: number): string {
	const date = new Date(ts * 1000);
	if (bucketSeconds >= 86_400) {
		return new Intl.DateTimeFormat(undefined, { month: "short", day: "numeric" }).format(date);
	}
	return new Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "2-digit" }).format(date);
}

function barSummary(bar: BarLayout): string {
	return `${formatTime(bar.bucket.ts)}: ${bar.bucket.queries} queries, ${bar.bucket.blocked} blocked, ${bar.bucket.cached} cached`;
}

function Tooltip({ bar, chartWidth }: { bar: BarLayout; chartWidth: number }) {
	const centerX = bar.slot.x + bar.slot.width / 2;
	const leftHalf = centerX < chartWidth / 2;
	const side = leftHalf
		? styles.tooltipLeft(Math.min(centerX + 8, chartWidth - 160))
		: styles.tooltipRight(chartWidth - centerX + 8);
	return (
		<div {...stylex.props(styles.tooltip, side)}>
			<div {...stylex.props(styles.tooltipTitle)}>{formatTime(bar.bucket.ts)}</div>
			<dl {...stylex.props(styles.tooltipList)}>
				<div {...stylex.props(styles.tooltipRow)}>
					<dt {...stylex.props(styles.tooltipTerm)}>Queries</dt>
					<dd {...stylex.props(shared.tabularNums)}>{bar.bucket.queries}</dd>
				</div>
				{SERIES.map((series) => (
					<div key={series.key} {...stylex.props(styles.tooltipRow)}>
						<dt {...stylex.props(styles.tooltipTerm)}>
							<span
								aria-hidden="true"
								{...stylex.props(styles.swatch, styles.swatchSmall, styles.swatchColor(series.color))}
							/>
							{series.label}
						</dt>
						<dd {...stylex.props(shared.tabularNums)}>
							{series.key === "other" ? bar.other : bar.bucket[series.key]}
						</dd>
					</div>
				))}
			</dl>
		</div>
	);
}

export default function TimeseriesChart({ data }: { data: StatsTimeseries }) {
	const [containerRef, measuredWidth] = useContainerWidth();
	const [hovered, setHovered] = useState<number | null>(null);
	const width = measuredWidth > 0 ? measuredWidth : FALLBACK_WIDTH;

	if (data.buckets.length === 0 || isEmptyTimeseries(data.buckets)) {
		return (
			<div ref={containerRef} {...stylex.props(styles.empty)}>
				No queries in this period.
			</div>
		);
	}

	const layout = layoutTimeseries(data.buckets, width, CHART_HEIGHT);
	const baseline = layout.plot.y + layout.plot.height;
	const hoveredBar = hovered !== null ? layout.bars[hovered] : undefined;

	return (
		<div ref={containerRef} {...stylex.props(styles.chartRoot)}>
			<svg
				role="img"
				aria-label={`Queries over time, ${data.buckets.length} buckets: blocked, cached and other queries per bucket`}
				width="100%"
				height={CHART_HEIGHT}
				viewBox={`0 0 ${width} ${CHART_HEIGHT}`}
				onMouseLeave={() => setHovered(null)}
			>
				{layout.yTicks.map((tick) => (
					<g key={tick.value}>
						<line
							x1={layout.plot.x}
							x2={layout.plot.x + layout.plot.width}
							y1={tick.y}
							y2={tick.y}
							{...stylex.props(styles.gridLine)}
						/>
						<text
							x={layout.plot.x - 6}
							y={tick.y}
							textAnchor="end"
							dominantBaseline="middle"
							{...stylex.props(styles.axisLabel, shared.tabularNums)}
						>
							{compact.format(tick.value)}
						</text>
					</g>
				))}
				<line
					x1={layout.plot.x}
					x2={layout.plot.x + layout.plot.width}
					y1={baseline}
					y2={baseline}
					{...stylex.props(styles.axisLine)}
				/>
				{layout.xTicks.map((tick) => (
					<text
						key={tick.ts}
						x={tick.x}
						y={baseline + 14}
						textAnchor="middle"
						{...stylex.props(styles.axisLabel)}
					>
						{formatTick(tick.ts, data.bucket_seconds)}
					</text>
				))}
				{layout.bars.map((bar, i) => (
					<g key={bar.bucket.ts} opacity={hovered === null || hovered === i ? 1 : 0.55}>
						{SERIES.map((series) => {
							const rect = bar.segments[series.key];
							if (rect.height <= 0) return null;
							return (
								<rect
									key={series.key}
									x={rect.x}
									y={rect.y}
									width={rect.width}
									height={rect.height}
									fill={series.color}
									strokeWidth={rect.width > 3 ? 1 : 0}
									{...stylex.props(styles.segment)}
								/>
							);
						})}
					</g>
				))}
				{layout.bars.map((bar, i) => (
					<rect
						key={bar.bucket.ts}
						x={bar.slot.x}
						y={bar.slot.y}
						width={bar.slot.width}
						height={bar.slot.height}
						fill="transparent"
						onMouseEnter={() => setHovered(i)}
					>
						<title>{barSummary(bar)}</title>
					</rect>
				))}
			</svg>
			{hoveredBar !== undefined && <Tooltip bar={hoveredBar} chartWidth={width} />}
			<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.swatchLarge, styles.swatchColor(series.color))}
						/>
						{series.label}
					</li>
				))}
			</ul>
			<table {...stylex.props(shared.srOnly)}>
				<caption>Queries per time bucket</caption>
				<thead>
					<tr>
						<th scope="col">Time</th>
						<th scope="col">Queries</th>
						<th scope="col">Blocked</th>
						<th scope="col">Cached</th>
						<th scope="col">Other</th>
					</tr>
				</thead>
				<tbody>
					{layout.bars.map((bar) => (
						<tr key={bar.bucket.ts}>
							<th scope="row">{formatTime(bar.bucket.ts)}</th>
							<td>{bar.bucket.queries}</td>
							<td>{bar.bucket.blocked}</td>
							<td>{bar.bucket.cached}</td>
							<td>{bar.other}</td>
						</tr>
					))}
				</tbody>
			</table>
		</div>
	);
}
