import { type JSX, useCallback, useEffect, useMemo, useState } from "react";
import { Download } from "lucide-react";

import type { CapturedLog } from "../proxy/schemas";
import { exportLogsAsZip } from "../lib/export-logs";
import packageJson from "../../package.json";
import { ConversationGroup, groupLogsByConversation } from "./proxy-viewer";

import { CrabLogo } from "./ui/crab-logo";
import { crabVariants } from "./ui/crab-variants";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
import { SettingsDialog } from "./providers/SettingsDialog";
import { computeCacheTrends } from "./proxy-viewer/cacheTrend";
import { CompareDrawer } from "./proxy-viewer/CompareDrawer";
import { buildValidPredecessors } from "./proxy-viewer/viewerState";

function truncateSessionId(id: string): string {
  if (id.length <= 30) return id;
  return id.slice(0, 12) + "…" + id.slice(-12);
}

function computeTokenSummary(logs: CapturedLog[]): { totalIn: number; totalOut: number } {
  let totalIn = 0;
  let totalOut = 0;
  for (const log of logs) {
    if (log.inputTokens !== null) totalIn += log.inputTokens;
    if (log.outputTokens !== null) totalOut += log.outputTokens;
  }
  return { totalIn, totalOut };
}

function CopyableCommand({ command }: { command: string }): JSX.Element {
  const [copied, setCopied] = useState(false);

  const handleCopy = useCallback(() => {
    void window.navigator.clipboard.writeText(command).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  }, [command]);

  return (
    <div className="inline-flex items-center gap-2 bg-muted rounded-md px-3 py-2">
      <pre className="text-blue-500 font-mono text-sm m-0">{command}</pre>
      <button
        type="button"
        onClick={handleCopy}
        className="text-muted-foreground hover:text-foreground transition-colors shrink-0 cursor-pointer"
        aria-label="Copy command"
      >
        {copied ? (
          <svg
            width="16"
            height="16"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            strokeWidth="2"
            strokeLinecap="round"
            strokeLinejoin="round"
          >
            <path d="M20 6 9 17l-5-5" />
          </svg>
        ) : (
          <svg
            width="16"
            height="16"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            strokeWidth="2"
            strokeLinecap="round"
            strokeLinejoin="round"
          >
            <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
            <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
          </svg>
        )}
      </button>
    </div>
  );
}

export type ProxyViewerProps = {
  logs: CapturedLog[];
  sessions: string[];
  models: string[];
  selectedSession: string;
  selectedModel: string;
  onSessionChange: (session: string) => void;
  onModelChange: (model: string) => void;
  onClearAll: () => void;
  /** Clear only the logs whose ids are passed. Called by the per-group
   *  Clear button on each conversation header. */
  onClearGroup: (ids: number[]) => void;
  viewMode: "simple" | "full";
  onViewModeChange: (mode: "simple" | "full") => void;
  /** Live strip-Claude-Code-billing-header flag, sourced once at the container. */
  strip: boolean;
};

export function ProxyViewer({
  logs,
  sessions,
  models,
  selectedSession,
  selectedModel,
  onSessionChange,
  onModelChange,
  onClearAll,
  onClearGroup,
  viewMode,
  onViewModeChange,
  strip,
}: ProxyViewerProps): JSX.Element {
  const { totalIn, totalOut } = useMemo(() => computeTokenSummary(logs), [logs]);
  const [exporting, setExporting] = useState(false);
  const [comparePair, setComparePair] = useState<[CapturedLog, CapturedLog] | null>(null);
  const [crabEntrancePhase, setCrabEntrancePhase] = useState<"hidden" | "playing" | "done">(
    "hidden",
  );

  useEffect(() => {
    const perCrabDuration = 400;
    const startDelay = 50;
    const playingDone = startDelay + crabVariants.length * perCrabDuration;
    const t1 = setTimeout(() => setCrabEntrancePhase("playing"), startDelay);
    const t2 = setTimeout(() => setCrabEntrancePhase("done"), playingDone);
    return () => {
      clearTimeout(t1);
      clearTimeout(t2);
    };
  }, []);

  const handleExport = useCallback(async () => {
    setExporting(true);
    try {
      await exportLogsAsZip(logs);
    } finally {
      setExporting(false);
    }
  }, [logs]);
  // Close the compare drawer when the user changes the session or model
  // filter, since the predecessor relationship may no longer be meaningful.
  useEffect(() => {
    setComparePair(null);
  }, [selectedSession, selectedModel]);

  const closeCompare = useCallback(() => {
    setComparePair(null);
  }, []);

  const groups = useMemo(() => groupLogsByConversation(logs), [logs]);
  const cacheTrends = useMemo(() => computeCacheTrends(groups), [groups]);
  const comparisonPredecessors = useMemo(() => buildValidPredecessors(groups), [groups]);
  const handleCompareWithPrevious = useCallback(
    (log: CapturedLog) => {
      const predecessor = comparisonPredecessors.get(log.id);
      if (predecessor !== undefined) setComparePair([predecessor, log]);
    },
    [comparisonPredecessors],
  );

  return (
    <div className="max-w-[1200px] mx-auto flex flex-col h-screen" style={{ maxHeight: "100vh" }}>
      {/* Brand row */}
      <div className="flex items-end px-6 pt-6 pb-8 relative">
        <h1 className="text-lg font-bold flex items-end gap-2 absolute left-1/2 -translate-x-1/2 whitespace-nowrap">
          {/* Crab family — hover to animate together */}
          <span className="flex items-end gap-1 group cursor-default" aria-hidden="true">
            <CrabLogo className="size-10 text-amber-500 transition-all duration-300 group-hover:scale-125 group-hover:-translate-y-1.5" />
            <span className="flex items-end gap-0.5">
              {crabVariants.map((Crab, i) => {
                const color = [
                  "text-amber-500",
                  "text-rose-500",
                  "text-sky-500",
                  "text-emerald-500",
                  "text-violet-500",
                  "text-orange-500",
                  "text-cyan-500",
                  "text-pink-500",
                  "text-lime-500",
                  "text-blue-500",
                  "text-yellow-500",
                  "text-fuchsia-500",
                ][i];
                const entranceClass =
                  crabEntrancePhase === "hidden"
                    ? "opacity-0 scale-0"
                    : crabEntrancePhase === "playing"
                      ? "animate-crab-piano-pop"
                      : "";
                return (
                  <Crab
                    key={i}
                    className={`size-5 ${color} transition-all duration-300 ease-out group-hover:scale-125 group-hover:-translate-y-1 ${entranceClass}`}
                    style={{
                      transitionDelay: `${i * 50}ms`,
                      ...(crabEntrancePhase === "playing"
                        ? { animationDelay: `${i * 400}ms` }
                        : {}),
                    }}
                  />
                );
              })}
            </span>
          </span>
          <span className="flex items-baseline gap-2">
            LLM Inspector
            <span className="text-xs text-muted-foreground font-mono">v{packageJson.version}</span>
          </span>
        </h1>
        <div className="ml-auto">
          <SettingsDialog />
        </div>
      </div>

      {/* Controls + Filters */}
      <div className="flex items-center gap-3 px-6 mb-4">
        <Select value={selectedSession} onValueChange={onSessionChange}>
          <SelectTrigger className="flex-1 max-w-[350px] text-xs">
            <SelectValue placeholder="All sessions" />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="__all__">All sessions</SelectItem>
            {sessions.map((s) => (
              <SelectItem key={s} value={s}>
                {truncateSessionId(s)}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
        <Select value={selectedModel} onValueChange={onModelChange}>
          <SelectTrigger className="flex-1 max-w-[250px] text-xs">
            <SelectValue placeholder="All models" />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="__all__">All models</SelectItem>
            {models.map((m) => (
              <SelectItem key={m} value={m}>
                {m}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
        <div className="flex items-center border border-border rounded-md overflow-hidden">
          <button
            type="button"
            onClick={() => onViewModeChange("simple")}
            className={`px-2 py-1 cursor-pointer transition-colors text-xs ${
              viewMode === "simple"
                ? "bg-muted text-foreground"
                : "text-muted-foreground hover:bg-muted/50"
            }`}
          >
            Simple
          </button>
          <button
            type="button"
            onClick={() => onViewModeChange("full")}
            className={`px-2 py-1 cursor-pointer transition-colors text-xs ${
              viewMode === "full"
                ? "bg-muted text-foreground"
                : "text-muted-foreground hover:bg-muted/50"
            }`}
          >
            Full
          </button>
        </div>
        <div className="flex-1" />
        <span className="text-muted-foreground text-xs font-mono">
          {logs.length} request{logs.length !== 1 ? "s" : ""}
          {totalIn > 0 || totalOut > 0
            ? ` · ${totalIn.toLocaleString()} in / ${totalOut.toLocaleString()} out`
            : ""}
        </span>
        {logs.length > 0 && (
          <button
            type="button"
            onClick={() => {
              void handleExport();
            }}
            disabled={exporting}
            className="text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-1"
            title="Export all logs as JSON ZIP"
          >
            {exporting ? (
              <span>Exporting...</span>
            ) : (
              <>
                <Download className="size-3" />
                <span>Export</span>
              </>
            )}
          </button>
        )}
        <button
          type="button"
          onClick={onClearAll}
          className="text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
          title="Clear all logs"
        >
          Clear
        </button>
      </div>

      {/* Log list */}
      <div className="flex-1 min-h-0 px-6 pb-6">
        {logs.length === 0 ? (
          <div className="text-center text-muted-foreground py-16 space-y-4">
            <p className="text-sm">No requests captured yet.</p>
            <p className="text-xs">Route AI coding tools through the proxy:</p>
            <CopyableCommand command="LLM_BASE_URL=http://localhost:25947/proxy <your-tool>" />
          </div>
        ) : (
          <div className="overflow-y-auto h-full flex flex-col gap-2">
            {groups.map((group) => (
              <ConversationGroup
                key={group.id}
                group={group}
                viewMode={viewMode}
                strip={strip}
                cacheTrends={cacheTrends}
                onCompareWithPrevious={handleCompareWithPrevious}
                comparisonPredecessors={comparisonPredecessors}
                onClearGroup={onClearGroup}
                standalone={groups.length === 1}
              />
            ))}
          </div>
        )}
      </div>

      {/* Compare drawer — sibling of the log list, not a route change. */}
      {comparePair !== null && (
        <CompareDrawer left={comparePair[0]} right={comparePair[1]} onClose={closeCompare} />
      )}
    </div>
  );
}
