import * as d3 from "d3";
import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
import { createRoot } from "react-dom/client";

type ViewMode = "kanban" | "dependencies";

interface Project {
	path: string;
	name: string;
	color: string;
}

interface AggregatedTask {
	id: string;
	title: string;
	status: string;
	priority?: string;
	description?: string;
	assignee: string[];
	labels: string[];
	projectName: string;
	projectColor: string;
	projectPath: string;
	createdDate?: string;
	updatedDate?: string;
}

interface WebSocketMessage {
	type: "init" | "update";
	projects: Project[];
	tasks: AggregatedTask[];
	timestamp?: number;
}

function App() {
	const [projects, setProjects] = useState<Project[]>([]);
	const [tasks, setTasks] = useState<AggregatedTask[]>([]);
	const [connected, setConnected] = useState(false);
	const [lastUpdate, setLastUpdate] = useState<Date | null>(null);
	const [filter, setFilter] = useState<string>("");
	const [selectedProjects, setSelectedProjects] = useState<Set<string> | null>(null); // null = all
	const [dropdownOpen, setDropdownOpen] = useState(false);
	const dropdownRef = useRef<HTMLDivElement>(null);
	const [actionError, setActionError] = useState<string | null>(null);
	const [draggedTask, setDraggedTask] = useState<AggregatedTask | null>(null);
	const [dragOverColumn, setDragOverColumn] = useState<string | null>(null);
	const [viewMode, setViewMode] = useState<ViewMode>("kanban");
	const [showCompleted, setShowCompleted] = useState(false);
	const wsRef = useRef<WebSocket | null>(null);
	const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

	const handleArchiveTask = async (task: AggregatedTask) => {
		try {
			const response = await fetch("/api/tasks/archive", {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({ projectPath: task.projectPath, taskId: task.id }),
			});
			if (!response.ok) {
				const data = await response.json();
				throw new Error(data.error || "Failed to archive task");
			}
			// Optimistically remove from UI immediately
			setTasks((prev) => prev.filter((t) => !(t.projectPath === task.projectPath && t.id === task.id)));
			setActionError(null);
		} catch (error) {
			setActionError(error instanceof Error ? error.message : "Failed to archive task");
			setTimeout(() => setActionError(null), 3000);
		}
	};

	const handleDeleteTask = async (task: AggregatedTask) => {
		if (!confirm(`Are you sure you want to delete "${task.title}"? This cannot be undone.`)) {
			return;
		}
		try {
			const response = await fetch("/api/tasks/delete", {
				method: "DELETE",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({ projectPath: task.projectPath, taskId: task.id }),
			});
			if (!response.ok) {
				const data = await response.json();
				throw new Error(data.error || "Failed to delete task");
			}
			// Optimistically remove from UI immediately
			setTasks((prev) => prev.filter((t) => !(t.projectPath === task.projectPath && t.id === task.id)));
			setActionError(null);
		} catch (error) {
			setActionError(error instanceof Error ? error.message : "Failed to delete task");
			setTimeout(() => setActionError(null), 3000);
		}
	};

	const handleUpdateTask = async (task: AggregatedTask, updates: Partial<AggregatedTask>) => {
		try {
			const response = await fetch("/api/tasks/update", {
				method: "PATCH",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({
					projectPath: task.projectPath,
					taskId: task.id,
					...updates,
				}),
			});
			if (!response.ok) {
				const data = await response.json();
				throw new Error(data.error || "Failed to update task");
			}
			setActionError(null);
		} catch (error) {
			setActionError(error instanceof Error ? error.message : "Failed to update task");
			setTimeout(() => setActionError(null), 3000);
		}
	};

	const handleColumnDragOver = (e: React.DragEvent, status: string) => {
		e.preventDefault();
		e.dataTransfer.dropEffect = "move";
		setDragOverColumn(status);
	};

	const handleColumnDragLeave = (e: React.DragEvent) => {
		// Only clear if leaving the column entirely
		if (!e.currentTarget.contains(e.relatedTarget as Node)) {
			setDragOverColumn(null);
		}
	};

	const handleColumnDrop = async (e: React.DragEvent, targetStatus: string) => {
		e.preventDefault();
		setDragOverColumn(null);
		setDraggedTask(null);

		try {
			const data = e.dataTransfer.getData("text/plain");
			if (!data) return;

			const { projectPath, taskId, sourceStatus } = JSON.parse(data);

			// Don't update if dropped on same status
			if (sourceStatus === targetStatus) return;

			// Find the task and update it
			const task = tasks.find(t => t.projectPath === projectPath && t.id === taskId);
			if (task) {
				await handleUpdateTask(task, { status: targetStatus });
			}
		} catch (error) {
			console.error("Drop error:", error);
			setActionError("Failed to move task");
			setTimeout(() => setActionError(null), 3000);
		}
	};

	const handleTaskDragStart = (task: AggregatedTask) => {
		setDraggedTask(task);
	};

	const handleTaskDragEnd = () => {
		setDraggedTask(null);
		setDragOverColumn(null);
	};

	const connectWebSocket = useCallback(() => {
		const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
		const ws = new WebSocket(`${protocol}//${window.location.host}`);

		ws.onopen = () => {
			setConnected(true);
			console.log("WebSocket connected");
		};

		ws.onmessage = (event) => {
			try {
				const data: WebSocketMessage = JSON.parse(event.data);
				if (data.type === "init" || data.type === "update") {
					setProjects(data.projects);
					setTasks(data.tasks);
					setLastUpdate(new Date());
				}
			} catch (error) {
				console.error("Failed to parse WebSocket message:", error);
			}
		};

		ws.onclose = () => {
			setConnected(false);
			console.log("WebSocket disconnected, reconnecting...");
			reconnectTimeoutRef.current = setTimeout(connectWebSocket, 3000);
		};

		ws.onerror = (error) => {
			console.error("WebSocket error:", error);
		};

		wsRef.current = ws;
	}, []);

	useEffect(() => {
		connectWebSocket();
		return () => {
			wsRef.current?.close();
			if (reconnectTimeoutRef.current) {
				clearTimeout(reconnectTimeoutRef.current);
			}
		};
	}, [connectWebSocket]);

	// Close dropdown on outside click
	useEffect(() => {
		const handleClick = (e: MouseEvent) => {
			if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
				setDropdownOpen(false);
			}
		};
		document.addEventListener("click", handleClick);
		return () => document.removeEventListener("click", handleClick);
	}, []);

	const isProjectSelected = (name: string) => selectedProjects === null || selectedProjects.has(name);

	const toggleProject = (name: string) => {
		const activeNames = projects.filter((p) => tasks.some((t) => t.projectName === p.name)).map((p) => p.name);
		if (selectedProjects === null) {
			const next = new Set(activeNames);
			next.delete(name);
			setSelectedProjects(next);
		} else if (selectedProjects.has(name)) {
			const next = new Set(selectedProjects);
			next.delete(name);
			setSelectedProjects(next);
		} else {
			const next = new Set(selectedProjects);
			next.add(name);
			setSelectedProjects(next.size === activeNames.length ? null : next);
		}
	};

	const activeProjects = projects.filter((p) => tasks.some((t) => t.projectName === p.name));

	const dropdownLabel = (() => {
		if (selectedProjects === null) return "All Projects";
		if (selectedProjects.size === 0) return "No Projects";
		if (selectedProjects.size === 1) return [...selectedProjects][0];
		return `${selectedProjects.size} Projects`;
	})();

	// Group tasks by status
	const statuses = ["To Do", "In Progress", "Done", "Won't Do"];
	const tasksByStatus = statuses.reduce(
		(acc, status) => {
			acc[status] = tasks.filter((t) => {
				const statusMatch = t.status.toLowerCase() === status.toLowerCase();
				const projectMatch = selectedProjects === null || selectedProjects.has(t.projectName);
				const filterMatch =
					!filter ||
					t.title.toLowerCase().includes(filter.toLowerCase()) ||
					t.projectName.toLowerCase().includes(filter.toLowerCase());
				return statusMatch && projectMatch && filterMatch;
			});
			return acc;
		},
		{} as Record<string, AggregatedTask[]>,
	);

	return (
		<div
			style={{
				minHeight: "100vh",
				backgroundColor: "#0f172a",
				color: "#e2e8f0",
				fontFamily: "system-ui, -apple-system, sans-serif",
			}}
		>
			{/* Header */}
			<header
				style={{
					padding: "1rem 2rem",
					backgroundColor: "#1e293b",
					borderBottom: "1px solid #334155",
					display: "flex",
					justifyContent: "space-between",
					alignItems: "center",
				}}
			>
				<div style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
					<h1 style={{ margin: 0, fontSize: "1.5rem", fontWeight: 600 }}>Backlog Aggregator</h1>
					<span
						style={{
							padding: "0.25rem 0.5rem",
							borderRadius: "9999px",
							fontSize: "0.75rem",
							backgroundColor: connected ? "#22c55e" : "#ef4444",
							color: "white",
						}}
					>
						{connected ? "Live" : "Reconnecting..."}
					</span>
					{/* View Mode Tabs */}
					<div style={{ display: "flex", gap: "0.25rem", marginLeft: "1rem" }}>
						<button
							onClick={() => setViewMode("kanban")}
							style={{
								padding: "0.5rem 1rem",
								borderRadius: "0.375rem",
								border: "none",
								backgroundColor: viewMode === "kanban" ? "#3b82f6" : "#334155",
								color: "#e2e8f0",
								cursor: "pointer",
								fontSize: "0.875rem",
								fontWeight: 500,
								display: "flex",
								alignItems: "center",
								gap: "0.5rem",
							}}
						>
							<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
								<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" />
							</svg>
							Kanban
						</button>
						<button
							onClick={() => setViewMode("dependencies")}
							style={{
								padding: "0.5rem 1rem",
								borderRadius: "0.375rem",
								border: "none",
								backgroundColor: viewMode === "dependencies" ? "#3b82f6" : "#334155",
								color: "#e2e8f0",
								cursor: "pointer",
								fontSize: "0.875rem",
								fontWeight: 500,
								display: "flex",
								alignItems: "center",
								gap: "0.5rem",
							}}
						>
							<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
								<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
							</svg>
							Dependencies
						</button>
					</div>
				</div>
				<div style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
					{actionError && (
						<span
							style={{
								padding: "0.25rem 0.75rem",
								borderRadius: "0.25rem",
								fontSize: "0.75rem",
								backgroundColor: "#ef4444",
								color: "white",
							}}
						>
							{actionError}
						</span>
					)}
					<span style={{ fontSize: "0.875rem", color: "#94a3b8" }}>
						{projects.length} projects | {tasks.length} tasks
						{lastUpdate && ` | Updated ${lastUpdate.toLocaleTimeString()}`}
					</span>
				</div>
			</header>

			{/* Project Filter Bar */}
			<div
				style={{
					padding: "1rem 2rem",
					backgroundColor: "#1e293b",
					display: "flex",
					gap: "0.5rem",
					flexWrap: "wrap",
					alignItems: "center",
				}}
			>
				<input
					type="text"
					placeholder="Filter tasks..."
					value={filter}
					onChange={(e) => setFilter(e.target.value)}
					style={{
						padding: "0.5rem 1rem",
						borderRadius: "0.375rem",
						border: "1px solid #334155",
						backgroundColor: "#0f172a",
						color: "#e2e8f0",
						minWidth: "200px",
					}}
				/>
				<div ref={dropdownRef} style={{ position: "relative" }}>
					<div
						onClick={(e) => {
							e.stopPropagation();
							setDropdownOpen(!dropdownOpen);
						}}
						style={{
							padding: "0.5rem 1rem",
							borderRadius: "0.375rem",
							border: "1px solid #334155",
							backgroundColor: "#0f172a",
							color: "#e2e8f0",
							cursor: "pointer",
							display: "flex",
							alignItems: "center",
							gap: "0.5rem",
							fontSize: "0.875rem",
							minWidth: "180px",
							userSelect: "none",
						}}
					>
						<span style={{ display: "flex", gap: "0.25rem", alignItems: "center" }}>
							{selectedProjects !== null && selectedProjects.size > 0 && selectedProjects.size <= 5
								? [...selectedProjects].map((name) => {
										const proj = projects.find((p) => p.name === name);
										return proj ? (
											<span
												key={name}
												style={{
													width: selectedProjects.size === 1 ? "0.75rem" : "0.5rem",
													height: selectedProjects.size === 1 ? "0.75rem" : "0.5rem",
													borderRadius: "50%",
													backgroundColor: proj.color,
													display: "inline-block",
												}}
											/>
										) : null;
									})
								: null}
						</span>
						<span>{dropdownLabel}</span>
						<span style={{ marginLeft: "auto", fontSize: "0.625rem", transition: "transform 0.2s", transform: dropdownOpen ? "rotate(180deg)" : "none" }}>
							▼
						</span>
					</div>
					{dropdownOpen && (
						<div
							style={{
								position: "absolute",
								top: "calc(100% + 4px)",
								left: 0,
								minWidth: "220px",
								background: "#1e293b",
								border: "1px solid #334155",
								borderRadius: "0.5rem",
								boxShadow: "0 10px 25px rgba(0,0,0,0.4)",
								zIndex: 100,
								overflow: "hidden",
							}}
						>
							<div style={{ display: "flex", borderBottom: "1px solid #334155" }}>
								<button
									onClick={() => setSelectedProjects(null)}
									style={{
										flex: 1,
										padding: "0.5rem",
										background: "none",
										border: "none",
										borderRight: "1px solid #334155",
										color: "#94a3b8",
										fontSize: "0.75rem",
										cursor: "pointer",
									}}
								>
									All Projects
								</button>
								<button
									onClick={() => setSelectedProjects(new Set())}
									style={{
										flex: 1,
										padding: "0.5rem",
										background: "none",
										border: "none",
										color: "#94a3b8",
										fontSize: "0.75rem",
										cursor: "pointer",
									}}
								>
									Clear All
								</button>
							</div>
							{activeProjects.map((project) => {
								const selected = isProjectSelected(project.name);
								return (
									<div
										key={project.path}
										onClick={() => toggleProject(project.name)}
										style={{
											display: "flex",
											alignItems: "center",
											gap: "0.5rem",
											padding: "0.5rem 0.75rem",
											cursor: "pointer",
											fontSize: "0.875rem",
											background: "transparent",
										}}
										onMouseEnter={(e) => (e.currentTarget.style.background = "#334155")}
										onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
									>
										<span
											style={{
												width: "1rem",
												height: "1rem",
												border: selected ? "1.5px solid #3b82f6" : "1.5px solid #475569",
												borderRadius: "3px",
												display: "flex",
												alignItems: "center",
												justifyContent: "center",
												background: selected ? "#3b82f6" : "transparent",
												flexShrink: 0,
											}}
										>
											{selected && (
												<svg width="10" height="10" viewBox="0 0 10 10" fill="none">
													<path d="M2 5L4 7L8 3" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
												</svg>
											)}
										</span>
										<span
											style={{
												width: "0.75rem",
												height: "0.75rem",
												borderRadius: "50%",
												backgroundColor: project.color,
												flexShrink: 0,
											}}
										/>
										{project.name}
									</div>
								);
							})}
						</div>
					)}
				</div>
			</div>

			{/* View Content */}
			{viewMode === "kanban" ? (
				/* Kanban Board */
				<div
					style={{
						padding: "2rem",
						display: "grid",
						gridTemplateColumns: "repeat(4, 1fr)",
						gap: "1.5rem",
						minHeight: "calc(100vh - 180px)",
					}}
				>
					{statuses.map((status) => {
						const isDropTarget = dragOverColumn === status && draggedTask?.status !== status;
						return (
						<div
							key={status}
							onDragOver={(e) => handleColumnDragOver(e, status)}
							onDragLeave={handleColumnDragLeave}
							onDrop={(e) => handleColumnDrop(e, status)}
							style={{
								backgroundColor: isDropTarget ? "#1e3a5f" : "#1e293b",
								borderRadius: "0.5rem",
								padding: "1rem",
								display: "flex",
								flexDirection: "column",
								border: isDropTarget ? "2px dashed #3b82f6" : "2px solid transparent",
								transition: "all 0.2s ease",
							}}
						>
							{/* Column Header */}
							<div
								style={{
									display: "flex",
									justifyContent: "space-between",
									alignItems: "center",
									marginBottom: "1rem",
									paddingBottom: "0.5rem",
									borderBottom: "1px solid #334155",
								}}
							>
								<h2 style={{ margin: 0, fontSize: "1rem", fontWeight: 600 }}>{status}</h2>
								<span
									style={{
										backgroundColor: "#334155",
										padding: "0.25rem 0.5rem",
										borderRadius: "9999px",
										fontSize: "0.75rem",
									}}
								>
									{tasksByStatus[status]?.length || 0}
								</span>
							</div>

							{/* Tasks */}
							<div style={{ display: "flex", flexDirection: "column", gap: "0.75rem", flex: 1, overflowY: "auto" }}>
								{tasksByStatus[status]?.map((task) => (
									<TaskCard
										key={`${task.projectPath}:${task.id}`}
										task={task}
										onArchive={handleArchiveTask}
										onDelete={handleDeleteTask}
										onUpdate={handleUpdateTask}
										statuses={statuses}
										onDragStart={handleTaskDragStart}
										onDragEnd={handleTaskDragEnd}
										isDragging={draggedTask?.id === task.id && draggedTask?.projectPath === task.projectPath}
									/>
								))}
								{(!tasksByStatus[status] || tasksByStatus[status].length === 0) && (
									<div style={{ color: "#64748b", textAlign: "center", padding: "2rem", fontSize: "0.875rem" }}>
										{draggedTask && draggedTask.status !== status ? "Drop here to move task" : "No tasks"}
									</div>
								)}
							</div>
						</div>
					);
					})}
				</div>
			) : (
				/* Dependency Graph View */
				<div style={{ padding: "2rem" }}>
					<div style={{ display: "flex", alignItems: "center", gap: "1rem", marginBottom: "1rem" }}>
						<label style={{ display: "flex", alignItems: "center", gap: "0.5rem", color: "#94a3b8", cursor: "pointer" }}>
							<input
								type="checkbox"
								checked={showCompleted}
								onChange={(e) => setShowCompleted(e.target.checked)}
								style={{ width: "1rem", height: "1rem" }}
							/>
							Show completed tasks
						</label>
					</div>
					<DependencyGraph
						tasks={tasks}
						projects={projects}
						selectedProjects={selectedProjects}
						filter={filter}
						showCompleted={showCompleted}
					/>
				</div>
			)}
		</div>
	);
}

interface TaskCardProps {
	task: AggregatedTask;
	onArchive: (task: AggregatedTask) => void;
	onDelete: (task: AggregatedTask) => void;
	onUpdate: (task: AggregatedTask, updates: Partial<AggregatedTask>) => void;
	statuses: string[];
	onDragStart?: (task: AggregatedTask) => void;
	onDragEnd?: () => void;
	isDragging?: boolean;
}

function TaskCard({ task, onArchive, onDelete, onUpdate, statuses, onDragStart, onDragEnd, isDragging }: TaskCardProps) {
	const [expanded, setExpanded] = useState(false);
	const [isHovering, setIsHovering] = useState(false);
	const [editingField, setEditingField] = useState<string | null>(null);
	const [editValue, setEditValue] = useState("");

	const priorityColors: Record<string, string> = {
		high: "#ef4444",
		medium: "#f59e0b",
		low: "#22c55e",
	};

	const priorities = ["high", "medium", "low"];

	const handleArchiveClick = (e: React.MouseEvent) => {
		e.stopPropagation();
		onArchive(task);
	};

	const handleDeleteClick = (e: React.MouseEvent) => {
		e.stopPropagation();
		onDelete(task);
	};

	const startEditing = (field: string, currentValue: string, e: React.MouseEvent) => {
		e.stopPropagation();
		setEditingField(field);
		setEditValue(currentValue);
	};

	const saveEdit = async (e: React.MouseEvent | React.KeyboardEvent) => {
		e.stopPropagation();
		if (editingField && editValue !== task[editingField as keyof AggregatedTask]) {
			onUpdate(task, { [editingField]: editValue });
		}
		setEditingField(null);
		setEditValue("");
	};

	const cancelEdit = (e: React.MouseEvent | React.KeyboardEvent) => {
		e.stopPropagation();
		setEditingField(null);
		setEditValue("");
	};

	const handleKeyDown = (e: React.KeyboardEvent) => {
		if (e.key === "Enter" && !e.shiftKey) {
			saveEdit(e);
		} else if (e.key === "Escape") {
			cancelEdit(e);
		}
	};

	const handleStatusChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
		e.stopPropagation();
		onUpdate(task, { status: e.target.value });
	};

	const handleDragStart = (e: React.DragEvent) => {
		e.dataTransfer.setData("text/plain", JSON.stringify({
			projectPath: task.projectPath,
			taskId: task.id,
			sourceStatus: task.status,
		}));
		e.dataTransfer.effectAllowed = "move";
		onDragStart?.(task);
	};

	const handleDragEnd = () => {
		onDragEnd?.();
	};

	const handlePriorityChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
		e.stopPropagation();
		onUpdate(task, { priority: e.target.value });
	};

	const inputStyle: React.CSSProperties = {
		backgroundColor: "#1e293b",
		border: "1px solid #3b82f6",
		borderRadius: "0.25rem",
		padding: "0.25rem 0.5rem",
		color: "#e2e8f0",
		fontSize: "inherit",
		width: "100%",
		outline: "none",
	};

	const selectStyle: React.CSSProperties = {
		backgroundColor: "#1e293b",
		border: "1px solid #334155",
		borderRadius: "0.25rem",
		padding: "0.125rem 0.25rem",
		color: "#e2e8f0",
		fontSize: "0.625rem",
		cursor: "pointer",
		outline: "none",
	};

	return (
		<div
			draggable={!editingField}
			onDragStart={handleDragStart}
			onDragEnd={handleDragEnd}
			onClick={() => !editingField && setExpanded(!expanded)}
			onMouseEnter={() => setIsHovering(true)}
			onMouseLeave={() => setIsHovering(false)}
			style={{
				backgroundColor: "#0f172a",
				borderRadius: "0.375rem",
				padding: "0.75rem",
				cursor: editingField ? "default" : isDragging ? "grabbing" : "grab",
				borderLeft: `4px solid ${task.projectColor}`,
				transition: "transform 0.1s, box-shadow 0.1s, opacity 0.1s",
				position: "relative",
				transform: isHovering && !editingField && !isDragging ? "translateY(-1px)" : "translateY(0)",
				boxShadow: isHovering && !editingField ? "0 4px 6px -1px rgba(0, 0, 0, 0.3)" : "none",
				opacity: isDragging ? 0.5 : 1,
			}}
		>
			{/* Action buttons - shown on hover */}
			{isHovering && !editingField && (
				<div
					style={{
						position: "absolute",
						top: "-8px",
						right: "-8px",
						display: "flex",
						gap: "4px",
						zIndex: 10,
					}}
				>
					<button
						onClick={handleArchiveClick}
						title="Archive task"
						style={{
							width: "24px",
							height: "24px",
							borderRadius: "50%",
							border: "none",
							backgroundColor: "#3b82f6",
							color: "white",
							cursor: "pointer",
							display: "flex",
							alignItems: "center",
							justifyContent: "center",
							fontSize: "12px",
							fontWeight: "bold",
							boxShadow: "0 2px 4px rgba(0,0,0,0.3)",
						}}
					>
						↑
					</button>
					<button
						onClick={handleDeleteClick}
						title="Delete task"
						style={{
							width: "24px",
							height: "24px",
							borderRadius: "50%",
							border: "none",
							backgroundColor: "#ef4444",
							color: "white",
							cursor: "pointer",
							display: "flex",
							alignItems: "center",
							justifyContent: "center",
							fontSize: "14px",
							fontWeight: "bold",
							boxShadow: "0 2px 4px rgba(0,0,0,0.3)",
						}}
					>
						×
					</button>
				</div>
			)}

			{/* Task Header */}
			<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: "0.5rem" }}>
				<div style={{ flex: 1 }}>
					{editingField === "title" ? (
						<div style={{ display: "flex", gap: "0.25rem", alignItems: "center" }}>
							<input
								type="text"
								value={editValue}
								onChange={(e) => setEditValue(e.target.value)}
								onKeyDown={handleKeyDown}
								onClick={(e) => e.stopPropagation()}
								autoFocus
								style={{ ...inputStyle, fontSize: "0.875rem", fontWeight: 500 }}
							/>
							<button onClick={saveEdit} style={{ ...selectStyle, backgroundColor: "#22c55e" }}>✓</button>
							<button onClick={cancelEdit} style={{ ...selectStyle, backgroundColor: "#ef4444" }}>✕</button>
						</div>
					) : (
						<div
							style={{ fontSize: "0.875rem", fontWeight: 500, marginBottom: "0.25rem", cursor: "text" }}
							onClick={(e) => startEditing("title", task.title, e)}
							title="Click to edit title"
						>
							{task.title}
						</div>
					)}
					<div style={{ fontSize: "0.75rem", color: "#64748b", display: "flex", alignItems: "center", gap: "0.5rem" }}>
						<span>{task.id} | {task.projectName}</span>
						<select
							value={task.status}
							onChange={handleStatusChange}
							onClick={(e) => e.stopPropagation()}
							style={{ ...selectStyle, marginLeft: "0.25rem" }}
							title="Change status"
						>
							{statuses.map((s) => (
								<option key={s} value={s}>{s}</option>
							))}
						</select>
					</div>
				</div>
				<select
					value={task.priority || ""}
					onChange={handlePriorityChange}
					onClick={(e) => e.stopPropagation()}
					style={{
						...selectStyle,
						backgroundColor: task.priority ? priorityColors[task.priority.toLowerCase()] + "22" : "#334155",
						color: task.priority ? priorityColors[task.priority.toLowerCase()] : "#94a3b8",
						fontWeight: 500,
						textTransform: "uppercase",
					}}
					title="Change priority"
				>
					<option value="">No priority</option>
					{priorities.map((p) => (
						<option key={p} value={p}>{p}</option>
					))}
				</select>
			</div>

			{/* Labels */}
			{task.labels && task.labels.length > 0 && (
				<div style={{ display: "flex", gap: "0.25rem", flexWrap: "wrap", marginTop: "0.5rem" }}>
					{task.labels.map((label) => (
						<span
							key={label}
							style={{
								padding: "0.125rem 0.375rem",
								borderRadius: "0.25rem",
								fontSize: "0.625rem",
								backgroundColor: "#334155",
								color: "#94a3b8",
							}}
						>
							{label}
						</span>
					))}
				</div>
			)}

			{/* Expanded Details */}
			{expanded && (
				<div
					style={{
						marginTop: "0.75rem",
						paddingTop: "0.75rem",
						borderTop: "1px solid #334155",
					}}
				>
					{editingField === "description" ? (
						<div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
							<textarea
								value={editValue}
								onChange={(e) => setEditValue(e.target.value)}
								onKeyDown={(e) => {
									if (e.key === "Escape") cancelEdit(e);
									// Allow Enter for newlines in textarea, use Ctrl+Enter to save
									if (e.key === "Enter" && e.ctrlKey) saveEdit(e);
								}}
								onClick={(e) => e.stopPropagation()}
								autoFocus
								rows={5}
								style={{
									...inputStyle,
									fontSize: "0.8rem",
									resize: "vertical",
									minHeight: "80px",
								}}
								placeholder="Enter description..."
							/>
							<div style={{ display: "flex", gap: "0.25rem", justifyContent: "flex-end" }}>
								<span style={{ fontSize: "0.625rem", color: "#64748b", marginRight: "auto" }}>
									Ctrl+Enter to save, Escape to cancel
								</span>
								<button onClick={saveEdit} style={{ ...selectStyle, backgroundColor: "#22c55e", padding: "0.25rem 0.5rem" }}>Save</button>
								<button onClick={cancelEdit} style={{ ...selectStyle, backgroundColor: "#ef4444", padding: "0.25rem 0.5rem" }}>Cancel</button>
							</div>
						</div>
					) : (
						<div
							onClick={(e) => startEditing("description", task.description || "", e)}
							style={{
								fontSize: "0.8rem",
								color: "#94a3b8",
								whiteSpace: "pre-wrap",
								cursor: "text",
								minHeight: "20px",
							}}
							title="Click to edit description"
						>
							{task.description ? (
								<>
									{task.description.slice(0, 500)}
									{task.description.length > 500 && "..."}
								</>
							) : (
								<span style={{ fontStyle: "italic", color: "#64748b" }}>Click to add description...</span>
							)}
						</div>
					)}
				</div>
			)}

			{/* Assignees */}
			{task.assignee && task.assignee.length > 0 && (
				<div style={{ marginTop: "0.5rem", fontSize: "0.75rem", color: "#64748b" }}>
					{task.assignee.join(", ")}
				</div>
			)}
		</div>
	);
}

// D3 Dependency Graph Component
interface DependencyGraphProps {
	tasks: AggregatedTask[];
	projects: Project[];
	selectedProjects: Set<string> | null;
	filter: string;
	showCompleted: boolean;
}

interface D3Node extends d3.SimulationNodeDatum {
	id: string;
	title: string;
	status: string;
	priority?: string;
	projectName: string;
	projectColor: string;
}

interface D3Edge extends d3.SimulationLinkDatum<D3Node> {
	source: string | D3Node;
	target: string | D3Node;
}

function getNodeColor(status: string): string {
	const lower = status.toLowerCase();
	if (lower.includes("done") || lower.includes("complete")) return "#22c55e";
	if (lower.includes("progress")) return "#3b82f6";
	return "#6b7280";
}

function isCompleted(status: string): boolean {
	const lower = status.toLowerCase();
	return lower.includes("done") || lower.includes("complete");
}

function DependencyGraph({ tasks, projects, selectedProjects, filter, showCompleted }: DependencyGraphProps) {
	const svgRef = useRef<SVGSVGElement>(null);
	const containerRef = useRef<HTMLDivElement>(null);
	const [dimensions, setDimensions] = useState({ width: 800, height: 600 });

	// Filter tasks
	const filteredTasks = useMemo(() => {
		return tasks.filter((t) => {
			if (!showCompleted && isCompleted(t.status)) return false;
			if (selectedProjects !== null && !selectedProjects.has(t.projectName)) return false;
			if (filter && !t.title.toLowerCase().includes(filter.toLowerCase())) return false;
			return true;
		});
	}, [tasks, showCompleted, selectedProjects, filter]);

	// Build graph data - note: aggregated tasks don't have dependencies in YAML
	// So we'll just show all tasks as nodes without edges for now
	const d3Data = useMemo(() => {
		const nodes: D3Node[] = filteredTasks.map((t) => ({
			id: `${t.projectPath}:${t.id}`,
			title: t.title,
			status: t.status,
			priority: t.priority,
			projectName: t.projectName,
			projectColor: t.projectColor,
		}));
		// No edges since aggregated tasks don't track dependencies across projects
		const edges: D3Edge[] = [];
		return { nodes, edges };
	}, [filteredTasks]);

	// Handle resize
	useEffect(() => {
		const updateDimensions = () => {
			if (containerRef.current) {
				const rect = containerRef.current.getBoundingClientRect();
				setDimensions({
					width: Math.max(600, rect.width),
					height: Math.max(500, 600),
				});
			}
		};
		updateDimensions();
		window.addEventListener("resize", updateDimensions);
		return () => window.removeEventListener("resize", updateDimensions);
	}, []);

	// D3 visualization
	useEffect(() => {
		if (!svgRef.current || d3Data.nodes.length === 0) return;

		const svg = d3.select(svgRef.current);
		svg.selectAll("*").remove();

		const { width, height } = dimensions;
		const g = svg.append("g").attr("class", "graph-container");

		// Zoom behavior
		const zoom = d3.zoom<SVGSVGElement, unknown>()
			.scaleExtent([0.1, 4])
			.on("zoom", (event) => g.attr("transform", event.transform));
		svg.call(zoom);
		svg.on("dblclick.zoom", () => svg.transition().duration(500).call(zoom.transform, d3.zoomIdentity));

		// Create copies for simulation
		const nodesCopy: D3Node[] = d3Data.nodes.map((d) => ({ ...d }));
		const edgesCopy: D3Edge[] = d3Data.edges.map((d) => ({ ...d }));

		// Force simulation
		const simulation = d3.forceSimulation<D3Node>(nodesCopy)
			.force("link", d3.forceLink<D3Node, D3Edge>(edgesCopy).id((d) => d.id).distance(150))
			.force("charge", d3.forceManyBody().strength(-400))
			.force("center", d3.forceCenter(width / 2, height / 2))
			.force("collision", d3.forceCollide().radius(45));

		// Draw edges
		const links = g.append("g").attr("class", "links")
			.selectAll("line").data(edgesCopy).join("line")
			.attr("stroke", "#9ca3af").attr("stroke-width", 2).style("opacity", 0.6);

		// Draw nodes
		const nodes = g.append("g").attr("class", "nodes")
			.selectAll<SVGGElement, D3Node>("g").data(nodesCopy).join("g")
			.attr("class", "node").attr("cursor", "grab");

		// Node circles with project color border
		nodes.append("circle")
			.attr("r", 28)
			.attr("fill", (d) => getNodeColor(d.status))
			.attr("stroke", (d) => d.projectColor)
			.attr("stroke-width", 3);

		// Progress fill for completed
		nodes.filter((d) => isCompleted(d.status))
			.append("circle").attr("r", 0).attr("fill", "#16a34a")
			.transition().duration(800).ease(d3.easeElastic).attr("r", 24);

		// Task ID label
		nodes.append("text")
			.attr("text-anchor", "middle").attr("dy", 4)
			.attr("fill", "white").attr("font-size", "10px").attr("font-weight", "bold")
			.attr("pointer-events", "none")
			.text((d) => d.id.split(":").pop()?.replace("task-", "") || "");

		// Title below node
		nodes.append("text")
			.attr("text-anchor", "middle").attr("dy", 45)
			.attr("fill", "#94a3b8").attr("font-size", "9px")
			.attr("pointer-events", "none")
			.text((d) => d.title.length > 18 ? `${d.title.substring(0, 18)}...` : d.title);

		// Project name above node
		nodes.append("text")
			.attr("text-anchor", "middle").attr("dy", -35)
			.attr("fill", (d) => d.projectColor).attr("font-size", "8px").attr("font-weight", "bold")
			.attr("pointer-events", "none")
			.text((d) => d.projectName);

		// Drag behavior
		const drag = d3.drag<SVGGElement, D3Node>()
			.on("start", (event, d) => {
				if (!event.active) simulation.alphaTarget(0.3).restart();
				d.fx = d.x; d.fy = d.y;
			})
			.on("drag", (event, d) => { d.fx = event.x; d.fy = event.y; })
			.on("end", (event, d) => {
				if (!event.active) simulation.alphaTarget(0);
			});
		nodes.call(drag);

		// Double-click to unpin
		nodes.on("dblclick", (event, d) => {
			event.stopPropagation();
			d.fx = null; d.fy = null;
			simulation.alpha(0.3).restart();
		});

		// Hover effects
		nodes.on("mouseenter", function() {
			d3.select(this).select("circle").attr("stroke-width", 5);
		}).on("mouseleave", function() {
			d3.select(this).select("circle").attr("stroke-width", 3);
		});

		// Tick
		simulation.on("tick", () => {
			links
				.attr("x1", (d) => (d.source as D3Node).x || 0)
				.attr("y1", (d) => (d.source as D3Node).y || 0)
				.attr("x2", (d) => (d.target as D3Node).x || 0)
				.attr("y2", (d) => (d.target as D3Node).y || 0);
			nodes.attr("transform", (d) => `translate(${d.x || 0},${d.y || 0})`);
		});

		// Initial zoom to fit
		setTimeout(() => {
			const bounds = (g.node() as SVGGElement)?.getBBox();
			if (bounds && bounds.width > 0) {
				const scale = Math.min(0.9, Math.min(width / (bounds.width + 100), height / (bounds.height + 100)));
				const translateX = width / 2 - (bounds.x + bounds.width / 2) * scale;
				const translateY = height / 2 - (bounds.y + bounds.height / 2) * scale;
				svg.transition().duration(500).call(zoom.transform, d3.zoomIdentity.translate(translateX, translateY).scale(scale));
			}
		}, 500);

		return () => { simulation.stop(); };
	}, [d3Data, dimensions]);

	if (filteredTasks.length === 0) {
		return (
			<div style={{
				display: "flex", alignItems: "center", justifyContent: "center",
				height: "400px", backgroundColor: "#1e293b", borderRadius: "0.5rem",
				color: "#64748b", fontSize: "1rem"
			}}>
				No tasks to display. Try adjusting your filters.
			</div>
		);
	}

	return (
		<div>
			<div style={{ marginBottom: "0.5rem", color: "#94a3b8", fontSize: "0.875rem" }}>
				{filteredTasks.length} task{filteredTasks.length !== 1 ? "s" : ""} • Drag to move • Scroll to zoom • Double-click to unpin
			</div>
			<div ref={containerRef} style={{ backgroundColor: "#1e293b", borderRadius: "0.5rem", overflow: "hidden" }}>
				<svg ref={svgRef} width={dimensions.width} height={dimensions.height} style={{ display: "block" }} />
			</div>
			{/* Legend */}
			<div style={{ display: "flex", gap: "1.5rem", marginTop: "1rem", fontSize: "0.75rem", color: "#94a3b8" }}>
				<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
					<span style={{ width: "12px", height: "12px", borderRadius: "50%", backgroundColor: "#22c55e" }} />
					Done
				</div>
				<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
					<span style={{ width: "12px", height: "12px", borderRadius: "50%", backgroundColor: "#3b82f6" }} />
					In Progress
				</div>
				<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
					<span style={{ width: "12px", height: "12px", borderRadius: "50%", backgroundColor: "#6b7280" }} />
					To Do
				</div>
				<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
					Border color = Project
				</div>
			</div>
		</div>
	);
}

// Mount the app
const root = createRoot(document.getElementById("root")!);
root.render(<App />);
