import * as React from "react";
import type {
	AgentConfigInfo,
	AgentEvent,
	AgentStatusValue,
} from "../api/agents";
import { agentsApi, subscribeAgentStream } from "../api/agents";
import { settingsApi } from "../api/settings";
import { useLozengeFly } from "./LozengeFlyContext";

const { useCallback, useEffect, useRef, useState } = React;

interface AgentState {
	agentName: string;
	status: AgentStatusValue;
	log: string[];
	sessionId: string | null;
	worktreePath: string | null;
	baseBranch: string | null;
	terminalAt: number | null;
}

const STATUS_COLORS: Record<AgentStatusValue, string> = {
	pending: "#e3b341",
	running: "#58a6ff",
	completed: "#7ee787",
	failed: "#ff7b72",
};

const STATUS_LABELS: Record<AgentStatusValue, string> = {
	pending: "Pending",
	running: "Running",
	completed: "Completed",
	failed: "Failed",
};

const formatTimestamp = (value: Date | null): string => {
	if (!value) return "";
	return value.toLocaleTimeString([], {
		hour: "2-digit",
		minute: "2-digit",
		second: "2-digit",
	});
};

function StatusBadge({ status }: { status: AgentStatusValue }) {
	return (
		<span
			style={{
				display: "inline-flex",
				alignItems: "center",
				gap: "4px",
				padding: "2px 8px",
				borderRadius: "999px",
				fontSize: "0.75em",
				fontWeight: 600,
				background: `${STATUS_COLORS[status]}22`,
				color: STATUS_COLORS[status],
				border: `1px solid ${STATUS_COLORS[status]}44`,
			}}
		>
			{status === "running" && (
				<span
					style={{
						width: "6px",
						height: "6px",
						borderRadius: "50%",
						background: STATUS_COLORS[status],
						animation: "pulse 1.5s infinite",
					}}
				/>
			)}
			{STATUS_LABELS[status]}
		</span>
	);
}

function RosterBadge({
	agent,
	activeStoryId,
}: {
	agent: AgentConfigInfo;
	activeStoryId: string | null;
}) {
	const { registerRosterEl } = useLozengeFly();
	const badgeRef = useRef<HTMLSpanElement>(null);
	const isActive = activeStoryId !== null;
	const storyNumber = activeStoryId?.match(/^(\d+)/)?.[1];

	// Register this element so fly animations know where to start/end
	useEffect(() => {
		const el = badgeRef.current;
		if (el) registerRosterEl(agent.name, el);
		return () => registerRosterEl(agent.name, null);
	}, [agent.name, registerRosterEl]);

	return (
		<span
			ref={badgeRef}
			data-testid={`roster-badge-${agent.name}`}
			style={{
				display: "inline-flex",
				alignItems: "center",
				gap: "4px",
				padding: "2px 8px",
				borderRadius: "6px",
				fontSize: "0.7em",
				background: isActive ? "#58a6ff18" : "#ffffff08",
				color: isActive ? "#58a6ff" : "#888",
				border: isActive ? "1px solid #58a6ff44" : "1px solid #333",
				transition: "background 0.3s, color 0.3s, border-color 0.3s",
			}}
			title={
				isActive
					? `Working on #${storyNumber ?? activeStoryId}`
					: `${agent.role || agent.name} — idle`
			}
		>
			{isActive && (
				<span
					style={{
						width: "5px",
						height: "5px",
						borderRadius: "50%",
						background: "#58a6ff",
						animation: "pulse 1.5s infinite",
						flexShrink: 0,
					}}
				/>
			)}
			{!isActive && (
				<span
					style={{
						width: "5px",
						height: "5px",
						borderRadius: "50%",
						background: "#555",
						flexShrink: 0,
					}}
				/>
			)}
			<span style={{ fontWeight: 600, color: isActive ? "#58a6ff" : "#aaa" }}>
				{agent.name}
			</span>
			{agent.model && (
				<span style={{ color: isActive ? "#7ab8ff" : "#666" }}>
					{agent.model}
				</span>
			)}
			{isActive && storyNumber && (
				<span style={{ color: "#7ab8ff", marginLeft: "2px" }}>
					#{storyNumber}
				</span>
			)}
			{!isActive && (
				<span style={{ color: "#444", fontStyle: "italic" }}>idle</span>
			)}
		</span>
	);
}

/** Build a composite key for tracking agent state. */
function agentKey(storyId: string, agentName: string): string {
	return `${storyId}:${agentName}`;
}

function DiffCommand({
	worktreePath,
	baseBranch,
}: {
	worktreePath: string;
	baseBranch: string;
}) {
	const [copied, setCopied] = useState(false);
	const command = `cd "${worktreePath}" && git difftool ${baseBranch}...HEAD`;

	const handleCopy = async () => {
		try {
			await navigator.clipboard.writeText(command);
			setCopied(true);
			setTimeout(() => setCopied(false), 2000);
		} catch {
			// Fallback: select text for manual copy
		}
	};

	return (
		<div
			style={{
				display: "flex",
				alignItems: "center",
				gap: "6px",
				marginBottom: "6px",
			}}
		>
			<code
				style={{
					flex: 1,
					fontSize: "0.7em",
					color: "#8b949e",
					background: "#0d1117",
					padding: "4px 8px",
					borderRadius: "4px",
					border: "1px solid #21262d",
					overflow: "hidden",
					textOverflow: "ellipsis",
					whiteSpace: "nowrap",
				}}
			>
				{command}
			</code>
			<button
				type="button"
				onClick={handleCopy}
				style={{
					padding: "3px 8px",
					borderRadius: "4px",
					border: "1px solid #30363d",
					background: copied ? "#238636" : "#21262d",
					color: copied ? "#fff" : "#8b949e",
					cursor: "pointer",
					fontSize: "0.7em",
					fontWeight: 600,
					whiteSpace: "nowrap",
				}}
			>
				{copied ? "Copied" : "Copy"}
			</button>
		</div>
	);
}

export function EditorCommand({
	worktreePath,
	editorCommand,
}: {
	worktreePath: string;
	editorCommand: string;
}) {
	const [copied, setCopied] = useState(false);
	const command = `${editorCommand} "${worktreePath}"`;

	const handleCopy = async () => {
		try {
			await navigator.clipboard.writeText(command);
			setCopied(true);
			setTimeout(() => setCopied(false), 2000);
		} catch {
			// Fallback: select text for manual copy
		}
	};

	return (
		<div
			style={{
				display: "flex",
				alignItems: "center",
				gap: "6px",
				marginBottom: "6px",
			}}
		>
			<code
				style={{
					flex: 1,
					fontSize: "0.7em",
					color: "#8b949e",
					background: "#0d1117",
					padding: "4px 8px",
					borderRadius: "4px",
					border: "1px solid #21262d",
					overflow: "hidden",
					textOverflow: "ellipsis",
					whiteSpace: "nowrap",
				}}
			>
				{command}
			</code>
			<button
				type="button"
				onClick={handleCopy}
				style={{
					padding: "3px 8px",
					borderRadius: "4px",
					border: "1px solid #30363d",
					background: copied ? "#238636" : "#21262d",
					color: copied ? "#fff" : "#8b949e",
					cursor: "pointer",
					fontSize: "0.7em",
					fontWeight: 600,
					whiteSpace: "nowrap",
				}}
			>
				{copied ? "Copied" : "Open"}
			</button>
		</div>
	);
}

const FADE_DURATION_MS = 60_000;

export function AgentPanel() {
	const [agents, setAgents] = useState<Record<string, AgentState>>({});
	const [roster, setRoster] = useState<AgentConfigInfo[]>([]);
	const [expandedKey, setExpandedKey] = useState<string | null>(null);
	const [actionError, setActionError] = useState<string | null>(null);
	const [lastRefresh, setLastRefresh] = useState<Date | null>(null);
	const [editorCommand, setEditorCommand] = useState<string | null>(null);
	const [editorInput, setEditorInput] = useState<string>("");
	const [editingEditor, setEditingEditor] = useState(false);
	const cleanupRefs = useRef<Record<string, () => void>>({});
	const logEndRefs = useRef<Record<string, HTMLDivElement | null>>({});
	// Refs for fade-out timers (pause/resume on expand/collapse)
	const fadeTimerRef = useRef<Record<string, ReturnType<typeof setTimeout>>>(
		{},
	);
	const fadeElapsedRef = useRef<Record<string, number>>({});
	const fadeTimerStartRef = useRef<Record<string, number | null>>({});

	// Load roster, existing agents, and editor preference on mount
	useEffect(() => {
		agentsApi
			.getAgentConfig()
			.then(setRoster)
			.catch((err) => console.error("Failed to load agent config:", err));

		agentsApi
			.listAgents()
			.then((agentList) => {
				const agentMap: Record<string, AgentState> = {};
				const now = Date.now();
				for (const a of agentList) {
					const key = agentKey(a.story_id, a.agent_name);
					const isTerminal = a.status === "completed" || a.status === "failed";
					agentMap[key] = {
						agentName: a.agent_name,
						status: a.status,
						log: [],
						sessionId: a.session_id,
						worktreePath: a.worktree_path,
						baseBranch: a.base_branch,
						terminalAt: isTerminal ? now : null,
					};
					if (a.status === "running" || a.status === "pending") {
						subscribeToAgent(a.story_id, a.agent_name);
					}
				}
				setAgents(agentMap);
				setLastRefresh(new Date());
			})
			.catch((err) => console.error("Failed to load agents:", err));

		settingsApi
			.getEditorCommand()
			.then((s) => {
				setEditorCommand(s.editor_command);
				setEditorInput(s.editor_command ?? "");
			})
			.catch((err) => console.error("Failed to load editor command:", err));

		return () => {
			for (const cleanup of Object.values(cleanupRefs.current)) {
				cleanup();
			}
		};
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, []);

	const subscribeToAgent = useCallback((storyId: string, agentName: string) => {
		const key = agentKey(storyId, agentName);
		cleanupRefs.current[key]?.();

		const cleanup = subscribeAgentStream(
			storyId,
			agentName,
			(event: AgentEvent) => {
				setAgents((prev) => {
					const current = prev[key] ?? {
						agentName,
						status: "pending" as AgentStatusValue,
						log: [],
						sessionId: null,
						worktreePath: null,
						baseBranch: null,
						terminalAt: null,
					};

					switch (event.type) {
						case "status": {
							const newStatus =
								(event.status as AgentStatusValue) ?? current.status;
							const isTerminal =
								newStatus === "completed" || newStatus === "failed";
							return {
								...prev,
								[key]: {
									...current,
									status: newStatus,
									terminalAt: isTerminal
										? (current.terminalAt ?? Date.now())
										: current.terminalAt,
								},
							};
						}
						case "output":
							return {
								...prev,
								[key]: {
									...current,
									log: [...current.log, event.text ?? ""],
								},
							};
						case "done":
							return {
								...prev,
								[key]: {
									...current,
									status: "completed",
									sessionId: event.session_id ?? current.sessionId,
									terminalAt: current.terminalAt ?? Date.now(),
								},
							};
						case "error":
							return {
								...prev,
								[key]: {
									...current,
									status: "failed",
									log: [
										...current.log,
										`[ERROR] ${event.message ?? "Unknown error"}`,
									],
									terminalAt: current.terminalAt ?? Date.now(),
								},
							};
						default:
							return prev;
					}
				});
			},
			() => {
				// SSE error — agent may not be streaming yet
			},
		);

		cleanupRefs.current[key] = cleanup;
	}, []);

	// Auto-scroll log when expanded
	useEffect(() => {
		if (expandedKey) {
			const el = logEndRefs.current[expandedKey];
			el?.scrollIntoView({ behavior: "smooth" });
		}
	}, [expandedKey, agents]);

	// Manage fade-out timers for terminal agents.
	// Timers are paused when an entry is expanded and resumed on collapse.
	useEffect(() => {
		for (const [key, agent] of Object.entries(agents)) {
			if (!agent.terminalAt) continue;
			const isExpanded = expandedKey === key;
			const hasTimer = key in fadeTimerRef.current;

			if (isExpanded && hasTimer) {
				// Pause: clear timer and accumulate elapsed time
				clearTimeout(fadeTimerRef.current[key]);
				const started = fadeTimerStartRef.current[key];
				if (started !== null && started !== undefined) {
					fadeElapsedRef.current[key] =
						(fadeElapsedRef.current[key] ?? 0) + (Date.now() - started);
				}
				delete fadeTimerRef.current[key];
				fadeTimerStartRef.current[key] = null;
			} else if (!isExpanded && !hasTimer) {
				// Start or resume timer with remaining time
				const elapsed = fadeElapsedRef.current[key] ?? 0;
				const remaining = Math.max(0, FADE_DURATION_MS - elapsed);
				fadeTimerStartRef.current[key] = Date.now();
				fadeTimerRef.current[key] = setTimeout(() => {
					setAgents((prev) => {
						const next = { ...prev };
						delete next[key];
						return next;
					});
					delete fadeTimerRef.current[key];
					delete fadeTimerStartRef.current[key];
					delete fadeElapsedRef.current[key];
				}, remaining);
			}
		}
	}, [agents, expandedKey]);

	// Clean up fade timers on unmount
	useEffect(() => {
		return () => {
			for (const timer of Object.values(fadeTimerRef.current)) {
				clearTimeout(timer);
			}
			fadeTimerRef.current = {};
		};
	}, []);

	const handleStop = async (storyId: string, agentName: string) => {
		setActionError(null);
		const key = agentKey(storyId, agentName);
		try {
			await agentsApi.stopAgent(storyId, agentName);
			cleanupRefs.current[key]?.();
			delete cleanupRefs.current[key];
			setAgents((prev) => {
				const next = { ...prev };
				delete next[key];
				return next;
			});
		} catch (err) {
			const message = err instanceof Error ? err.message : String(err);
			setActionError(`Failed to stop agent for ${storyId}: ${message}`);
		}
	};

	const handleSaveEditor = async () => {
		try {
			const trimmed = editorInput.trim() || null;
			const result = await settingsApi.setEditorCommand(trimmed);
			setEditorCommand(result.editor_command);
			setEditorInput(result.editor_command ?? "");
			setEditingEditor(false);
		} catch (err) {
			const message = err instanceof Error ? err.message : String(err);
			setActionError(`Failed to save editor: ${message}`);
		}
	};

	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={{
						display: "flex",
						alignItems: "center",
						gap: "8px",
					}}
				>
					<div style={{ fontWeight: 600 }}>Agents</div>
					<div
						style={{
							fontSize: "0.75em",
							color: "#777",
							fontFamily: "monospace",
						}}
					>
						{Object.values(agents).filter((a) => a.status === "running").length}{" "}
						running
					</div>
				</div>
				{lastRefresh && (
					<div style={{ fontSize: "0.7em", color: "#555" }}>
						Loaded {formatTimestamp(lastRefresh)}
					</div>
				)}
			</div>

			{/* Editor preference */}
			<div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
				<span style={{ fontSize: "0.75em", color: "#666" }}>Editor:</span>
				{editingEditor ? (
					<>
						<input
							type="text"
							value={editorInput}
							onChange={(e) => setEditorInput(e.target.value)}
							onKeyDown={(e) => {
								if (e.key === "Enter") handleSaveEditor();
								if (e.key === "Escape") setEditingEditor(false);
							}}
							placeholder="zed, code, cursor..."
							style={{
								fontSize: "0.75em",
								background: "#111",
								border: "1px solid #444",
								borderRadius: "4px",
								color: "#ccc",
								padding: "2px 6px",
								width: "120px",
							}}
						/>
						<button
							type="button"
							onClick={handleSaveEditor}
							style={{
								fontSize: "0.7em",
								padding: "2px 8px",
								borderRadius: "4px",
								border: "1px solid #238636",
								background: "#238636",
								color: "#fff",
								cursor: "pointer",
							}}
						>
							Save
						</button>
						<button
							type="button"
							onClick={() => setEditingEditor(false)}
							style={{
								fontSize: "0.7em",
								padding: "2px 8px",
								borderRadius: "4px",
								border: "1px solid #444",
								background: "none",
								color: "#888",
								cursor: "pointer",
							}}
						>
							Cancel
						</button>
					</>
				) : (
					<button
						type="button"
						onClick={() => setEditingEditor(true)}
						style={{
							fontSize: "0.75em",
							background: "none",
							border: "1px solid #333",
							borderRadius: "4px",
							color: editorCommand ? "#aaa" : "#555",
							cursor: "pointer",
							padding: "2px 8px",
							fontFamily: editorCommand ? "monospace" : "inherit",
						}}
					>
						{editorCommand ?? "Set editor..."}
					</button>
				)}
			</div>

			{/* Roster badges — show all configured agents with idle/active state */}
			{roster.length > 0 && (
				<div
					style={{
						display: "flex",
						flexWrap: "wrap",
						gap: "4px",
					}}
				>
					{roster.map((a) => {
						// Find the story this roster agent is currently working on (if any)
						const activeEntry = Object.entries(agents).find(
							([, state]) =>
								state.agentName === a.name &&
								(state.status === "running" || state.status === "pending"),
						);
						const activeStoryId = activeEntry
							? activeEntry[0].split(":")[0]
							: null;
						return (
							<RosterBadge
								key={`roster-${a.name}`}
								agent={a}
								activeStoryId={activeStoryId}
							/>
						);
					})}
				</div>
			)}

			{actionError && (
				<div
					style={{
						fontSize: "0.85em",
						color: "#ff7b72",
						padding: "4px 8px",
						background: "#ff7b7211",
						borderRadius: "6px",
					}}
				>
					{actionError}
				</div>
			)}

			{/* Active agents */}
			{Object.entries(agents).length > 0 && (
				<div
					style={{
						display: "flex",
						flexDirection: "column",
						gap: "6px",
					}}
				>
					{Object.entries(agents).map(([key, a]) => (
						<div
							key={`agent-${key}`}
							data-testid={`agent-entry-${key}`}
							style={{
								border: "1px solid #2a2a2a",
								borderRadius: "8px",
								background: "#191919",
								overflow: "hidden",
								...(a.terminalAt
									? {
											animationName: "agentFadeOut",
											animationDuration: "60s",
											animationTimingFunction: "linear",
											animationFillMode: "forwards",
											animationPlayState:
												expandedKey === key ? "paused" : "running",
										}
									: {}),
							}}
						>
							<div
								style={{
									padding: "8px 12px",
									display: "flex",
									alignItems: "center",
									gap: "8px",
								}}
							>
								<button
									type="button"
									onClick={() =>
										setExpandedKey(expandedKey === key ? null : key)
									}
									style={{
										background: "none",
										border: "none",
										color: "#aaa",
										cursor: "pointer",
										fontSize: "0.8em",
										padding: "0 4px",
										transform:
											expandedKey === key ? "rotate(90deg)" : "rotate(0deg)",
										transition: "transform 0.15s",
									}}
								>
									&#9654;
								</button>

								<div
									style={{
										flex: 1,
										fontWeight: 600,
										fontSize: "0.9em",
										overflow: "hidden",
										textOverflow: "ellipsis",
										whiteSpace: "nowrap",
									}}
								>
									<span style={{ color: "#888" }}>{a.agentName}</span>
									<span style={{ color: "#555", margin: "0 6px" }}>
										{key.split(":")[0]}
									</span>
								</div>

								<StatusBadge status={a.status} />

								{(a.status === "running" || a.status === "pending") && (
									<button
										type="button"
										onClick={() => handleStop(key.split(":")[0], a.agentName)}
										style={{
											padding: "4px 10px",
											borderRadius: "999px",
											border: "1px solid #ff7b7244",
											background: "#ff7b7211",
											color: "#ff7b72",
											cursor: "pointer",
											fontSize: "0.75em",
											fontWeight: 600,
										}}
									>
										Stop
									</button>
								)}
							</div>

							{expandedKey === key && (
								<div
									style={{
										borderTop: "1px solid #2a2a2a",
										padding: "8px 12px",
									}}
								>
									{a.worktreePath && (
										<div
											style={{
												fontSize: "0.75em",
												color: "#666",
												fontFamily: "monospace",
												marginBottom: "6px",
											}}
										>
											Worktree: {a.worktreePath}
										</div>
									)}
									{a.worktreePath && (
										<DiffCommand
											worktreePath={a.worktreePath}
											baseBranch={a.baseBranch ?? "master"}
										/>
									)}
									<div
										style={{
											maxHeight: "300px",
											overflowY: "auto",
											background: "#111",
											borderRadius: "6px",
											padding: "8px",
											fontFamily: "monospace",
											fontSize: "0.8em",
											lineHeight: "1.5",
											color: "#ccc",
											whiteSpace: "pre-wrap",
											wordBreak: "break-word",
										}}
									>
										{a.log.length === 0 ? (
											<span style={{ color: "#555" }}>
												{a.status === "pending" || a.status === "running"
													? "Waiting for output..."
													: "No output captured."}
											</span>
										) : (
											a.log.map((line, i) => (
												<div
													key={`log-${key}-${i.toString()}`}
													style={{
														color: line.startsWith("[ERROR]")
															? "#ff7b72"
															: "#ccc",
													}}
												>
													{line}
												</div>
											))
										)}
										<div
											ref={(el) => {
												logEndRefs.current[key] = el;
											}}
										/>
									</div>
								</div>
							)}
						</div>
					))}
				</div>
			)}
		</div>
	);
}
