import { Check, Copy, GitCompareArrows } from "lucide-react";
import { Suspense, type JSX } from "react";
import { useCallback, useEffect, useMemo, useRef, useState, memo } from "react";
import type { CapturedLog } from "../../proxy/schemas";
import { stripClaudeCodeBillingHeader } from "../../proxy/claudeCodeStrip";
import { Button } from "../ui/button";
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
import { JsonExpansionButton } from "../ui/json-expansion-button";
import { useJsonBulkExpansion } from "../ui/json-viewer-bulk";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
import {
  LazyJsonViewer,
  LazyJsonViewerFromString,
  LazyReplayDialog,
  LazyRequestAnatomy,
  LazyResponseView,
  LazyStreamingChunkSequence,
} from "./lazy";
import type { AnatomySegment } from "./anatomy/types";
import { useAnatomyJump } from "./anatomy/useAnatomyJump";
import { computeHeadersDiff, computeRequestDiff, DiffView } from "./diff";
import { LogEntryHeader } from "./LogEntryHeader";
import type { CacheTrendEntry } from "./cacheTrend";
import { getLogFormatAdapter, resolveLogFormat } from "./log-formats";
import {
  shouldShowHeadersDiffButton,
  shouldShowRawRequestTab,
  shouldShowRequestDiffButton,
} from "./logEntryVisibility";

/**
 * Lightweight fallback for lazy-loaded tabs. Renders an empty 1px-tall row so
 * the layout doesn't jump while the chunk is fetching.
 */
function TabFallback(): JSX.Element {
  return <div className="h-1" aria-hidden="true" />;
}

export type LogEntryProps = {
  log: CapturedLog;
  viewMode?: "simple" | "full";
  /** Live "strip Claude Code billing header" flag, sourced once at the viewer
   * container. Hoisted out of `LogEntry` so a single SWR subscription serves
   * the whole virtualized list (N logs == N subscriptions is the previous
   * cost).
   */
  strip: boolean;
  /** Slow-response threshold in seconds. `0` disables the warning indicator. */
  slowResponseThresholdSeconds: number;
  /**
   * Per-log cache token trend, looked up in the viewer-level trend map.
   * `null` (or absent) means the header should render with no arrows.
   */
  cacheTrend?: CacheTrendEntry | null;
  /** Callback to open CompareDrawer with this log and its immediate predecessor. */
  onCompareWithPrevious?: (log: CapturedLog) => void;
};

function CopyButton({
  text,
  label,
  copied,
  onCopy,
}: {
  text: string | null;
  label: string;
  copied: boolean;
  onCopy: (e: React.MouseEvent) => void;
}): JSX.Element | null {
  if (text === null) return null;
  return (
    <Tooltip>
      <TooltipTrigger asChild>
        <Button
          variant="outline"
          size="sm"
          className="h-8 text-xs"
          onClick={onCopy}
          aria-label={label}
        >
          {copied ? (
            <Check className="size-3.5 mr-1 text-emerald-500" />
          ) : (
            <Copy className="size-3.5 mr-1" />
          )}
          {copied ? "Copied!" : label}
        </Button>
      </TooltipTrigger>
      <TooltipContent>{copied ? "Copied to clipboard" : label}</TooltipContent>
    </Tooltip>
  );
}

function DiffToggleButton({
  active,
  onClick,
}: {
  active: boolean;
  onClick: (e: React.MouseEvent) => void;
}): JSX.Element {
  return (
    <Tooltip>
      <TooltipTrigger asChild>
        <Button
          variant={active ? "default" : "outline"}
          size="sm"
          className="h-8 text-xs"
          onClick={onClick}
          aria-pressed={active}
        >
          <GitCompareArrows className="size-3.5 mr-1" />
          {active ? "Showing diff" : "Diff with Raw"}
        </Button>
      </TooltipTrigger>
      <TooltipContent>
        {active ? "Hide diff view" : "Compare proxy output against the original raw version"}
      </TooltipContent>
    </Tooltip>
  );
}

const RequestDiffContent = memo(function ({
  rawBody,
  displayedBody,
  emptyLabel,
}: {
  rawBody: string | null;
  displayedBody: string | null;
  emptyLabel: string;
}): JSX.Element {
  const result = useMemo(
    () => computeRequestDiff(rawBody, displayedBody),
    [rawBody, displayedBody],
  );
  return <DiffView result={result} emptyLabel={emptyLabel} />;
});

const HeadersDiffContent = memo(function ({
  rawHeaders,
  headers,
  emptyLabel,
}: {
  rawHeaders: Record<string, string> | undefined;
  headers: Record<string, string> | undefined;
  emptyLabel: string;
}): JSX.Element {
  const result = useMemo(() => computeHeadersDiff(rawHeaders, headers), [rawHeaders, headers]);
  return <DiffView result={result} emptyLabel={emptyLabel} />;
});

function useCopyFeedback(text: string | null): {
  copied: boolean;
  copy: (event: React.MouseEvent) => void;
} {
  const [copied, setCopied] = useState(false);
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(
    () => () => {
      if (timerRef.current !== null) clearTimeout(timerRef.current);
    },
    [],
  );

  const copy = useCallback(
    (event: React.MouseEvent) => {
      event.stopPropagation();
      if (text === null) return;
      void window.navigator.clipboard.writeText(text).then(() => {
        setCopied(true);
        if (timerRef.current !== null) clearTimeout(timerRef.current);
        timerRef.current = setTimeout(() => setCopied(false), 2000);
      });
    },
    [text],
  );

  return { copied, copy };
}

export const LogEntry = memo(function ({
  log,
  viewMode = "simple",
  strip,
  slowResponseThresholdSeconds,
  cacheTrend = null,
  onCompareWithPrevious,
}: LogEntryProps): JSX.Element {
  const [expanded, setExpanded] = useState<boolean>(false);
  const [replayOpen, setReplayOpen] = useState<boolean>(false);
  const [headersDiff, setHeadersDiff] = useState<boolean>(false);
  const [requestDiff, setRequestDiff] = useState<boolean>(false);
  const [activeTab, setActiveTab] = useState("request");
  const [expandToPath, setExpandToPath] = useState<string | null>(null);
  const requestJsonRef = useRef<HTMLDivElement | null>(null);
  const resolvedFormat = resolveLogFormat(log);
  const adapter = getLogFormatAdapter(resolvedFormat);
  const requestAnalysis = useMemo(
    () => adapter.analyzeRequest(log.rawRequestBody),
    [adapter, log.rawRequestBody],
  );
  const responseAnalysis = useMemo(
    () => adapter.analyzeResponse(log.responseText),
    [adapter, log.responseText],
  );
  const strippedRequestBody = useMemo(() => {
    if (!strip || resolvedFormat !== "anthropic" || log.rawRequestBody === null) {
      return null;
    }
    return stripClaudeCodeBillingHeader(log.rawRequestBody).body;
  }, [log.rawRequestBody, resolvedFormat, strip]);
  const displayedRequestBody = strippedRequestBody ?? log.rawRequestBody;
  const requestCopy = useCopyFeedback(displayedRequestBody);
  const rawRequestCopy = useCopyFeedback(log.rawRequestBody);
  const responseCopy = useCopyFeedback(log.responseText);
  const requestExpansion = useJsonBulkExpansion(displayedRequestBody);
  const rawRequestExpansion = useJsonBulkExpansion(log.rawRequestBody);
  const anatomySegments = useMemo(
    () =>
      requestExpansion.parsedData !== null
        ? adapter.anatomySegments(requestExpansion.parsedData)
        : null,
    [adapter, requestExpansion.parsedData],
  );
  const anatomyPaths = useMemo(() => {
    if (anatomySegments === null) return undefined;
    return new Set(anatomySegments.map((s) => s.path));
  }, [anatomySegments]);
  const jumpToAnatomySegment = useAnatomyJump({
    containerRef: requestJsonRef,
    setExpandToPath,
    ensureTabActive: () => {
      if (activeTab !== "request") setActiveTab("request");
    },
  });

  return (
    <TooltipProvider>
      <div className="border border-border rounded-lg mb-1 overflow-hidden">
        <LogEntryHeader
          log={log}
          messageCount={requestAnalysis.messageCount}
          toolCount={requestAnalysis.toolCount}
          expanded={expanded}
          onToggle={() => setExpanded(!expanded)}
          responseToolNames={responseAnalysis.toolNames}
          cacheTrend={cacheTrend}
          slowResponseThresholdSeconds={slowResponseThresholdSeconds}
          onReplay={
            onCompareWithPrevious === undefined
              ? undefined
              : () => {
                  setReplayOpen(true);
                }
          }
          onCopyRequest={
            displayedRequestBody === null
              ? undefined
              : (e) => {
                  requestCopy.copy(e);
                }
          }
          requestCopied={requestCopy.copied}
          onToggleRequestExpansion={requestExpansion.toggle}
          requestExpansionState={
            requestExpansion.policy === null
              ? null
              : {
                  isExpanded: requestExpansion.isExpanded,
                  isPending: requestExpansion.isPending,
                }
          }
        />

        {expanded && (
          <div onClick={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()}>
            <Tabs value={activeTab} onValueChange={setActiveTab}>
              <TabsList className="mx-4 mt-2">
                {viewMode === "full" && <TabsTrigger value="raw-headers">Raw Headers</TabsTrigger>}
                {viewMode === "full" && <TabsTrigger value="headers">Headers</TabsTrigger>}
                {shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && (
                  <TabsTrigger value="raw-request">Raw Request</TabsTrigger>
                )}
                <TabsTrigger value="request">Request</TabsTrigger>
                {anatomySegments !== null && <TabsTrigger value="anatomy">Anatomy</TabsTrigger>}
                {viewMode === "full" && <TabsTrigger value="raw">Raw Response</TabsTrigger>}
                <TabsTrigger value="parsed">Response</TabsTrigger>
              </TabsList>

              {shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && (
                <TabsContent value="raw-request">
                  {activeTab === "raw-request" && (
                    <div className="px-4 pt-1 pb-3">
                      <div className="flex justify-end gap-2 mb-2">
                        <JsonExpansionButton
                          policy={rawRequestExpansion.policy}
                          isExpanded={rawRequestExpansion.isExpanded}
                          isPending={rawRequestExpansion.isPending}
                          onToggle={rawRequestExpansion.toggle}
                        />
                        <CopyButton
                          text={log.rawRequestBody}
                          label="Copy"
                          copied={rawRequestCopy.copied}
                          onCopy={rawRequestCopy.copy}
                        />
                      </div>
                      {log.rawRequestBody === null ? (
                        <p className="text-xs text-muted-foreground italic">No request body</p>
                      ) : rawRequestExpansion.parsedData !== null ? (
                        <Suspense fallback={<TabFallback />}>
                          <LazyJsonViewer
                            data={rawRequestExpansion.parsedData}
                            bulkDepth={rawRequestExpansion.bulkDepth}
                            bulkRevision={rawRequestExpansion.bulkRevision}
                          />
                        </Suspense>
                      ) : (
                        <pre className="font-mono text-xs whitespace-pre-wrap break-words">
                          {log.rawRequestBody}
                        </pre>
                      )}
                    </div>
                  )}
                </TabsContent>
              )}

              <TabsContent value="request">
                {activeTab === "request" && (
                  <div className="px-4 pt-1 pb-3">
                    {/* Per-tab secondary actions (diff toggles, compare-with-previous).
                        Replay / Copy / Expand-all JSON now live in the header. */}
                    {(shouldShowRequestDiffButton(
                      resolvedFormat,
                      viewMode,
                      strip,
                      log.rawRequestBody !== null,
                    ) ||
                      onCompareWithPrevious !== undefined) && (
                      <div className="flex justify-end gap-2 mb-2">
                        {shouldShowRequestDiffButton(
                          resolvedFormat,
                          viewMode,
                          strip,
                          log.rawRequestBody !== null,
                        ) && (
                          <DiffToggleButton
                            active={requestDiff}
                            onClick={(e) => {
                              e.stopPropagation();
                              setRequestDiff(!requestDiff);
                            }}
                          />
                        )}
                        {onCompareWithPrevious !== undefined && (
                          <Tooltip>
                            <TooltipTrigger asChild>
                              <Button
                                variant="outline"
                                size="sm"
                                className="h-8 text-xs"
                                onClick={(e) => {
                                  e.stopPropagation();
                                  onCompareWithPrevious(log);
                                }}
                              >
                                <GitCompareArrows className="size-3 mr-1" />
                                Diff with Previous
                              </Button>
                            </TooltipTrigger>
                            <TooltipContent>
                              Compare this request with the immediately preceding one
                            </TooltipContent>
                          </Tooltip>
                        )}
                      </div>
                    )}
                    {requestDiff ? (
                      <RequestDiffContent
                        rawBody={log.rawRequestBody}
                        displayedBody={displayedRequestBody}
                        emptyLabel="No transformation applied — raw and sent request bodies are identical."
                      />
                    ) : (
                      <div ref={requestJsonRef}>
                        {displayedRequestBody === null ? (
                          <p className="text-xs text-muted-foreground italic">No request body</p>
                        ) : requestExpansion.parsedData !== null ? (
                          <Suspense fallback={<TabFallback />}>
                            <LazyJsonViewer
                              data={requestExpansion.parsedData}
                              bulkDepth={requestExpansion.bulkDepth}
                              bulkRevision={requestExpansion.bulkRevision}
                              anatomyPaths={anatomyPaths}
                              expandToPath={expandToPath}
                            />
                          </Suspense>
                        ) : (
                          <pre className="font-mono text-xs whitespace-pre-wrap break-words">
                            {displayedRequestBody}
                          </pre>
                        )}
                      </div>
                    )}
                  </div>
                )}
              </TabsContent>

              {anatomySegments !== null && (
                <TabsContent value="anatomy">
                  {activeTab === "anatomy" && (
                    <Suspense fallback={<TabFallback />}>
                      <LazyRequestAnatomy
                        parsed={null}
                        inputTokens={log.inputTokens ?? null}
                        segments={anatomySegments}
                        onSegmentActivate={jumpToAnatomySegment}
                      />
                    </Suspense>
                  )}
                </TabsContent>
              )}

              {viewMode === "full" && (
                <TabsContent value="headers">
                  {activeTab === "headers" && (
                    <div className="px-4 pt-1 pb-3">
                      <div className="flex justify-end gap-2 mb-2">
                        {shouldShowHeadersDiffButton(
                          viewMode,
                          log.rawHeaders !== undefined && Object.keys(log.rawHeaders).length > 0,
                        ) && (
                          <DiffToggleButton
                            active={headersDiff}
                            onClick={(e) => {
                              e.stopPropagation();
                              setHeadersDiff(!headersDiff);
                            }}
                          />
                        )}
                      </div>
                      {headersDiff ? (
                        <HeadersDiffContent
                          rawHeaders={log.rawHeaders}
                          headers={log.headers}
                          emptyLabel="No transformation applied — raw and processed headers are identical."
                        />
                      ) : log.headers && Object.keys(log.headers).length > 0 ? (
                        <div className="space-y-1 font-mono text-xs">
                          {Object.entries(log.headers)
                            .sort(([a], [b]) => a.localeCompare(b))
                            .map(([key, value]) => (
                              <div key={key} className="flex gap-2">
                                <span className="text-blue-600 dark:text-blue-400 font-semibold shrink-0">
                                  {key}:
                                </span>
                                <span className="text-muted-foreground truncate" title={value}>
                                  {value}
                                </span>
                              </div>
                            ))}
                        </div>
                      ) : (
                        <p className="text-xs text-muted-foreground italic">No headers captured</p>
                      )}
                    </div>
                  )}
                </TabsContent>
              )}

              {viewMode === "full" && (
                <TabsContent value="raw-headers">
                  {activeTab === "raw-headers" && (
                    <div className="px-4 pt-1 pb-3">
                      {log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? (
                        <div className="space-y-1 font-mono text-xs">
                          {Object.entries(log.rawHeaders)
                            .sort(([a], [b]) => a.localeCompare(b))
                            .map(([key, value]) => (
                              <div key={key} className="flex gap-2">
                                <span className="text-blue-600 dark:text-blue-400 font-semibold shrink-0">
                                  {key}:
                                </span>
                                <span className="text-muted-foreground truncate" title={value}>
                                  {value}
                                </span>
                              </div>
                            ))}
                        </div>
                      ) : (
                        <p className="text-xs text-muted-foreground italic">
                          No raw headers captured
                        </p>
                      )}
                    </div>
                  )}
                </TabsContent>
              )}

              <TabsContent value="raw">
                {activeTab === "raw" && (
                  <div className="px-4 pt-1 pb-3 space-y-3">
                    {log.error !== undefined && log.error !== null && (
                      <div className="rounded border border-destructive/50 bg-destructive/10 p-3 text-xs">
                        <div className="font-semibold text-destructive mb-1">SSE Error</div>
                        <div className="text-muted-foreground font-mono">{log.error}</div>
                      </div>
                    )}
                    <div className="flex justify-end">
                      <CopyButton
                        text={log.responseText}
                        label="Copy"
                        copied={responseCopy.copied}
                        onCopy={responseCopy.copy}
                      />
                    </div>
                    {log.responseText !== null ? (
                      <Suspense fallback={<TabFallback />}>
                        <LazyJsonViewerFromString text={log.responseText} defaultExpandDepth={0} />
                      </Suspense>
                    ) : (
                      <p className="text-xs text-muted-foreground italic">No response</p>
                    )}
                    {log.streaming === true && (
                      <Suspense fallback={<TabFallback />}>
                        <LazyStreamingChunkSequence
                          logId={log.id}
                          truncated={log.streamingChunksPath !== null}
                        />
                      </Suspense>
                    )}
                  </div>
                )}
              </TabsContent>

              <TabsContent value="parsed">
                {activeTab === "parsed" && (
                  <div className="px-4 pt-1 pb-3">
                    <Suspense fallback={<TabFallback />}>
                      <LazyResponseView
                        responseText={log.responseText}
                        responseStatus={log.responseStatus}
                        streaming={log.streaming}
                        inputTokens={log.inputTokens}
                        outputTokens={log.outputTokens}
                        cacheCreationInputTokens={log.cacheCreationInputTokens}
                        cacheReadInputTokens={log.cacheReadInputTokens}
                        apiFormat={resolvedFormat}
                        error={log.error}
                      />
                    </Suspense>
                  </div>
                )}
              </TabsContent>
            </Tabs>
          </div>
        )}
      </div>
      <Suspense fallback={null}>
        <LazyReplayDialog log={log} open={replayOpen} onOpenChange={setReplayOpen} />
      </Suspense>
    </TooltipProvider>
  );
});
