"use client";

import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useState, useCallback, useRef, useEffect } from "react";
import Image from "next/image";
import { toast } from "sonner";
import { CheckCircle2, XCircle, Circle } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { timeAgo } from "@/lib/time";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";

// ── Types ───────────────────────────────────────────────────

type ModSide = "client" | "server" | "both";

type ModMeta = {
  modId: string;
  displayName: string;
  version: string;
  filename: string;
  size: string;
  side: ModSide;
};

type SearchResult = {
  slug: string;
  title: string;
  description: string;
  icon_url: string;
  downloads: number;
  project_id: string;
  author: string;
  date_modified: string;
  follows: number;
};

type ModDownload = {
  projectId: string;
  versionId: string;
  title: string;
  filename: string;
  url: string;
  isDependency: boolean;
  alreadyInstalled: boolean;
  side: ModSide;
};

type ResolveResult = {
  toInstall: ModDownload[];
  skipped: ModDownload[];
  conflicts: string[];
};

type SnapshotInfo = {
  name: string;
  dirName: string;
  createdAt: string;
  modCount: number;
  mods: string[];
};

type WizardStep = "idle" | "searching" | "reviewing" | "installing";

type TimelineStep = {
  id: string;
  label: string;
  status: "pending" | "active" | "done" | "error";
  message?: string;
};

const INSTALL_STEPS_SERVER: Pick<TimelineStep, "id" | "label">[] = [
  { id: "snapshot", label: "Create snapshot" },
  { id: "download", label: "Download mods" },
  { id: "restart", label: "Restart server" },
  { id: "health", label: "Verify server" },
  { id: "modpack", label: "Update modpack" },
];

const INSTALL_STEPS_CLIENT: Pick<TimelineStep, "id" | "label">[] = [
  { id: "snapshot", label: "Create snapshot" },
  { id: "download", label: "Download mods" },
  { id: "modpack", label: "Update modpack" },
];

const UPDATE_STEPS_SERVER: Pick<TimelineStep, "id" | "label">[] = [
  { id: "resolve", label: "Resolve latest versions" },
  { id: "snapshot", label: "Create snapshot" },
  { id: "download", label: "Download updates" },
  { id: "restart", label: "Restart server" },
  { id: "health", label: "Verify server" },
  { id: "modpack", label: "Update modpack" },
];

async function consumeSSE(
  res: Response,
  onStep: (data: { id: string; status: TimelineStep["status"]; message?: string }) => void
): Promise<{ success: boolean; message: string; installed?: string[]; rolledBack?: boolean }> {
  if (!res.body) throw new Error("No response stream");
  const reader = res.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  let finalResult: { success: boolean; message: string; installed?: string[]; rolledBack?: boolean } | null = null;

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const frames = buffer.split("\n\n");
    buffer = frames.pop()!;
    for (const frame of frames) {
      if (!frame.trim()) continue;
      const eventMatch = frame.match(/^event:\s*(\w+)/m);
      const dataMatch = frame.match(/^data:\s*(.+)/m);
      if (!eventMatch || !dataMatch) continue;
      const ev = eventMatch[1];
      const data = JSON.parse(dataMatch[1]);
      if (ev === "step") onStep(data);
      else if (ev === "done") finalResult = data;
    }
  }
  if (!finalResult) throw new Error("Stream ended without result");
  return finalResult;
}

// ── Side Badge ──────────────────────────────────────────────

const sideConfig = {
  client: { label: "Client", className: "border-purple-500/30 text-purple-400" },
  server: { label: "Server", className: "border-orange-500/30 text-orange-400" },
  both: { label: "Both", className: "border-green-500/30 text-green-400" },
} as const;

function SideBadge({ side }: { side: ModSide }) {
  const config = sideConfig[side];
  return (
    <Badge variant="outline" className={`text-xs px-1.5 py-0 ${config.className}`}>
      {config.label}
    </Badge>
  );
}

// ── Component ───────────────────────────────────────────────

export function ModManager() {
  const queryClient = useQueryClient();
  const [step, setStep] = useState<WizardStep>("idle");
  const [searchQuery, setSearchQuery] = useState("");
  const [selected, setSelected] = useState<Map<string, SearchResult>>(new Map());
  const [resolved, setResolved] = useState<ResolveResult | null>(null);
  const [installStatus, setInstallStatus] = useState("");
  const [timelineSteps, setTimelineSteps] = useState<TimelineStep[]>([]);
  const [newlyInstalled, setNewlyInstalled] = useState<Set<string>>(new Set());
  const [installResult, setInstallResult] = useState<{
    success: boolean;
    message: string;
    rolledBack?: boolean;
  } | null>(null);
  const [confirmRemove, setConfirmRemove] = useState<string | null>(null);
  const [confirmRestore, setConfirmRestore] = useState<string | null>(null);
  const [confirmDeleteSnap, setConfirmDeleteSnap] = useState<string | null>(null);
  const [installedQuery, setInstalledQuery] = useState("");
  const [sideFilter, setSideFilter] = useState<"all" | ModSide>("all");
  const debounceRef = useRef<ReturnType<typeof setTimeout>>(null);

  // Installed mods
  const { data: mods = [] } = useQuery<ModMeta[]>({
    queryKey: ["mods"],
    queryFn: () => fetch("/api/mods").then((r) => r.json()),
    staleTime: 30_000,
  });

  // Updates available (from Modrinth)
  const { data: updates = [] } = useQuery<{ filename: string; latestFilename: string; dateModified: string }[]>({
    queryKey: ["mod-updates"],
    queryFn: () =>
      fetch("/api/mods/updates").then((r) => (r.ok ? r.json() : [])),
    staleTime: 15 * 60 * 1000,
    refetchOnMount: false,
  });

  const updateMap = new Map(updates.map((u) => [u.filename, u]));

  // Search (debounced)
  const [debouncedQuery, setDebouncedQuery] = useState("");

  useEffect(() => {
    if (debounceRef.current) clearTimeout(debounceRef.current);
    debounceRef.current = setTimeout(() => {
      setDebouncedQuery(searchQuery);
    }, 300);
    return () => { if (debounceRef.current) clearTimeout(debounceRef.current); };
  }, [searchQuery]);

  // Global Esc: clear confirm prompts and exit non-installing wizard steps
  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if (e.key !== "Escape") return;
      if (confirmRemove || confirmRestore || confirmDeleteSnap) {
        setConfirmRemove(null);
        setConfirmRestore(null);
        setConfirmDeleteSnap(null);
        return;
      }
      if (step === "searching" || step === "reviewing") {
        setStep("idle");
        setSelected(new Map());
        setResolved(null);
        setSearchQuery("");
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [confirmRemove, confirmRestore, confirmDeleteSnap, step]);

  const { data: searchResults = [], isFetching: isSearching } = useQuery<SearchResult[]>({
    queryKey: ["mod-search", debouncedQuery],
    queryFn: () =>
      fetch(`/api/mods/search?q=${encodeURIComponent(debouncedQuery)}`).then((r) =>
        r.json()
      ),
    enabled: debouncedQuery.length >= 2 && step === "searching",
    staleTime: 60_000,
  });

  // Snapshots
  const { data: snapshots = [] } = useQuery<SnapshotInfo[]>({
    queryKey: ["snapshots"],
    queryFn: () => fetch("/api/snapshots").then((r) => r.json()),
    staleTime: 30_000,
  });

  // Resolve dependencies
  const resolve = useMutation({
    mutationFn: async () => {
      const projectIds = Array.from(selected.keys());
      const titles: Record<string, string> = {};
      selected.forEach((v, k) => (titles[k] = v.title));

      const res = await fetch("/api/mods/resolve", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ projectIds, titles }),
      });
      return (await res.json()) as ResolveResult;
    },
    onSuccess: (data) => {
      setResolved(data);
      setStep("reviewing");
    },
  });

  // Batch install (SSE streaming)
  const install = useMutation({
    mutationFn: async () => {
      if (!resolved) throw new Error("No resolved mods");

      const modsToInstall = resolved.toInstall.filter((m) => !m.alreadyInstalled);
      const snapshotName = `before-${modsToInstall.map((m) => m.title).join("-").slice(0, 40)}`;
      const hasServerMods = modsToInstall.some((m) => m.side !== "client");

      // Initialize timeline
      const steps = (hasServerMods ? INSTALL_STEPS_SERVER : INSTALL_STEPS_CLIENT)
        .map((s) => ({ ...s, status: "pending" as const }));
      setTimelineSteps(steps);
      setInstallStatus("Starting installation...");

      const res = await fetch("/api/mods/batch-install", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ mods: modsToInstall, snapshotName }),
      });

      if (!res.body) throw new Error("No response stream");

      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buffer = "";
      let finalResult: { success: boolean; message: string; installed?: string[]; rolledBack?: boolean } | null = null;

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });

        const frames = buffer.split("\n\n");
        buffer = frames.pop()!;

        for (const frame of frames) {
          if (!frame.trim()) continue;
          const eventMatch = frame.match(/^event:\s*(\w+)/m);
          const dataMatch = frame.match(/^data:\s*(.+)/m);
          if (!eventMatch || !dataMatch) continue;

          const event = eventMatch[1];
          const data = JSON.parse(dataMatch[1]);

          if (event === "step") {
            setTimelineSteps((prev) => {
              const exists = prev.some((s) => s.id === data.id);
              if (exists) {
                return prev.map((s) =>
                  s.id === data.id ? { ...s, status: data.status, message: data.message } : s
                );
              }
              // Dynamically add rollback step
              return [...prev, { id: data.id, label: "Rollback", status: data.status, message: data.message }];
            });
            setInstallStatus(data.message);
          } else if (event === "done") {
            finalResult = data;
          }
        }
      }

      if (!finalResult) throw new Error("Stream ended without result");
      return finalResult;
    },
    onSuccess: (data) => {
      setInstallStatus("");
      setInstallResult(data);
      if (data.success) {
        setStep("idle");
        setSelected(new Map());
        setResolved(null);
        setTimelineSteps([]);
        if (data.installed?.length) {
          setNewlyInstalled(new Set(data.installed));
        }
        toast.success(data.message || "Mods installed");
      } else {
        toast.error(data.message || "Install failed", {
          description: data.rolledBack ? "Changes rolled back." : undefined,
        });
      }
      queryClient.invalidateQueries({ queryKey: ["mods"] });
      queryClient.invalidateQueries({ queryKey: ["status"] });
      queryClient.invalidateQueries({ queryKey: ["snapshots"] });
    },
    onError: (err) => {
      setInstallStatus("");
      setTimelineSteps([]);
      setInstallResult({ success: false, message: err.message });
      toast.error("Install failed", { description: err.message });
    },
  });

  // Update mod(s)
  const updateMods = useMutation({
    mutationFn: async (filenames: string[]) => {
      const hasServerMod = filenames.some((f) => {
        const m = mods.find((x) => x.filename === f);
        return m ? m.side !== "client" : true;
      });
      const steps = (hasServerMod ? UPDATE_STEPS_SERVER : INSTALL_STEPS_CLIENT)
        .map((s) => ({ ...s, status: "pending" as const }));
      setTimelineSteps(steps);
      setInstallStatus("Starting update...");

      const res = await fetch("/api/mods/update", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ filenames }),
      });

      return consumeSSE(res, (data) => {
        setTimelineSteps((prev) => {
          const exists = prev.some((s) => s.id === data.id);
          if (exists) {
            return prev.map((s) =>
              s.id === data.id
                ? { ...s, status: data.status, message: data.message }
                : s
            );
          }
          return [
            ...prev,
            {
              id: data.id,
              label: data.id === "rollback" ? "Rollback" : data.id,
              status: data.status,
              message: data.message,
            },
          ];
        });
        setInstallStatus(data.message || "");
      });
    },
    onSuccess: (data) => {
      setInstallStatus("");
      setInstallResult(data);
      if (data.success) {
        setTimelineSteps([]);
        if (data.installed?.length) {
          setNewlyInstalled(new Set(data.installed));
        }
        toast.success(data.message || "Mods updated");
      } else {
        toast.error(data.message || "Update failed", {
          description: data.rolledBack ? "Changes rolled back." : undefined,
        });
      }
      queryClient.invalidateQueries({ queryKey: ["mods"] });
      queryClient.invalidateQueries({ queryKey: ["mod-updates"] });
      queryClient.invalidateQueries({ queryKey: ["status"] });
      queryClient.invalidateQueries({ queryKey: ["snapshots"] });
    },
    onError: (err) => {
      setInstallStatus("");
      setTimelineSteps([]);
      setInstallResult({ success: false, message: err.message });
      toast.error("Update failed", { description: err.message });
    },
  });

  // Remove mod
  const removeMod = useMutation({
    mutationFn: async (filename: string) => {
      setConfirmRemove(null);
      const res = await fetch("/api/mods/remove", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ filename }),
      });
      return res.json();
    },
    onSuccess: (data) => {
      setInstallResult(data);
      (data.success ? toast.success : toast.error)(data.message || "Mod removed");
      queryClient.invalidateQueries({ queryKey: ["mods"] });
      queryClient.invalidateQueries({ queryKey: ["status"] });
      queryClient.invalidateQueries({ queryKey: ["snapshots"] });
    },
    onError: (err) => toast.error("Remove failed", { description: err.message }),
  });

  // Restore snapshot
  const restoreSnap = useMutation({
    mutationFn: async (dirName: string) => {
      setConfirmRestore(null);
      const res = await fetch("/api/snapshots/restore", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ dirName }),
      });
      return res.json();
    },
    onSuccess: (data) => {
      setInstallResult(data);
      (data.success ? toast.success : toast.error)(data.message || "Snapshot restored");
      queryClient.invalidateQueries({ queryKey: ["mods"] });
      queryClient.invalidateQueries({ queryKey: ["status"] });
      queryClient.invalidateQueries({ queryKey: ["snapshots"] });
    },
    onError: (err) => toast.error("Restore failed", { description: err.message }),
  });

  // Delete snapshot
  const deleteSnap = useMutation({
    mutationFn: async (dirName: string) => {
      await fetch("/api/snapshots", {
        method: "DELETE",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ dirName }),
      });
    },
    onSuccess: () => {
      toast.success("Snapshot deleted");
      queryClient.invalidateQueries({ queryKey: ["snapshots"] });
    },
    onError: (err) => toast.error("Delete failed", { description: err.message }),
  });

  const toggleSelect = useCallback((result: SearchResult) => {
    setSelected((prev) => {
      const next = new Map(prev);
      if (next.has(result.project_id)) {
        next.delete(result.project_id);
      } else {
        next.set(result.project_id, result);
      }
      return next;
    });
  }, []);

  const isBusy =
    install.isPending ||
    removeMod.isPending ||
    restoreSnap.isPending ||
    updateMods.isPending;
  const showTimeline =
    (step === "installing" || updateMods.isPending) && timelineSteps.length > 0;

  const formatDownloads = (n: number) => {
    if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
    if (n >= 1_000) return `${(n / 1_000).toFixed(0)}K`;
    return n.toString();
  };

  return (
    <div className="space-y-4 sm:space-y-6">
      {/* ── Mod Manager Card ──────────────────────────────── */}
      <Card>
        <CardHeader>
          <div className="flex items-center justify-between">
            <div>
              <CardTitle>Mod Manager</CardTitle>
              <CardDescription>
                Search Modrinth, auto-resolve dependencies, install with rollback safety
              </CardDescription>
            </div>
            {step === "idle" && (
              <Button onClick={() => setStep("searching")} disabled={isBusy}>
                Add Mods
              </Button>
            )}
            {step !== "idle" && step !== "installing" && (
              <Button
                variant="ghost"
                onClick={() => {
                  setStep("idle");
                  setSelected(new Map());
                  setResolved(null);
                  setSearchQuery("");
                }}
              >
                Cancel
              </Button>
            )}
          </div>
        </CardHeader>

        <CardContent className="space-y-4">
          {/* Step indicator */}
          {step !== "idle" && (
            <ol className="flex items-center gap-2 text-xs">
              {[
                { key: "searching", label: "Search" },
                { key: "reviewing", label: "Review" },
                { key: "installing", label: "Install" },
              ].map((s, i, arr) => {
                const currentIdx = arr.findIndex((x) => x.key === step);
                const thisIdx = i;
                const state =
                  thisIdx < currentIdx ? "done" : thisIdx === currentIdx ? "active" : "pending";
                return (
                  <li key={s.key} className="flex items-center gap-2">
                    <span
                      className={`flex h-5 w-5 items-center justify-center rounded-full border text-[10px] font-semibold ${
                        state === "active"
                          ? "border-primary bg-primary text-primary-foreground"
                          : state === "done"
                          ? "border-emerald-500/40 bg-emerald-500/10 text-emerald-300"
                          : "border-border text-muted-foreground"
                      }`}
                    >
                      {thisIdx + 1}
                    </span>
                    <span
                      className={
                        state === "active"
                          ? "font-medium text-foreground"
                          : "text-muted-foreground"
                      }
                    >
                      {s.label}
                    </span>
                    {i < arr.length - 1 && (
                      <span className="w-6 h-px bg-border mx-1" />
                    )}
                  </li>
                );
              })}
            </ol>
          )}

          {/* Result feedback */}
          {installResult && !isBusy && step === "idle" && (
            <Alert
              className={
                installResult.success
                  ? "border-emerald-500/20 bg-emerald-500/5"
                  : "border-red-500/20 bg-red-500/5"
              }
            >
              <AlertDescription
                className={installResult.success ? "text-emerald-300" : "text-red-300"}
              >
                {installResult.message}
                {installResult.rolledBack && (
                  <span className="block text-amber-300 mt-1 text-xs">
                    Changes were automatically rolled back.
                  </span>
                )}
              </AlertDescription>
            </Alert>
          )}

          {/* ── Step 1: Search & Select ─────────────────── */}
          {step === "searching" && (
            <div className="space-y-4">
              <Input
                placeholder="Search mods on Modrinth..."
                value={searchQuery}
                onChange={(e) => setSearchQuery(e.target.value)}
                autoFocus
              />

              {/* Selected queue */}
              {selected.size > 0 && (
                <div className="flex flex-wrap gap-1.5">
                  {Array.from(selected.values()).map((s) => (
                    <Badge
                      key={s.project_id}
                      variant="secondary"
                      className="gap-1 cursor-pointer hover:bg-destructive/20"
                      onClick={() => toggleSelect(s)}
                    >
                      {s.title}
                      <span className="text-muted-foreground">x</span>
                    </Badge>
                  ))}
                </div>
              )}

              {/* Search results */}
              {isSearching && (
                <p className="text-sm text-muted-foreground">Searching...</p>
              )}

              {debouncedQuery.length >= 2 && (
                <ul className="space-y-1 max-h-[350px] overflow-y-auto">
                  {searchResults.map((result) => {
                    const isSelected = selected.has(result.project_id);
                    return (
                      <li
                        key={result.project_id}
                        onClick={() => toggleSelect(result)}
                        className={`flex items-center gap-3 p-3 rounded-lg cursor-pointer transition ${
                          isSelected
                            ? "bg-primary/10 border border-primary/30"
                            : "bg-muted/50 hover:bg-muted border border-transparent"
                        }`}
                      >
                        {result.icon_url ? (
                          <Image
                            src={result.icon_url}
                            alt=""
                            width={40}
                            height={40}
                            unoptimized
                            className="w-10 h-10 rounded-md shrink-0 bg-muted object-cover"
                          />
                        ) : (
                          <div className="w-10 h-10 rounded-md bg-muted shrink-0" />
                        )}
                        <div className="flex-1 min-w-0">
                          <div className="flex items-center gap-2 flex-wrap">
                            <span className="text-sm font-medium truncate">
                              {result.title}
                            </span>
                            {result.author && (
                              <span className="text-xs text-muted-foreground shrink-0">
                                by {result.author}
                              </span>
                            )}
                          </div>
                          <p className="text-xs text-muted-foreground truncate">
                            {result.description}
                          </p>
                          <div className="flex items-center gap-2 mt-0.5 text-[10px] text-muted-foreground/80 tabular-nums">
                            <span>{formatDownloads(result.downloads)} downloads</span>
                            {result.date_modified && (
                              <>
                                <span aria-hidden>·</span>
                                <span title={new Date(result.date_modified).toLocaleString()}>
                                  updated {timeAgo(result.date_modified)}
                                </span>
                              </>
                            )}
                          </div>
                        </div>
                        {isSelected && (
                          <Badge variant="default" className="shrink-0 text-xs">
                            Selected
                          </Badge>
                        )}
                      </li>
                    );
                  })}
                </ul>
              )}

              {/* Next button */}
              {selected.size > 0 && (
                <div className="flex justify-end">
                  <Button
                    onClick={() => resolve.mutate()}
                    disabled={resolve.isPending}
                  >
                    {resolve.isPending
                      ? "Resolving dependencies..."
                      : `Next (${selected.size} selected)`}
                  </Button>
                </div>
              )}
            </div>
          )}

          {/* ── Step 2: Review & Validate ───────────────── */}
          {step === "reviewing" && resolved && (
            <div className="space-y-4">
              {/* Conflicts */}
              {resolved.conflicts.length > 0 && (
                <Alert className="border-red-500/20 bg-red-500/5">
                  <AlertDescription className="text-red-300">
                    <strong>Issues found:</strong>
                    <ul className="list-disc pl-4 mt-1 space-y-0.5">
                      {resolved.conflicts.map((c, i) => (
                        <li key={i}>{c}</li>
                      ))}
                    </ul>
                  </AlertDescription>
                </Alert>
              )}

              {/* Mods to install */}
              {resolved.toInstall.length > 0 && (
                <div>
                  <h3 className="text-sm font-semibold mb-2">
                    Will be installed ({resolved.toInstall.length})
                  </h3>
                  <ul className="space-y-1">
                    {resolved.toInstall.map((mod) => (
                      <li
                        key={mod.projectId}
                        className="px-3 py-2 rounded-md bg-muted/50"
                      >
                        <div className="flex items-center gap-2 flex-wrap">
                          <span className="text-sm font-medium">
                            {mod.title}
                          </span>
                          <SideBadge side={mod.side} />
                          {mod.isDependency && (
                            <Badge
                              variant="outline"
                              className="text-xs px-1.5 py-0 border-blue-500/30 text-blue-300"
                            >
                              dependency
                            </Badge>
                          )}
                        </div>
                        <p className="text-xs text-muted-foreground truncate">
                          {mod.filename}
                        </p>
                      </li>
                    ))}
                  </ul>
                </div>
              )}

              {/* Skipped (already installed) */}
              {resolved.skipped.length > 0 && (
                <div>
                  <h3 className="text-sm font-semibold mb-2 text-muted-foreground">
                    Already installed (skipped)
                  </h3>
                  <ul className="space-y-1">
                    {resolved.skipped.map((mod) => (
                      <li
                        key={mod.projectId}
                        className="flex items-center gap-2 px-3 py-2 rounded-md bg-muted/30 opacity-60"
                      >
                        <span className="text-sm">{mod.title}</span>
                        <SideBadge side={mod.side} />
                        <Badge variant="secondary" className="text-xs px-1.5 py-0">
                          installed
                        </Badge>
                      </li>
                    ))}
                  </ul>
                </div>
              )}

              {/* Actions */}
              <div className="flex justify-between">
                <Button
                  variant="ghost"
                  onClick={() => {
                    setStep("searching");
                    setResolved(null);
                  }}
                >
                  Back
                </Button>
                <Button
                  onClick={() => {
                    setStep("installing");
                    install.mutate();
                  }}
                  disabled={
                    resolved.toInstall.length === 0 ||
                    resolved.conflicts.length > 0
                  }
                >
                  Install {resolved.toInstall.length} mod(s)
                </Button>
              </div>
            </div>
          )}

          {/* ── Step 3: Installing / Updating (Timeline) ── */}
          {showTimeline && (
            <div className="space-y-1.5 py-2" role="status" aria-live="polite">
              {timelineSteps.map((s) => (
                <div key={s.id} className="flex items-start gap-3 px-1">
                  <div className="mt-0.5 shrink-0">
                    {s.status === "done" && (
                      <CheckCircle2 className="h-4 w-4 text-emerald-300" />
                    )}
                    {s.status === "active" && (
                      <span className="flex h-4 w-4 items-center justify-center">
                        <span className="h-2 w-2 rounded-full bg-blue-400 animate-pulse" />
                      </span>
                    )}
                    {s.status === "error" && (
                      <XCircle className="h-4 w-4 text-red-300" />
                    )}
                    {s.status === "pending" && (
                      <Circle className="h-4 w-4 text-muted-foreground/40" />
                    )}
                  </div>
                  <div className="min-w-0">
                    <p className={`text-sm font-medium ${
                      s.status === "done" ? "text-emerald-300" :
                      s.status === "active" ? "text-blue-300" :
                      s.status === "error" ? "text-red-300" :
                      "text-muted-foreground/60"
                    }`}>
                      {s.label}
                    </p>
                    {s.message && s.status === "active" && (
                      <p className="text-xs text-muted-foreground">{s.message}</p>
                    )}
                    {s.message && s.status === "error" && (
                      <p className="text-xs text-red-300/80">{s.message}</p>
                    )}
                  </div>
                </div>
              ))}
              <p className="text-xs text-muted-foreground px-1 pt-1">
                Do not close this page.
              </p>
            </div>
          )}

          {/* ── Installed mods list ─────────────────────── */}
          {step === "idle" && (
            <>
              <Separator />
              <div>
                <div className="flex items-center justify-between gap-3 mb-3 flex-wrap">
                  <h3 className="text-sm font-semibold flex items-center gap-2">
                    {updates.length > 0 && (
                      <Button
                        size="sm"
                        variant="outline"
                        onClick={() =>
                          updateMods.mutate(updates.map((u) => u.filename))
                        }
                        disabled={isBusy}
                        className="text-xs h-8 border-amber-500/40 text-amber-300 hover:bg-amber-500/10"
                      >
                        Update all ({updates.length})
                      </Button>
                    )}
                    <span>Installed Mods ({(() => {
                      const q = installedQuery.trim().toLowerCase();
                      const count = mods.filter((m) =>
                        (sideFilter === "all" || m.side === sideFilter) &&
                        (!q ||
                          m.displayName.toLowerCase().includes(q) ||
                          m.modId.toLowerCase().includes(q) ||
                          m.filename.toLowerCase().includes(q))
                      ).length;
                      return count === mods.length ? mods.length : `${count} / ${mods.length}`;
                    })()})</span>
                  </h3>
                  <div className="flex items-center gap-2 flex-wrap">
                    <div className="flex gap-1">
                      {(["all", "both", "server", "client"] as const).map((s) => (
                        <button
                          key={s}
                          onClick={() => setSideFilter(s)}
                          aria-pressed={sideFilter === s}
                          aria-label={`Filter by ${s}`}
                          className={`text-[10px] font-semibold uppercase rounded px-2 py-1 border transition ${
                            sideFilter === s
                              ? "border-primary/40 bg-primary/10 text-primary"
                              : "border-transparent text-muted-foreground hover:text-foreground"
                          }`}
                        >
                          {s}
                        </button>
                      ))}
                    </div>
                    <Input
                      placeholder="Search installed..."
                      value={installedQuery}
                      onChange={(e) => setInstalledQuery(e.target.value)}
                      className="h-8 text-sm max-w-[200px]"
                    />
                  </div>
                </div>
                <ul className="max-h-[400px] overflow-y-auto space-y-1">
                  {mods
                    .filter((m) => {
                      if (sideFilter !== "all" && m.side !== sideFilter) return false;
                      const q = installedQuery.trim().toLowerCase();
                      if (!q) return true;
                      return (
                        m.displayName.toLowerCase().includes(q) ||
                        m.modId.toLowerCase().includes(q) ||
                        m.filename.toLowerCase().includes(q)
                      );
                    })
                    .map((mod) => (
                    <li
                      key={mod.filename}
                      className="flex items-center justify-between gap-2 px-3 py-2 rounded-md bg-muted/50 group"
                    >
                      <div className="flex-1 min-w-0">
                        <div className="flex items-center gap-2">
                          <span className="text-sm font-medium truncate">
                            {mod.displayName}
                          </span>
                          {mod.version && (
                            <Badge
                              variant="secondary"
                              className="text-xs px-1.5 py-0"
                            >
                              {mod.version}
                            </Badge>
                          )}
                          <SideBadge side={mod.side} />
                          {newlyInstalled.has(mod.filename) && (
                            <Badge variant="outline" className="text-xs px-1.5 py-0 border-emerald-500/30 text-emerald-300">
                              New
                            </Badge>
                          )}
                          {updateMap.has(mod.filename) && (
                            <Badge
                              variant="outline"
                              className="text-xs px-1.5 py-0 border-amber-500/40 text-amber-300"
                              title={`Latest: ${updateMap.get(mod.filename)?.latestFilename}`}
                            >
                              Update available
                            </Badge>
                          )}
                        </div>
                        <p className="text-xs text-muted-foreground truncate">
                          {mod.filename} — {mod.size}
                        </p>
                      </div>

                      {confirmRemove === mod.filename ? (
                        <div className="flex gap-1 shrink-0">
                          <Button
                            size="sm"
                            variant="destructive"
                            onClick={() => removeMod.mutate(mod.filename)}
                            disabled={isBusy}
                            className="text-xs h-9"
                          >
                            Confirm
                          </Button>
                          <Button
                            size="sm"
                            variant="ghost"
                            onClick={() => setConfirmRemove(null)}
                            className="text-xs h-9"
                          >
                            Cancel
                          </Button>
                        </div>
                      ) : (
                        <div className="flex gap-1 shrink-0">
                          {updateMap.has(mod.filename) && (
                            <Button
                              size="sm"
                              variant="outline"
                              onClick={() => updateMods.mutate([mod.filename])}
                              disabled={isBusy}
                              className="text-xs h-9 border-amber-500/40 text-amber-300 hover:bg-amber-500/10"
                            >
                              Update
                            </Button>
                          )}
                          <Button
                            size="sm"
                            variant="ghost"
                            onClick={() => setConfirmRemove(mod.filename)}
                            disabled={isBusy}
                            className="text-xs h-9 text-muted-foreground sm:opacity-0 sm:group-hover:opacity-100 transition"
                          >
                            Remove
                          </Button>
                        </div>
                      )}
                    </li>
                  ))}
                </ul>
              </div>
            </>
          )}
        </CardContent>
      </Card>

      {/* ── Snapshots Card ────────────────────────────────── */}
      {step === "idle" && snapshots.length > 0 && (
        <Card>
          <CardHeader>
            <CardTitle className="text-base">Snapshots</CardTitle>
            <CardDescription>
              Restore a previous mod configuration
            </CardDescription>
          </CardHeader>
          <CardContent>
            <ul className="space-y-1">
              {snapshots.map((snap) => (
                <li
                  key={snap.dirName}
                  className="flex items-center justify-between px-3 py-2.5 rounded-md bg-muted/50 group"
                >
                  <div className="min-w-0">
                    <div className="flex items-center gap-2">
                      <span className="text-sm font-medium">{snap.name}</span>
                      <Badge
                        variant="secondary"
                        className="text-xs px-1.5 py-0"
                      >
                        {snap.modCount} mods
                      </Badge>
                    </div>
                    <p
                      className="text-xs text-muted-foreground"
                      title={new Date(snap.createdAt).toLocaleString()}
                    >
                      {timeAgo(snap.createdAt)}
                    </p>
                  </div>
                  <div className="flex gap-1 shrink-0 flex-wrap justify-end">
                    {confirmRestore === snap.dirName ? (
                      <>
                        <Button
                          size="sm"
                          variant="default"
                          onClick={() => restoreSnap.mutate(snap.dirName)}
                          disabled={isBusy}
                          className="text-xs h-9"
                        >
                          Confirm Restore
                        </Button>
                        <Button
                          size="sm"
                          variant="ghost"
                          onClick={() => setConfirmRestore(null)}
                          className="text-xs h-9"
                        >
                          Cancel
                        </Button>
                      </>
                    ) : confirmDeleteSnap === snap.dirName ? (
                      <>
                        <Button
                          size="sm"
                          variant="destructive"
                          onClick={() => {
                            deleteSnap.mutate(snap.dirName);
                            setConfirmDeleteSnap(null);
                          }}
                          disabled={isBusy}
                          className="text-xs h-9"
                        >
                          Confirm Delete
                        </Button>
                        <Button
                          size="sm"
                          variant="ghost"
                          onClick={() => setConfirmDeleteSnap(null)}
                          className="text-xs h-9"
                        >
                          Cancel
                        </Button>
                      </>
                    ) : (
                      <>
                        <Button
                          size="sm"
                          variant="outline"
                          onClick={() => setConfirmRestore(snap.dirName)}
                          disabled={isBusy}
                          className="text-xs h-9 sm:opacity-0 sm:group-hover:opacity-100 transition"
                        >
                          Restore
                        </Button>
                        <Button
                          size="sm"
                          variant="ghost"
                          onClick={() => setConfirmDeleteSnap(snap.dirName)}
                          disabled={isBusy}
                          className="text-xs h-9 text-muted-foreground sm:opacity-0 sm:group-hover:opacity-100 transition"
                        >
                          Delete
                        </Button>
                      </>
                    )}
                  </div>
                </li>
              ))}
            </ul>
          </CardContent>
        </Card>
      )}
    </div>
  );
}
