/**
 * Single dispatch point from a detected event to its card component, shared
 * by the live feed and the VoD feed. Frames are loaded lazily through
 * `getFrame` (IndexedDB keeps them out of the listed records); the Inspect
 * action (open the frame in the screenshot page in a new browser tab, so
 * the running scan is left undisturbed) is derived from it here so pages
 * don't duplicate the wiring.
 */

import clsx from "clsx";
import { SCANNER_PAGE } from "~/utils/urls";
import type { PlayerAbilityMap } from "../core/ability-harvest";
import {
	DEATH_EVENT_TYPE,
	type DeathData,
} from "../core/detectors/death/index";
import {
	MAP_START_EVENT_TYPE,
	type MapStartData,
} from "../core/detectors/map-start/index";
import {
	MINIMAP_EVENT_TYPE,
	type MinimapData,
} from "../core/detectors/minimap/index";
import {
	OBJECTIVE_EVENT_TYPE,
	type ObjectiveData,
} from "../core/detectors/objective/index";
import type { ScoreboardData } from "../core/detectors/scoreboard/index";
import {
	SCOREBOARD_OWN_EVENT_TYPE,
	type ScoreboardOwnData,
} from "../core/detectors/scoreboard-own/index";
import { scannerSearchParams } from "../scanner-search-params";
import type { SendStatus } from "../store/events";
import { newInspectKey, putInspectFrame } from "../store/inspect";
import { DeathCard } from "./DeathCard";
import type { FixtureData } from "./fixture-export";
import { useEventTimeFormatter } from "./format";
import { MapStartCard } from "./MapStartCard";
import { MinimapCard } from "./MinimapCard";
import { ObjectiveCard } from "./ObjectiveCard";
import { ScoreboardCard } from "./ScoreboardCard";
import { ScoreboardOwnCard } from "./ScoreboardOwnCard";

export type GetFrame = () => Promise<Blob | null | undefined>;

export function EventCard(props: {
	type: string;
	t: number;
	confidence: number;
	data: FixtureData;
	thumbnail?: string;
	detectedAt?: number;
	/** lazy loader for the exact analyzed frame; enables Inspect + fixture export */
	getFrame?: GetFrame;
	/** Scoreboard only: abilities harvested from the match's death events */
	abilities?: PlayerAbilityMap;
	/** sendou.ink /ingest status of this event; absent = never attempted */
	send?: SendStatus;
	/** when set, shows a Send/Retry button that sends this event's match batch */
	onSend?: () => void;
}) {
	const { type, t, confidence, data, thumbnail, detectedAt, getFrame } = props;
	// window.open must run synchronously in the click gesture (popup blockers);
	// the frame write catches up and the new tab polls for it
	const onInspect = getFrame
		? () => {
				const key = newInspectKey();
				window.open(
					scannerSearchParams.href(SCANNER_PAGE, {
						tab: "screenshot",
						inspect: key,
					}),
					"_blank",
				);
				void getFrame().then((frame) => {
					if (frame) void putInspectFrame(key, frame);
				});
			}
		: undefined;
	const shared = { t, confidence, thumbnail, detectedAt, getFrame, onInspect };

	const card = renderCard(type, data, shared, props.abilities);
	if (!props.send && !props.onSend) return card;
	return (
		<div className="send-wrap">
			{card}
			<SendStrip send={props.send} onSend={props.onSend} />
		</div>
	);
}

const SEND_LABELS: Record<SendStatus["state"], string> = {
	queued: "queued",
	sending: "sending…",
	sent: "sent",
	unlinked: "waiting for report",
	failed: "failed",
};

function SendStrip({
	send,
	onSend,
}: {
	send?: SendStatus;
	onSend?: () => void;
}) {
	const state = send?.state;
	const formatSentAt = useEventTimeFormatter();
	return (
		<div className={clsx("send-strip", state ?? "unsent")}>
			<span>
				sendou.ink: {state ? SEND_LABELS[state] : "not sent"}
				{state === "sent" && send ? ` ${formatSentAt(send.at)}` : null}
			</span>
			{send?.error ? <span className="error">{send.error}</span> : null}
			{onSend && state !== "sent" && state !== "sending" ? (
				<button type="button" onClick={onSend}>
					{state === "failed" ? "Retry" : "Send"}
				</button>
			) : null}
		</div>
	);
}

function renderCard(
	type: string,
	data: FixtureData,
	shared: {
		t: number;
		confidence: number;
		thumbnail?: string;
		detectedAt?: number;
		getFrame?: GetFrame;
		onInspect?: () => void;
	},
	abilities?: PlayerAbilityMap,
) {
	return type === DEATH_EVENT_TYPE ? (
		<DeathCard {...shared} data={data as DeathData} />
	) : type === MAP_START_EVENT_TYPE ? (
		<MapStartCard {...shared} data={data as MapStartData} />
	) : type === SCOREBOARD_OWN_EVENT_TYPE ? (
		<ScoreboardOwnCard {...shared} data={data as ScoreboardOwnData} />
	) : type === MINIMAP_EVENT_TYPE ? (
		<MinimapCard {...shared} data={data as MinimapData} />
	) : type === OBJECTIVE_EVENT_TYPE ? (
		<ObjectiveCard {...shared} data={data as ObjectiveData} />
	) : (
		<ScoreboardCard
			{...shared}
			eventType={type}
			data={data as ScoreboardData}
			abilities={abilities}
		/>
	);
}
