import { useRef, useState, useCallback } from "react";
import { useQuery } from "@tanstack/react-query";
import { Play, Loader2, Trash2, Terminal } from "lucide-react";
import { Button } from "./ui/button";
import { cn } from "~/client/lib/utils";
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from "./ui/sheet";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
import { Label } from "./ui/label";
import { Textarea } from "./ui/textarea";
import { listRepositoriesOptions } from "~/client/api-client/@tanstack/react-query.gen";
import { devPanelExec } from "~/client/api-client/sdk.gen";
import { parseError } from "../lib/errors";

type DevPanelProps = {
	open: boolean;
	onOpenChange: (open: boolean) => void;
};

type SseOutputEvent = {
	type: "stdout" | "stderr";
	line: string;
};

type SseDoneEvent = {
	type: "done";
	exitCode: number;
};

type SseErrorEvent = {
	type: "error";
	message: string;
};

type SseEvent = SseOutputEvent | SseDoneEvent | SseErrorEvent;

export function DevPanel({ open, onOpenChange }: DevPanelProps) {
	const { data: repositories = [] } = useQuery({
		...listRepositoriesOptions(),
		enabled: open,
	});

	const [selectedRepoId, setSelectedRepoId] = useState<string>("");
	const [commandLine, setCommandLine] = useState("snapshots");
	const [output, setOutput] = useState<string[]>([]);
	const [isRunning, setIsRunning] = useState(false);
	const abortControllerRef = useRef<AbortController | null>(null);
	const outputRef = useRef<HTMLDivElement>(null);

	const scrollToBottom = useCallback(() => {
		if (outputRef.current) {
			outputRef.current.scrollTop = outputRef.current.scrollHeight;
		}
	}, []);

	const appendOutput = useCallback(
		(line: string) => {
			setOutput((prev) => {
				const newOutput = [...prev, line];
				setTimeout(scrollToBottom, 0);
				return newOutput;
			});
		},
		[scrollToBottom],
	);

	const handleRun = async () => {
		if (!selectedRepoId || !commandLine.trim()) {
			return;
		}

		setOutput([]);
		setIsRunning(true);
		appendOutput(`$ restic ${commandLine}`.trim());
		appendOutput("---");

		abortControllerRef.current = new AbortController();

		const trimmedLine = commandLine.trim();
		const parts = trimmedLine.split(/\s+/);
		const command = parts[0];
		const argsArray = parts.slice(1);

		try {
			const result = await devPanelExec({
				path: { shortId: selectedRepoId },
				body: { command, args: argsArray.length > 0 ? argsArray : undefined },
				signal: abortControllerRef.current.signal,
			});

			for await (const event of result.stream) {
				if (abortControllerRef.current.signal.aborted) {
					break;
				}
				const data = event as unknown as SseEvent;

				if (!data || typeof data !== "object") {
					continue;
				}

				if (data.type === "stdout" || data.type === "stderr") {
					appendOutput(data.line);
				} else if (data.type === "done") {
					appendOutput(`---`);
					appendOutput(`Command finished with exit code: ${data.exitCode}`);
				} else if (data.type === "error") {
					appendOutput(`Error: ${data.message}`);
				}
			}
		} catch (err) {
			if (err instanceof Error && err.name === "AbortError") {
				appendOutput("---");
				appendOutput("Command cancelled");
			} else {
				appendOutput(`Error: ${parseError(err)?.message}`);
			}
		} finally {
			setIsRunning(false);
			abortControllerRef.current = null;
		}
	};

	const handleCancel = () => {
		abortControllerRef.current?.abort();
	};

	const handleClear = () => {
		setOutput([]);
	};

	const handleClose = () => {
		if (isRunning) {
			handleCancel();
		}
		onOpenChange(false);
	};

	return (
		<Sheet open={open} onOpenChange={handleClose}>
			<SheetContent side="right" className="w-full sm:max-w-xl flex flex-col px-4">
				<SheetHeader>
					<SheetTitle className="flex items-center gap-2">
						<Terminal className="h-5 w-5" />
						Dev Panel
					</SheetTitle>
					<SheetDescription>Execute restic commands against a repository</SheetDescription>
				</SheetHeader>

				<div className="flex flex-col gap-4 flex-1 min-h-0 mt-4">
					<div className="grid gap-4">
						<div className="grid gap-2">
							<Label htmlFor="repository">Repository</Label>
							<Select value={selectedRepoId} onValueChange={setSelectedRepoId}>
								<SelectTrigger id="repository">
									<SelectValue placeholder="Select a repository" />
								</SelectTrigger>
								<SelectContent>
									{repositories.map((repo) => (
										<SelectItem key={repo.id} value={repo.shortId}>
											{repo.name} ({repo.type})
										</SelectItem>
									))}
								</SelectContent>
							</Select>
						</div>

						<div className="grid gap-2">
							<Label htmlFor="command">Command</Label>
							<Textarea
								id="command"
								placeholder="e.g., snapshots, check --dry-run, forget --keep-last 10"
								value={commandLine}
								onChange={(e) => setCommandLine(e.target.value)}
								className="font-mono text-sm"
								rows={2}
							/>
						</div>

						<div className="flex gap-2">
							<Button onClick={handleRun} disabled={isRunning || !selectedRepoId || !commandLine.trim()}>
								<Loader2 className={cn("h-4 w-4 animate-spin mr-2", { hidden: !isRunning })} />
								<Play className={cn("h-4 w-4 mr-2", { hidden: isRunning })} />
								<span className={cn({ hidden: !isRunning })}>Running...</span>
								<span className={cn({ hidden: isRunning })}>Run</span>
							</Button>
							<Button variant="destructive" onClick={handleCancel} className={cn({ hidden: !isRunning })}>
								Cancel
							</Button>
							<Button variant="outline" onClick={handleClear} disabled={isRunning}>
								<Trash2 className="h-4 w-4 mr-2" />
								Clear
							</Button>
						</div>
					</div>

					<div className="flex-1 min-h-0 border rounded-md bg-muted/30">
						<div ref={outputRef} className="h-full overflow-auto p-3 font-mono text-xs">
							<div className={cn("text-muted-foreground", { hidden: output.length > 0 })}>
								Output will appear here...
							</div>
							<div className={cn("space-y-0.5", { hidden: output.length === 0 })}>
								{output.map((line, i) => {
									let displayLine = line;
									let isJson = false;
									try {
										const parsed = JSON.parse(line);
										displayLine = JSON.stringify(parsed, null, 2);
										isJson = true;
									} catch {
										// Not valid JSON, display as-is
									}
									return (
										<pre
											key={`${i}-${line.slice(0, 20)}`}
											className={cn("whitespace-pre-wrap break-all text-xs", {
												"wrap-break-word text-[10px] leading-tight": isJson,
											})}
										>
											{displayLine}
										</pre>
									);
								})}
							</div>
						</div>
					</div>
				</div>
			</SheetContent>
		</Sheet>
	);
}
