import * as React from "react";
import type { AgentConfigInfo } from "../api/agents";
import type { AgentAssignment, PipelineStageItem } from "../api/client";
import { useLozengeFly } from "./LozengeFlyContext";

const { useLayoutEffect, useRef, useState } = React;

/** Renders merge-failure gate output in a bounded scroll region with expand and copy controls. */
function GateOutputBox({ text }: { text: string }) {
	const [expanded, setExpanded] = useState(false);
	const [copied, setCopied] = useState(false);

	const handleToggle = (e: React.MouseEvent) => {
		e.stopPropagation();
		setExpanded((prev) => !prev);
	};

	const handleCopy = (e: React.MouseEvent) => {
		e.stopPropagation();
		navigator.clipboard.writeText(text).then(() => {
			setCopied(true);
			setTimeout(() => setCopied(false), 1500);
		});
	};

	const btnStyle: React.CSSProperties = {
		background: "transparent",
		border: "1px solid #444",
		borderRadius: "4px",
		color: "#aaa",
		cursor: "pointer",
		fontSize: "0.75em",
		padding: "1px 6px",
		lineHeight: 1.4,
	};

	return (
		<div style={{ marginTop: "4px" }}>
			<div
				data-testid="gate-output-text"
				style={{
					fontSize: "0.8em",
					color: "#f85149",
					whiteSpace: "pre-wrap",
					wordBreak: "break-word",
					fontFamily: "monospace",
					background: "#1a0808",
					borderRadius: "4px",
					padding: "6px 8px",
					maxHeight: expanded ? "none" : "10rem",
					overflowY: expanded ? "visible" : "auto",
				}}
			>
				{text}
			</div>
			<div
				style={{
					display: "flex",
					gap: "6px",
					marginTop: "4px",
				}}
			>
				<button
					type="button"
					data-testid="gate-output-toggle"
					onClick={handleToggle}
					style={btnStyle}
				>
					{expanded ? "▲ Collapse" : "▼ Expand"}
				</button>
				<button
					type="button"
					data-testid="gate-output-copy"
					onClick={handleCopy}
					style={btnStyle}
				>
					{copied ? "✓ Copied" : "⎘ Copy"}
				</button>
			</div>
		</div>
	);
}

type WorkItemType = "story" | "bug" | "spike" | "refactor" | "unknown";

const TYPE_COLORS: Record<WorkItemType, string> = {
	story: "#3fb950",
	bug: "#f85149",
	spike: "#58a6ff",
	refactor: "#a371f7",
	unknown: "#444",
};

const TYPE_LABELS: Record<WorkItemType, string | null> = {
	story: "STORY",
	bug: "BUG",
	spike: "SPIKE",
	refactor: "REFACTOR",
	unknown: null,
};

function getWorkItemType(storyId: string): WorkItemType {
	const match = storyId.match(/^\d+_([a-z]+)_/);
	if (!match) return "unknown";
	const segment = match[1];
	if (
		segment === "story" ||
		segment === "bug" ||
		segment === "spike" ||
		segment === "refactor"
	) {
		return segment;
	}
	return "unknown";
}

interface StagePanelProps {
	title: string;
	items: PipelineStageItem[];
	emptyMessage?: string;
	onItemClick?: (item: PipelineStageItem) => void;
	onStopAgent?: (storyId: string, agentName: string) => void;
	onDeleteItem?: (item: PipelineStageItem) => void;
	/** Map of story_id → total_cost_usd for displaying cost badges. */
	costs?: Map<string, number>;
	/** Agent roster to populate the start agent dropdown. */
	agentRoster?: AgentConfigInfo[];
	/** Names of agents currently running/pending (busy). */
	busyAgentNames?: Set<string>;
	/** Called when the user requests to start an agent on a story. */
	onStartAgent?: (storyId: string, agentName?: string) => void;
	/** Set of story IDs that currently have a deterministic merge in progress. */
	mergesInFlight?: Set<string>;
	/** True when this panel shows merge-stage items — enables the mergemaster robot icon. */
	isMergeStage?: boolean;
}

function AgentLozenge({
	agent,
	storyId,
	onStop,
}: {
	agent: AgentAssignment;
	storyId: string;
	onStop?: () => void;
}) {
	const { saveSlotRect, pendingFlyIns } = useLozengeFly();
	const lozengeRef = useRef<HTMLDivElement>(null);
	const isRunning = agent.status === "running";
	const isPending = agent.status === "pending";
	const color = isRunning ? "#3fb950" : isPending ? "#e3b341" : "#aaa";
	const label = agent.model
		? `${agent.agent_name} ${agent.model}`
		: agent.agent_name;

	const isFlyingIn = pendingFlyIns.has(storyId);

	// Save our rect on every render so flyOut can reference it after unmount
	useLayoutEffect(() => {
		if (lozengeRef.current) {
			saveSlotRect(storyId, lozengeRef.current.getBoundingClientRect());
		}
	});

	return (
		<div
			ref={lozengeRef}
			className="agent-lozenge"
			data-testid={`slot-lozenge-${storyId}`}
			style={{
				display: "inline-flex",
				alignItems: "center",
				gap: "5px",
				padding: "2px 8px",
				borderRadius: "999px",
				fontSize: "0.72em",
				fontWeight: 600,
				background: `${color}18`,
				color,
				border: `1px solid ${color}44`,
				marginTop: "4px",
				// Fixed intrinsic width – never stretches to fill parent panel
				alignSelf: "flex-start",
				// Hidden during fly-in; revealed with a fade once the clone arrives
				opacity: isFlyingIn ? 0 : 1,
				transition: isFlyingIn ? "none" : "opacity 0.15s",
				animation: isFlyingIn ? "none" : "agentAppear 0.3s ease-out",
			}}
		>
			{isRunning && (
				<span
					style={{
						width: "5px",
						height: "5px",
						borderRadius: "50%",
						background: color,
						animation: "pulse 1.5s infinite",
						flexShrink: 0,
					}}
				/>
			)}
			{isPending && (
				<span
					style={{
						width: "5px",
						height: "5px",
						borderRadius: "50%",
						background: color,
						opacity: 0.7,
						flexShrink: 0,
					}}
				/>
			)}
			{label}
			{isRunning && onStop && (
				<button
					type="button"
					data-testid={`stop-agent-${storyId}`}
					onClick={(e) => {
						e.stopPropagation();
						onStop();
					}}
					title="Stop agent"
					style={{
						marginLeft: "4px",
						padding: "0 3px",
						background: "transparent",
						border: "none",
						color,
						cursor: "pointer",
						fontSize: "0.9em",
						lineHeight: 1,
						opacity: 0.8,
						flexShrink: 0,
					}}
				>
					■
				</button>
			)}
		</div>
	);
}

function StartAgentControl({
	storyId,
	agentRoster,
	busyAgentNames,
	onStartAgent,
}: {
	storyId: string;
	agentRoster: AgentConfigInfo[];
	busyAgentNames: Set<string>;
	onStartAgent: (storyId: string, agentName?: string) => void;
}) {
	const [selectedAgent, setSelectedAgent] = useState<string>("");

	const allBusy =
		agentRoster.length > 0 &&
		agentRoster.every((a) => busyAgentNames.has(a.name));

	const handleStart = (e: React.MouseEvent) => {
		e.stopPropagation();
		onStartAgent(storyId, selectedAgent || undefined);
	};

	const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
		e.stopPropagation();
		setSelectedAgent(e.target.value);
	};

	return (
		<div
			style={{
				display: "flex",
				gap: "4px",
				marginTop: "6px",
				alignItems: "center",
			}}
		>
			{agentRoster.length > 1 && (
				<select
					value={selectedAgent}
					onChange={handleSelectChange}
					disabled={allBusy}
					data-testid={`start-agent-select-${storyId}`}
					style={{
						background: "#2a2a2a",
						color: allBusy ? "#555" : "#ccc",
						border: "1px solid #444",
						borderRadius: "5px",
						padding: "2px 4px",
						fontSize: "0.75em",
						cursor: allBusy ? "not-allowed" : "pointer",
						flex: 1,
						minWidth: 0,
					}}
				>
					<option value="">Default agent</option>
					{agentRoster.map((a) => (
						<option key={a.name} value={a.name}>
							{a.name}
						</option>
					))}
				</select>
			)}
			<button
				type="button"
				onClick={handleStart}
				disabled={allBusy}
				data-testid={`start-agent-btn-${storyId}`}
				title={allBusy ? "All agents are busy" : "Start a coder on this story"}
				style={{
					background: allBusy ? "#1a1a1a" : "#1a3a1a",
					color: allBusy ? "#555" : "#3fb950",
					border: `1px solid ${allBusy ? "#333" : "#2a5a2a"}`,
					borderRadius: "5px",
					padding: "2px 8px",
					fontSize: "0.75em",
					fontWeight: 600,
					cursor: allBusy ? "not-allowed" : "pointer",
					whiteSpace: "nowrap",
					flexShrink: 0,
				}}
			>
				▶ Start
			</button>
		</div>
	);
}

export function StagePanel({
	title,
	items,
	emptyMessage = "Empty.",
	onItemClick,
	onStopAgent,
	onDeleteItem,
	costs,
	agentRoster,
	busyAgentNames,
	onStartAgent,
	mergesInFlight,
	isMergeStage,
}: StagePanelProps) {
	const showStartButton =
		Boolean(onStartAgent) &&
		agentRoster !== undefined &&
		agentRoster.length > 0;

	return (
		<div
			style={{
				border: "1px solid #333",
				borderRadius: "10px",
				padding: "12px 16px",
				background: "#1f1f1f",
				display: "flex",
				flexDirection: "column",
				gap: "8px",
			}}
		>
			<div
				style={{
					display: "flex",
					alignItems: "center",
					justifyContent: "space-between",
					gap: "12px",
				}}
			>
				<div style={{ fontWeight: 600 }}>{title}</div>
				<div
					style={{
						fontSize: "0.85em",
						color: "#aaa",
					}}
				>
					{items.length}
				</div>
			</div>

			{items.length === 0 ? (
				<div style={{ fontSize: "0.85em", color: "#555" }}>{emptyMessage}</div>
			) : (
				<div
					style={{
						display: "flex",
						flexDirection: "column",
						gap: "6px",
					}}
				>
					{items.map((item) => {
						const itemNumber = item.story_id?.match(/^(\d+)/)?.[1];
						const itemType = item.story_id
							? getWorkItemType(item.story_id)
							: "unknown";
						const borderColor = TYPE_COLORS[itemType];
						const typeLabel = TYPE_LABELS[itemType];
						const hasMergeFailure = Boolean(item.merge_failure);
						const cardStyle = {
							border: hasMergeFailure
								? "1px solid #6e1b1b"
								: item.agent
									? "1px solid #2a3a4a"
									: "1px solid #2a2a2a",
							borderRadius: "8px",
							padding: "8px 12px",
							background: hasMergeFailure
								? "#1f1010"
								: item.agent
									? "#161e2a"
									: "#191919",
							display: "flex",
							flexDirection: "column" as const,
							gap: "2px",
							width: "100%",
							textAlign: "left" as const,
							color: "inherit",
							font: "inherit",
							cursor: onItemClick ? "pointer" : "default",
						};

						// Only offer "Start" when the item has no assigned agent
						const canStart = showStartButton && !item.agent;

						const cardInner = (
							<>
								<div style={{ flex: 1 }}>
									<div style={{ fontWeight: 600, fontSize: "0.9em" }}>
										{hasMergeFailure &&
											(() => {
												const agentStatus = item.agent?.status;
												if (agentStatus === "running") {
													return (
														<span
															data-testid={`merge-failure-icon-${item.story_id}`}
															title="Merge recovery in progress — no human action needed"
															style={{
																display: "inline-block",
																color: "#e3b341",
																marginRight: "6px",
																animation: "spin 1s linear infinite",
															}}
														>
															⟳
														</span>
													);
												}
												if (agentStatus === "pending") {
													return (
														<span
															data-testid={`merge-failure-icon-${item.story_id}`}
															title="Merge recovery scheduled — waiting for a slot"
															style={{
																color: "#e3b341",
																marginRight: "6px",
																fontStyle: "normal",
															}}
														>
															⏳
														</span>
													);
												}
												return (
													<span
														data-testid={`merge-failure-icon-${item.story_id}`}
														title="Merge failed — needs human"
														style={{
															color: "#f85149",
															marginRight: "6px",
															fontStyle: "normal",
														}}
													>
														✕
													</span>
												);
											})()}
										{isMergeStage &&
											item.agent?.status === "running" && (
												<span
													data-testid={`mergemaster-icon-${item.story_id}`}
													title="Mergemaster recovery agent running"
													style={{
														marginRight: "4px",
													}}
												>
													🤖
												</span>
											)}
										{mergesInFlight?.has(item.story_id) && (
											<span
												data-testid={`merge-in-flight-icon-${item.story_id}`}
												title="Deterministic merge in progress"
												style={{
													display: "inline-block",
													marginRight: "6px",
													animation: "spin 1s linear infinite",
												}}
											>
												⟳
											</span>
										)}
										{itemNumber && (
											<span
												style={{
													color: "#777",
													fontFamily: "monospace",
													marginRight: "8px",
												}}
											>
												#{itemNumber}
											</span>
										)}
										{typeLabel && (
											<span
												data-testid={`type-badge-${item.story_id}`}
												style={{
													fontSize: "0.7em",
													fontWeight: 700,
													color: borderColor,
													marginRight: "8px",
													letterSpacing: "0.05em",
												}}
											>
												{typeLabel}
											</span>
										)}
										{item.blocked &&
											!item.merge_failure &&
											(() => {
												const agentStatus = item.agent?.status;
												if (agentStatus === "running") {
													return (
														<span
															data-testid={`blocked-badge-${item.story_id}`}
															title="Recovery coder running — no human action needed"
															style={{
																display: "inline-block",
																fontSize: "0.65em",
																fontWeight: 700,
																color: "#e3b341",
																background: "#2a1f0a",
																border: "1px solid #6e4a00",
																borderRadius: "4px",
																padding: "1px 4px",
																marginRight: "8px",
																letterSpacing: "0.05em",
																animation: "spin 1s linear infinite",
															}}
														>
															⟳ RECOVERING
														</span>
													);
												}
												if (agentStatus === "pending") {
													return (
														<span
															data-testid={`blocked-badge-${item.story_id}`}
															title="Recovery coder queued — waiting for a slot"
															style={{
																fontSize: "0.65em",
																fontWeight: 700,
																color: "#e3b341",
																background: "#2a1f0a",
																border: "1px solid #6e4a00",
																borderRadius: "4px",
																padding: "1px 4px",
																marginRight: "8px",
																letterSpacing: "0.05em",
															}}
														>
															⏳ QUEUED
														</span>
													);
												}
												return (
													<span
														data-testid={`blocked-badge-${item.story_id}`}
														title="Blocked — awaiting human unblock"
														style={{
															fontSize: "0.65em",
															fontWeight: 700,
															color: "#f85149",
															background: "#2a1010",
															border: "1px solid #6e1b1b",
															borderRadius: "4px",
															padding: "1px 4px",
															marginRight: "8px",
															letterSpacing: "0.05em",
														}}
													>
														⊘ BLOCKED
													</span>
												);
											})()}
										{item.frozen && (
											<span
												data-testid={`frozen-badge-${item.story_id}`}
												title="Frozen — auto-assign paused"
												style={{
													fontSize: "0.65em",
													fontWeight: 700,
													color: "#58a6ff",
													background: "#0d1f36",
													border: "1px solid #1a3a6e",
													borderRadius: "4px",
													padding: "1px 4px",
													marginRight: "8px",
													letterSpacing: "0.05em",
												}}
											>
												❄ FROZEN
											</span>
										)}
										{costs?.has(item.story_id) && (
											<span
												data-testid={`cost-badge-${item.story_id}`}
												style={{
													fontSize: "0.65em",
													fontWeight: 600,
													color: "#e3b341",
													marginRight: "8px",
												}}
											>
												${costs.get(item.story_id)?.toFixed(2)}
											</span>
										)}
										{item.name || item.story_id}
									</div>
									{item.error && (
										<div
											style={{
												fontSize: "0.8em",
												color: "#ff7b72",
												marginTop: "4px",
											}}
										>
											{item.error}
										</div>
									)}
									{item.merge_failure && (
										<div
											data-testid={`merge-failure-reason-${item.story_id}`}
										>
											<GateOutputBox text={item.merge_failure} />
										</div>
									)}
									{item.depends_on && item.depends_on.length > 0 && (
										<div
											data-testid={`depends-on-${item.story_id}`}
											style={{
												fontSize: "0.75em",
												color: "#888",
												marginTop: "4px",
											}}
										>
											Depends on:{" "}
											{item.depends_on.map((n) => `#${n}`).join(", ")}
										</div>
									)}
								</div>
								{item.agent && (
									<AgentLozenge
										agent={item.agent}
										storyId={item.story_id}
										onStop={
											onStopAgent && item.agent.status === "running"
												? () =>
														onStopAgent(
															item.story_id,
															item.agent?.agent_name ?? "",
														)
												: undefined
										}
									/>
								)}
								{canStart && onStartAgent && (
									<StartAgentControl
										storyId={item.story_id}
										agentRoster={agentRoster ?? []}
										busyAgentNames={busyAgentNames ?? new Set()}
										onStartAgent={onStartAgent}
									/>
								)}
							</>
						);
						const card = onItemClick ? (
							<button
								type="button"
								data-testid={`card-${item.story_id}`}
								onClick={() => onItemClick(item)}
								style={cardStyle}
							>
								{cardInner}
							</button>
						) : (
							<div data-testid={`card-${item.story_id}`} style={cardStyle}>
								{cardInner}
							</div>
						);
						return (
							<div
								key={`${title}-${item.story_id}`}
								style={{ position: "relative" }}
							>
								{card}
								{onDeleteItem && (
									<button
										type="button"
										data-testid={`delete-btn-${item.story_id}`}
										title={`Delete ${item.name || item.story_id}`}
										onClick={(e) => {
											e.stopPropagation();
											const label = item.name || item.story_id;
											if (
												window.confirm(
													`Delete "${label}"? This cannot be undone.`,
												)
											) {
												onDeleteItem(item);
											}
										}}
										style={{
											position: "absolute",
											top: "4px",
											right: "4px",
											background: "transparent",
											border: "none",
											color: "#555",
											cursor: "pointer",
											fontSize: "0.85em",
											lineHeight: 1,
											padding: "2px 4px",
											borderRadius: "4px",
										}}
										onMouseEnter={(e) => {
											(e.currentTarget as HTMLButtonElement).style.color =
												"#f85149";
										}}
										onMouseLeave={(e) => {
											(e.currentTarget as HTMLButtonElement).style.color =
												"#555";
										}}
									>
										✕
									</button>
								)}
							</div>
						);
					})}
				</div>
			)}
		</div>
	);
}
