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

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 ? "#3fb95018" : "#aaaaaa18",
				color: isActive ? "#3fb950" : "#aaa",
				border: isActive ? "1px solid #3fb95044" : "1px solid #aaaaaa44",
				transition: "background 0.3s, color 0.3s, border-color 0.3s",
			}}
			title={
				isActive
					? `Working on #${storyNumber ?? activeStoryId}`
					: `${agent.role || agent.name} — available`
			}
		>
			{isActive && (
				<span
					data-testid={`roster-dot-${agent.name}`}
					style={{
						width: "5px",
						height: "5px",
						borderRadius: "50%",
						background: "#3fb950",
						animation: "pulse 1.5s infinite",
						flexShrink: 0,
					}}
				/>
			)}
			{!isActive && (
				<span
					data-testid={`roster-dot-${agent.name}`}
					style={{
						width: "5px",
						height: "5px",
						borderRadius: "50%",
						background: "#3fb950",
						flexShrink: 0,
					}}
				/>
			)}
			<span style={{ fontWeight: 600, color: isActive ? "#3fb950" : "#aaa" }}>
				{agent.name}
			</span>
			{agent.model && (
				<span style={{ color: isActive ? "#5ab96a" : "#888" }}>
					{agent.model}
				</span>
			)}
			{isActive && storyNumber && (
				<span style={{ color: "#5ab96a", marginLeft: "2px" }}>
					#{storyNumber}
				</span>
			)}
			{!isActive && (
				<span style={{ color: "#888", fontStyle: "italic" }}>available</span>
			)}
		</span>
	);
}

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

export function AgentPanel() {
	const { hiddenRosterAgents } = useLozengeFly();
	const [agents, setAgents] = useState<Record<string, AgentState>>({});
	const [roster, setRoster] = useState<AgentConfigInfo[]>([]);
	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>>({});

	// 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;
	}, []);

	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;
						const isHidden = hiddenRosterAgents.has(a.name);
						return (
							// Collapsing wrapper: smoothly shrinks when agent departs
							// to a work item and expands when it returns.
							<div
								key={`roster-wrapper-${a.name}`}
								data-testid={`roster-badge-wrapper-${a.name}`}
								style={{
									overflow: "hidden",
									maxWidth: isHidden ? "0" : "300px",
									opacity: isHidden ? 0 : 1,
									transition: "max-width 0.35s ease, opacity 0.2s ease",
								}}
							>
								<RosterBadge agent={a} activeStoryId={activeStoryId} />
							</div>
						);
					})}
				</div>
			)}

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