/**
 * Activity in live mode: the SSE stream, its bounded ring buffer, and the modal
 * detail a streamed row opens.
 *
 * This subtree is mounted only while the URL says `mode=live`, which is what
 * closes the EventSource on the way back to history: the connection is a
 * server-side resource capped per address, so a page that kept it open while
 * showing something else would spend a viewer slot on nothing.
 *
 * Freeze and Follow are display state and stay out of the URL. They describe
 * what the screen is doing right now, not what it is showing, so a shared link
 * would carry a frozen moment the recipient never saw fill.
 */

import { useEffect, useRef, useState } from "react";
import { Link } from "@tanstack/react-router";
import * as stylex from "@stylexjs/stylex";
import { useClientNames } from "@/features/clients/clientNames";
import { summarizeEvent } from "@/features/provenance/querySummary";
import Dialog from "@/ui/Dialog";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
import { ActivityCells, ActivityTableHead, activityDomainLink } from "./cells";
import ProvenanceDetail from "./ProvenanceDetail";
import RelatedActions from "./RelatedActions";
import { RING_CAPACITY, summaryOf, type StreamedRow } from "./ringBuffer";
import type { ActivitySearch } from "./search";
import { useLiveQueries, type StreamStatus } from "./useLiveQueries";

const DARK = "@media (prefers-color-scheme: dark)";

const styles = stylex.create({
	toolbar: {
		marginTop: "1rem",
		display: "flex",
		flexWrap: "wrap",
		alignItems: "center",
		gap: "0.75rem",
	},
	toolbarButton: {
		fontWeight: 500,
	},
	pill: {
		borderRadius: "9999px",
		paddingInline: "0.625rem",
		paddingBlock: "0.125rem",
		fontSize: "0.75rem",
		lineHeight: "1rem",
		fontWeight: 500,
	},
	/** Four stream states need four tints; only two of them map onto a token role. */
	pillConnecting: {
		backgroundColor: { default: "oklch(96.7% 0.001 286.375)", [DARK]: "oklch(27.4% 0.006 286.033)" },
		color: { default: "oklch(37% 0.013 285.805)", [DARK]: "oklch(87.1% 0.006 286.286)" },
	},
	pillOpen: {
		backgroundColor: { default: "oklch(96.2% 0.044 156.743)", [DARK]: "oklch(39.3% 0.095 152.535)" },
		color: { default: "oklch(44.8% 0.119 151.328)", [DARK]: "oklch(92.5% 0.084 155.995)" },
	},
	pillRetrying: {
		backgroundColor: { default: "oklch(96.2% 0.059 95.617)", [DARK]: "oklch(41.4% 0.112 45.904)" },
		color: { default: "oklch(47.3% 0.137 46.201)", [DARK]: "oklch(92.4% 0.12 95.746)" },
	},
	pillCapped: {
		backgroundColor: { default: "oklch(93.6% 0.032 17.717)", [DARK]: "oklch(39.6% 0.141 25.723)" },
		color: { default: "oklch(44.4% 0.177 26.899)", [DARK]: "oklch(88.5% 0.062 18.334)" },
	},
	note: {
		marginTop: "0.5rem",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.textMuted,
	},
	/** Informational, neither a warning nor a failure, so the blue ramp stands alone. */
	resumed: {
		marginTop: "0.75rem",
		display: "flex",
		alignItems: "center",
		gap: "0.75rem",
		borderRadius: "0.25rem",
		borderWidth: 1,
		borderStyle: "solid",
		borderColor: { default: "oklch(80.9% 0.105 251.813)", [DARK]: "oklch(37.9% 0.146 265.522)" },
		backgroundColor: { default: "oklch(97% 0.014 254.604)", [DARK]: "oklch(28.2% 0.091 267.935)" },
		color: { default: "oklch(42.4% 0.199 265.638)", [DARK]: "oklch(88.2% 0.059 254.128)" },
		paddingInline: "0.75rem",
		paddingBlock: "0.5rem",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
	},
	dismiss: {
		cursor: { default: "pointer", ":disabled": "not-allowed" },
		borderStyle: "none",
		backgroundColor: "transparent",
		padding: 0,
		color: "inherit",
		fontSize: "inherit",
		fontWeight: 500,
		textDecorationLine: "underline",
	},
	failureNote: {
		marginTop: "0.75rem",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.dangerText,
	},
	cappedBox: {
		marginTop: "1rem",
		borderRadius: "0.25rem",
		borderWidth: 1,
		borderStyle: "solid",
		borderColor: colors.dangerBorder,
		backgroundColor: colors.dangerSurface,
		padding: "1rem",
	},
	cappedHeading: {
		fontWeight: 600,
		color: colors.dangerText,
	},
	cappedDetail: {
		marginTop: "0.25rem",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.dangerText,
	},
	empty: {
		marginTop: "1.5rem",
		color: colors.textMuted,
	},
	tableWrap: {
		marginTop: "1rem",
		overflowX: "auto",
		borderRadius: "0.25rem",
		borderWidth: 1,
		borderStyle: "solid",
		borderColor: colors.border,
	},
	table: {
		width: "100%",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
	},
	/** `divide-y`: a hairline between rows, so the first row carries none. */
	row: {
		borderTopWidth: { default: 1, ":first-child": 0 },
		borderTopStyle: "solid",
		borderTopColor: colors.border,
	},
	rowBlocked: {
		backgroundColor: {
			default: "oklch(97.1% 0.013 17.38)",
			[DARK]: "oklch(25.8% 0.092 26.042 / 0.4)",
		},
	},
	/** A streamed row opens its detail here rather than at a route, so it is a button that looks like the link beside it. */
	domainButton: {
		borderStyle: "none",
		backgroundColor: "transparent",
		padding: 0,
		font: "inherit",
		textAlign: "left",
		cursor: "pointer",
		textDecorationLine: "underline",
		textDecorationStyle: "dotted",
	},
	footnote: {
		marginTop: "0.75rem",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.textMuted,
	},
});

const PILL_LABELS: Record<StreamStatus, string> = {
	connecting: "Connecting…",
	open: "Live",
	retrying: "Reconnecting…",
	capped: "Disconnected",
};

function pillStyle(status: StreamStatus) {
	if (status === "open") return styles.pillOpen;
	if (status === "retrying") return styles.pillRetrying;
	if (status === "capped") return styles.pillCapped;
	return styles.pillConnecting;
}

function StatusPill({ status }: { status: StreamStatus }) {
	const label = PILL_LABELS[status];
	return (
		<span role="status" aria-label={label} {...stylex.props(styles.pill, pillStyle(status))}>
			{label}
		</span>
	);
}

/**
 * The open detail, held as the selected row itself rather than as a key into
 * the buffer.
 *
 * The buffer is a 500-row ring that a gap merge also rewrites: a reference by
 * key would go stale under the reader while they were still reading it, and the
 * dialog would blank out or swap under their eyes for no reason they could see.
 * The snapshot is the whole fact — a streamed frame carries its own provenance
 * — so it survives eviction, a merge and a Freeze/Resume, and closes only when
 * the reader closes it or leaves live mode. The stream behind it never stops.
 */
function LiveDetail({ row, origin, onClose }: { row: StreamedRow; origin: ActivitySearch; onClose: () => void }) {
	const summary = summarizeEvent(row.event);
	return (
		<Dialog title="Streamed query" size="detail" isOpen onClose={onClose}>
			<ProvenanceDetail
				provenance={row.event}
				persistedId={null}
				relatedActions={
					<RelatedActions
						domain={summary.domain}
						client={summary.client_ip}
						ts={summary.ts}
						origin={origin}
					/>
				}
			/>
		</Dialog>
	);
}

/**
 * `capacity` is the ring size, a parameter only so a test can provoke an
 * eviction with a handful of rows rather than 500 frames through React state.
 * The route renders this without it, so the app is always the 500-row ring.
 */
export default function LiveActivity({
	origin,
	capacity = RING_CAPACITY,
}: {
	origin: ActivitySearch;
	capacity?: number;
}) {
	const live = useLiveQueries({ capacity });
	const clientNames = useClientNames();
	const [selected, setSelected] = useState<StreamedRow | null>(null);
	const trigger = useRef<HTMLButtonElement | null>(null);
	const results = useRef<HTMLDivElement>(null);
	const restoring = useRef(false);

	/**
	 * Where focus lands when the dialog closes.
	 *
	 * React Aria restores focus itself, but in a `requestAnimationFrame` and
	 * only while focus is still on the body — and its target is the row button,
	 * which the ring may have evicted while the reader was reading. So this runs
	 * in the effect that follows the focus scope's teardown and puts focus on a
	 * connected element first: the row if it is still there, the results region
	 * if it is not. React Aria's deferred pass then finds focus already placed
	 * and does nothing, so the two never fight over it. If a navigation unmounts
	 * this component the effect never runs, which is the right answer — there is
	 * no longer a table to return to.
	 */
	useEffect(() => {
		if (selected !== null || !restoring.current) return;
		restoring.current = false;
		const from = trigger.current;
		trigger.current = null;
		(from?.isConnected === true ? from : results.current)?.focus();
	}, [selected]);

	function open(row: StreamedRow, from: HTMLButtonElement) {
		trigger.current = from;
		restoring.current = true;
		setSelected(row);
	}

	return (
		<>
			<div {...stylex.props(styles.toolbar)}>
				<StatusPill status={live.status} />
				<button
					type="button"
					onClick={live.toggleFreeze}
					aria-pressed={live.frozen}
					{...stylex.props(shared.button, styles.toolbarButton, shared.focusRing)}
				>
					{live.frozen ? "Resume" : "Freeze"}
				</button>
			</div>

			{live.frozen && (
				<p {...stylex.props(styles.note)} role="status">
					Display frozen — new queries keep buffering ({live.liveCount} in buffer, newest {capacity} kept).
				</p>
			)}

			{live.missed !== null && (
				<div role="status" {...stylex.props(styles.resumed)}>
					<span>
						Stream resumed —{" "}
						{live.missed === 0 ? "no queries missed" : `${live.missed} missed queries recovered`}.
					</span>
					<button
						type="button"
						onClick={live.dismissMissed}
						{...stylex.props(styles.dismiss, shared.focusRing)}
					>
						Dismiss
					</button>
				</div>
			)}

			{live.resyncFailed && (
				<p role="alert" {...stylex.props(styles.failureNote)}>
					Stream resumed, but re-syncing the gap failed — some queries may be missing here.
				</p>
			)}

			{live.status === "capped" && (
				<div role="alert" {...stylex.props(styles.cappedBox)}>
					<h2 {...stylex.props(styles.cappedHeading)}>Live stream unavailable</h2>
					<p {...stylex.props(styles.cappedDetail)}>
						The connection failed repeatedly — possibly too many live viewers (the server caps streams per
						address), or the server is unreachable.
					</p>
					<button type="button" onClick={live.retry} {...stylex.props(shared.retryButton, shared.focusRing)}>
						Retry
					</button>
				</div>
			)}

			{/*
			 * The region is the anchor focus falls back to when the row that
			 * opened the dialog is gone, so it is rendered unconditionally: an
			 * anchor that disappears with the last row is no anchor at all.
			 */}
			<div
				ref={results}
				tabIndex={-1}
				role="region"
				aria-label="Live queries"
				{...stylex.props(shared.focusRing)}
			>
				{live.rows.length === 0 ? (
					live.status !== "capped" && (
						<p {...stylex.props(styles.empty)}>
							{live.status === "open" ? "Waiting for queries…" : "No queries received yet."}
						</p>
					)
				) : (
					<>
						<div {...stylex.props(styles.tableWrap)}>
							<table {...stylex.props(styles.table)}>
								<ActivityTableHead />
								<tbody>
									{live.rows.map((row) => {
										const summary = summaryOf(row);
										return (
											<tr
												key={row.key}
												{...stylex.props(styles.row, summary.blocked && styles.rowBlocked)}
											>
												<ActivityCells
													row={summary}
													clientNames={clientNames}
													renderDomain={(_id, children) =>
														row.kind === "streamed" ? (
															<button
																type="button"
																aria-haspopup="dialog"
																onClick={(event) => open(row, event.currentTarget)}
																{...stylex.props(
																	styles.domainButton,
																	activityDomainLink,
																	shared.focusRing,
																)}
															>
																{children}
															</button>
														) : (
															<Link
																to="/activity/queries/$id"
																params={{ id: String(row.row.id) }}
																search={origin}
																{...stylex.props(activityDomainLink, shared.focusRing)}
															>
																{children}
															</Link>
														)
													}
												/>
											</tr>
										);
									})}
								</tbody>
							</table>
						</div>
						<p {...stylex.props(styles.footnote)}>
							Showing {live.rows.length} {live.rows.length === 1 ? "query" : "queries"} (newest first,
							last {capacity} kept).
						</p>
					</>
				)}
			</div>

			{selected !== null && <LiveDetail row={selected} origin={origin} onClose={() => setSelected(null)} />}
		</>
	);
}
