<script lang="ts">
	import type { EventAttendance } from '$lib/statistics/aggregate';
	import { formatMonth } from '$lib/statistics/aggregate';

	interface Props {
		data: EventAttendance[];
	}

	let { data }: Props = $props();

	/*
	 * One series — unique players per event — so there is no legend: the heading
	 * above the chart already says what is plotted, and a one-swatch legend would
	 * just restate it. Columns rather than a line because the events are discrete
	 * monthly rankings; a line would imply attendance existed between them.
	 *
	 * Marks are painted with the theme's own accent token, which is a different
	 * (validated) step in light and dark, so the chart is not a flipped light chart.
	 * Axes and labels wear text/line tokens, never the data colour.
	 */

	// Rendered 1:1 against the measured container width: a viewBox that scales would
	// shrink the tick labels on a phone and blur the hairlines.
	let width = $state(880);
	const height = 260;
	const pad = { top: 14, right: 10, bottom: 28, left: 40 };

	let host: HTMLDivElement | undefined = $state();
	$effect(() => {
		if (!host || typeof ResizeObserver === 'undefined') return;
		const observer = new ResizeObserver((entries) => {
			const measured = entries[0]?.contentRect.width ?? 0;
			if (measured > 0) width = Math.max(320, measured);
		});
		observer.observe(host);
		return () => observer.disconnect();
	});

	const plot = $derived({
		x: pad.left,
		y: pad.top,
		w: Math.max(10, width - pad.left - pad.right),
		h: Math.max(10, height - pad.top - pad.bottom)
	});

	/** Clean axis ceiling — 0 / 20 / 40 rather than 0 / 17 / 34. */
	function niceTicks(max: number, wanted = 4): number[] {
		if (max <= 0) return [0, 1];
		const raw = max / wanted;
		const magnitude = 10 ** Math.floor(Math.log10(raw));
		const step = [1, 2, 2.5, 5, 10].map((m) => m * magnitude).find((s) => s >= raw) ?? magnitude * 10;
		const ticks: number[] = [];
		for (let value = 0; value <= max + step / 2; value += step) ticks.push(Math.round(value));
		return ticks;
	}

	const peak = $derived(Math.max(0, ...data.map((entry) => entry.players)));
	const ticks = $derived(niceTicks(peak));
	const scaleMax = $derived(ticks[ticks.length - 1] || 1);

	const slot = $derived(data.length > 0 ? plot.w / data.length : plot.w);
	/** Capped at 24px, and 2px of the gap is the surface showing between neighbours. */
	const barWidth = $derived(Math.max(2, Math.min(24, slot - 2)));

	interface Bar {
		entry: EventAttendance;
		x: number;
		y: number;
		w: number;
		h: number;
		slotX: number;
		label: string;
	}

	const bars = $derived<Bar[]>(
		data.map((entry, index) => {
			const barHeight = scaleMax === 0 ? 0 : (entry.players / scaleMax) * plot.h;
			const slotX = plot.x + index * slot;
			return {
				entry,
				x: slotX + (slot - barWidth) / 2,
				y: plot.y + plot.h - barHeight,
				w: barWidth,
				h: barHeight,
				slotX,
				label: formatMonth(entry.date) || entry.name
			};
		})
	);

	/** Square at the baseline, 4px rounded at the data end. */
	function columnPath(bar: Bar): string {
		const r = Math.min(4, bar.w / 2, bar.h);
		const base = bar.y + bar.h;
		if (r <= 0.5) return `M${bar.x} ${bar.y}h${bar.w}v${bar.h}h${-bar.w}Z`;
		return [
			`M${bar.x} ${base}`,
			`V${bar.y + r}`,
			`a${r} ${r} 0 0 1 ${r} ${-r}`,
			`h${bar.w - 2 * r}`,
			`a${r} ${r} 0 0 1 ${r} ${r}`,
			`V${base}`,
			'Z'
		].join(' ');
	}

	/** Label the ends plus a sparse interior: a label on every column is unreadable. */
	const labelEvery = $derived(Math.max(1, Math.ceil(data.length / Math.max(1, Math.floor(width / 90)))));
	function showLabel(index: number): boolean {
		if (data.length <= 1) return true;
		if (index === 0 || index === data.length - 1) return true;
		return index % labelEvery === 0 && index > 0 && index < data.length - 1;
	}

	let hovered = $state<number | null>(null);
	const active = $derived(hovered === null ? null : bars[hovered]);
</script>

<div bind:this={host} class="relative w-full">
	{#if data.length === 0}
		<p class="py-10 text-center text-sm text-subtle">No event in this scope.</p>
	{:else}
		<svg
			{width}
			{height}
			role="img"
			aria-label="Unique players per event, oldest first. The same numbers are in the table below."
			class="block"
		>
			<!-- Gridlines: hairline, solid, one step off the surface. -->
			{#each ticks as tick (tick)}
				{@const y = plot.y + plot.h - (scaleMax === 0 ? 0 : (tick / scaleMax) * plot.h)}
				<line
					x1={plot.x}
					x2={plot.x + plot.w}
					y1={y}
					y2={y}
					stroke="var(--app-line)"
					stroke-width="1"
					shape-rendering="crispEdges"
				/>
				<text
					x={plot.x - 8}
					{y}
					dy="0.32em"
					text-anchor="end"
					font-size="11"
					fill="var(--app-subtle)"
				>
					{tick}
				</text>
			{/each}

			{#each bars as bar, index (bar.entry.eventId)}
				<path
					d={columnPath(bar)}
					fill="var(--app-accent)"
					opacity={hovered === null || hovered === index ? 1 : 0.45}
				/>
			{/each}

			{#each bars as bar, index (bar.entry.eventId)}
				{#if showLabel(index)}
					<text
						x={bar.x + bar.w / 2}
						y={plot.y + plot.h + 18}
						text-anchor="middle"
						font-size="11"
						fill="var(--app-subtle)"
					>
						{bar.label}
					</text>
				{/if}
			{/each}

			<!-- Hit targets are the full slot height, so thin columns stay hoverable. -->
			{#each bars as bar, index (bar.entry.eventId)}
				<rect
					x={bar.slotX}
					y={plot.y}
					width={Math.max(slot, 6)}
					height={plot.h}
					fill="transparent"
					onmouseenter={() => (hovered = index)}
					onmouseleave={() => (hovered = null)}
					role="presentation"
				/>
			{/each}
		</svg>

		{#if active}
			<div
				class="pointer-events-none absolute z-10 w-44 rounded-lg border border-line bg-overlay p-2 text-xs shadow-card"
				style="left: {Math.min(
					Math.max(active.x + active.w / 2 - 88, 0),
					Math.max(width - 176, 0)
				)}px; top: {Math.max(active.y - 92, 0)}px"
			>
				<p class="font-semibold text-ink">{active.entry.name}</p>
				{#if formatMonth(active.entry.date)}
					<p class="text-subtle">{formatMonth(active.entry.date)}</p>
				{/if}
				<dl class="mt-1 space-y-0.5 text-muted">
					<div class="flex justify-between gap-2">
						<dt>Players</dt>
						<dd class="font-semibold text-ink">{active.entry.players}</dd>
					</div>
					<div class="flex justify-between gap-2">
						<dt>Entries</dt>
						<dd>{active.entry.entries}</dd>
					</div>
					<div class="flex justify-between gap-2">
						<dt>Brackets</dt>
						<dd>{active.entry.brackets}</dd>
					</div>
				</dl>
			</div>
		{/if}
	{/if}
</div>
