import type { ReactElement } from "react";
import { useEffect, useMemo, useRef, useState } from "react";

import type { RunnerTrace, TraceEvent } from "../../types/domain";
import { formatBeijingTime, formatDuration, toneClass } from "../../utils";
import { summarizeLastEvent } from "../../state/code-agent-status";
import { traceDisplayRows, traceNoiseEventCount, type TraceEventRow } from "../../../../../tools/src/hwlab-cli/trace-renderer";
import { MessageMarkdown } from "../shared/MessageMarkdown";

interface MessageTracePanelProps {
  trace: RunnerTrace;
  defaultOpen?: boolean;
  storageKey?: string;
  collapseWhenFinalResponse?: boolean;
}

export function MessageTracePanel({ trace, defaultOpen, storageKey, collapseWhenFinalResponse }: MessageTracePanelProps): ReactElement {
  const traceRunning = isRunningTrace(trace);
  const shouldCollapseForFinalResponse = collapseWhenFinalResponse === true && !traceRunning;
  const storedOpen = shouldCollapseForFinalResponse ? false : readStoredOpen(storageKey, defaultOpen ?? traceRunning);
  const [open, setOpen] = useState<boolean>(storedOpen);
  const [pulseKey, setPulseKey] = useState(0);
  const listRef = useRef<HTMLDivElement | null>(null);
  const updateKey = traceUpdateKey(trace);

  useEffect(() => {
    if (traceRunning) {
      setOpen(true);
    } else if (shouldCollapseForFinalResponse) {
      setOpen(false);
    }
  }, [shouldCollapseForFinalResponse, traceRunning, trace.traceId]);

  useEffect(() => {
    if (storageKey) {
      try { window.localStorage.setItem(storageKey, open ? "1" : "0"); } catch { /* ignore */ }
    }
  }, [open, storageKey]);

  useEffect(() => {
    if (traceRunning && open && listRef.current) {
      listRef.current.scrollTop = listRef.current.scrollHeight;
    }
  }, [trace, traceRunning, open]);

  useEffect(() => {
    setPulseKey((value) => value + 1);
  }, [updateKey]);

  const events = Array.isArray(trace.events) ? trace.events : [];
  const last = summarizeLastEvent(trace);
  const rows = useMemo<TraceEventRow[]>(() => {
    const eventRows = traceDisplayRows(trace, events);
    return eventRows.length > 0 ? eventRows : terminalTraceEvidenceRows(trace);
  }, [trace, events]);
  const noiseCount = useMemo<number>(() => traceNoiseEventCount(events), [events]);
  const lastUpdatedAt = traceUpdatedAt(trace, events, last.ts);

  return (
    <details className={`message-trace tone-border-${toneClass(last.tone)}`} data-trace-ui-key={trace.traceId ?? "trace"} data-trace-status={trace.status ?? "source"} data-trace-mode="all" data-trace-update-key={pulseKey} open={open} onToggle={(event) => setOpen((event.currentTarget as HTMLDetailsElement).open)}>
      <summary className="message-trace-summary">
        <span>Trace · {trace.status ?? "unknown"} · {traceCountText(trace, events.length, rows.length, noiseCount)}</span>
        <span className={`message-trace-last tone-${toneClass(last.tone)}`}>最近更新</span>
        {lastUpdatedAt ? <span className="message-trace-time">{formatBeijingTime(lastUpdatedAt, { withSeconds: true })}</span> : null}
        <span className="message-trace-update-dot" aria-hidden="true" />
      </summary>
      <div
        className="message-trace-events"
        data-trace-ui-key={trace.traceId ?? "trace"}
        data-trace-status={trace.status ?? "source"}
        data-trace-mode="all"
        ref={listRef}
      >
        {rows.length === 0 ? (
          <p className="message-trace-empty">trace={trace.traceId ?? "pending"}；等待后端事件。</p>
        ) : (
          <ol className="message-trace-list">
            {rows.map((row) => (
              <li key={row.rowId} className={`message-trace-row tone-border-${toneClass(row.tone)}`} data-trace-row-id={row.rowId} data-trace-terminal={row.terminal === true ? "true" : undefined}>
                <header className="message-trace-line">
                  <span className={`message-trace-tone tone-${toneClass(row.tone)}`} aria-hidden="true">●</span>
                  <span className="message-trace-label">{row.header}</span>
                </header>
                {row.body ? <TraceRowBody row={row} /> : null}
              </li>
            ))}
          </ol>
        )}
      </div>
    </details>
  );
}

function TraceRowBody({ row }: { row: TraceEventRow }): ReactElement | null {
  if (!row.body) return null;
  if (row.bodyFormat === "markdown") {
    return (
      <MessageMarkdown className="message-trace-body message-trace-markdown message-copy-markdown" data-body-format="markdown">
        {row.body}
      </MessageMarkdown>
    );
  }
  return <pre className="message-trace-body message-copy-markdown" data-body-format="text">{row.body}</pre>;
}

function traceUpdateKey(trace: RunnerTrace): string {
  const eventCount = Number.isInteger(trace.eventCount) ? String(trace.eventCount) : String(trace.events?.length ?? 0);
  return [trace.traceId ?? "trace", trace.status ?? "unknown", trace.updatedAt ?? "", trace.finishedAt ?? "", eventCount].join(":");
}

function traceUpdatedAt(trace: RunnerTrace, events: TraceEvent[], fallback: unknown): unknown {
  return trace.updatedAt ?? trace.finishedAt ?? events.at(-1)?.createdAt ?? events.at(-1)?.ts ?? fallback;
}

function traceCountText(trace: RunnerTrace, events: number, rows: number, noiseCount: number): string {
  const rawTotal = Number.isInteger(trace.eventCount) ? trace.eventCount : events;
  const noise = noiseCount > 0 ? `，去噪 ${noiseCount}` : "";
  if (rawTotal === events) return `${rows}/${rawTotal} 行${noise}`;
  return `${rows}/${events}/${rawTotal} 行${noise}`;
}

function terminalTraceEvidenceRows(trace: RunnerTrace): TraceEventRow[] {
  const evidence = objectOrNull(trace.terminalEvidence);
  const summary = objectOrNull(trace.traceSummary);
  const response = objectOrNull(trace.finalResponse);
  const text = firstNonEmptyTraceText(response?.text, response?.content, response?.message, summary?.finalText, summary?.text);
  const sourceCount = positiveInteger(summary?.sourceEventCount ?? summary?.eventCount ?? trace.eventCount);
  if (!text && evidence?.available !== true && sourceCount === 0) return [];
  const status = firstNonEmptyTraceText(trace.traceStatus, trace.status, summary?.terminalStatus, objectOrNull(trace.agentRun)?.terminalStatus) ?? "expired";
  const expired = isExpiredTraceStatus(trace.traceStatus, trace.status) || isExpiredTerminalEvidence(evidence);
  const header = expired
    ? `${trace.traceId ?? "trace"} 历史 trace 已过期，显示 AgentRun 终态证据`
    : `${trace.traceId ?? "trace"} 完整 trace 回放中，先显示 AgentRun 终态证据`;
  const meta = [
    `traceStatus=${status}`,
    sourceCount > 0 ? `sourceEventCount=${sourceCount}` : null,
    firstNonEmptyTraceText(evidence?.source, summary?.source) ? `source=${firstNonEmptyTraceText(evidence?.source, summary?.source)}` : null
  ].filter(Boolean).join("\n");
  const body = [meta, text].filter(Boolean).join("\n\n") || (expired ? "后端没有保留原始 event，但 AgentRun command result 已返回终态证据。" : "后端声明存在原始 event，正在通过 trace 回放入口补齐完整事件列表。");
  return [{ rowId: `terminal:${trace.traceId ?? "trace"}`, seq: null, tone: "source", header, body, terminal: true, bodyFormat: text ? "markdown" : "text" }];
}

function isExpiredTraceStatus(...values: unknown[]): boolean {
  return values.some((value) => ["expired", "missing"].includes(String(value ?? "").trim().toLowerCase()));
}

function isExpiredTerminalEvidence(evidence: Record<string, unknown> | null): boolean {
  const store = String(evidence?.liveTraceStore ?? "").trim().toLowerCase();
  const source = String(evidence?.source ?? "").trim().toLowerCase();
  return store.includes("expired") || source.includes("expired");
}

function objectOrNull(value: unknown): Record<string, unknown> | null {
  return value && typeof value === "object" ? value as Record<string, unknown> : null;
}

function firstNonEmptyTraceText(...values: unknown[]): string | null {
  for (const value of values) {
    if (typeof value !== "string") continue;
    const text = value.trim();
    if (text) return text;
  }
  return null;
}

function positiveInteger(value: unknown): number {
  const parsed = Number(value);
  return Number.isInteger(parsed) && parsed > 0 ? parsed : 0;
}

function isRunningTrace(trace: RunnerTrace): boolean {
  return String(trace.status ?? "").toLowerCase() === "running";
}

function readStoredOpen(key: string | undefined, fallback: boolean): boolean {
  if (!key || typeof window === "undefined") return fallback;
  try {
    const value = window.localStorage.getItem(key);
    if (value === "1") return true;
    if (value === "0") return false;
  } catch { /* ignore */ }
  return fallback;
}

export { formatDuration };
