// SPEC: PJ2026-0106050514 Workbench实时运行面 draft-2026-06-30-p0-1297-spec-first; PJ2026-0104010803 Workbench唯一投影 draft-2026-06-18-p0-unique-projection.
// Responsibility: Pure Workbench trace/message/projection merge helpers consumed by the store orchestration layer.

import { mergeRunnerTrace, type TraceSnapshot } from "@/composables/workbench-trace-snapshot";
import type { AgentChatResultResponse, AgentRunProvenance, ApiResult, ChatMessage, ProjectionDiagnostic, TraceEvent, WorkbenchTurnTimingProjection } from "@/types";
import { firstNonEmptyString } from "@/utils";
import { normalizeErrorDiagnostic, normalizeProjectionDiagnostic } from "@/utils/workbench-error-runtime";

export function clearRunnerTraceTransientDiagnostics(trace: NonNullable<ChatMessage["runnerTrace"]>): NonNullable<ChatMessage["runnerTrace"]> {
  return { ...trace, error: undefined, projection: null, projectionStatus: null, projectionHealth: null, staleMs: null, blocker: null };
}

export function shouldClearCompletedTurnDiagnostics(status: unknown, error: ChatMessage["error"] | null): boolean {
  return normalizedStatusText(status) === "completed" && !error;
}

export function nonBlockingProjection(projection: ProjectionDiagnostic | null): ProjectionDiagnostic | null {
  if (!projection) return null;
  const status = firstNonEmptyString(projection.projectionStatus);
  const health = firstNonEmptyString(projection.projectionHealth);
  if (status === "caught-up" || health === "healthy") return projection;
  if (!projection.blocker && !projection.apiError && !projection.diagnostic) return projection;
  return null;
}

export function normalizedStatusText(value: unknown): string | null {
  const text = firstNonEmptyString(value);
  return text ? text.trim().toLowerCase().replace(/_/gu, "-") : null;
}

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

export function traceSnapshotError(value: unknown): Exclude<TraceSnapshot["error"], null> | undefined {
  const error = normalizeAgentError(value);
  return error ? error as Exclude<TraceSnapshot["error"], null> : undefined;
}

export function mergeTerminalResultTrace(previous: ChatMessage["runnerTrace"], result: AgentChatResultResponse): NonNullable<ChatMessage["runnerTrace"]> {
  const resultTrace = recordValue(result.runnerTrace);
  const events = firstArray(result.events, result.traceEvents, resultTrace?.events, previous?.events);
  const agentRun = asAgentRun(result.agentRun ?? resultTrace?.agentRun ?? previous?.agentRun);
  const traceDetailStatus = firstNonEmptyString(result.traceStatus, resultTrace?.traceStatus, resultTrace?.status);
  const timing = normalizeTimingProjection(result);
  const nextTrace = {
    ...resultTrace,
    traceId: firstNonEmptyString(result.traceId, resultTrace?.traceId, previous?.traceId) ?? undefined,
    sessionId: firstNonEmptyString(result.sessionId, resultTrace?.sessionId, previous?.sessionId) ?? undefined,
    threadId: firstNonEmptyString(result.threadId, resultTrace?.threadId, previous?.threadId) ?? undefined,
    events,
    eventCount: firstFiniteNumber(result.eventCount, resultTrace?.eventCount, previous?.eventCount, events.length),
    eventsCompacted: firstBoolean(resultTrace?.eventsCompacted, previous?.eventsCompacted),
    retention: result.retention ?? resultTrace?.retention ?? previous?.retention,
    terminalEvidence: result.terminalEvidence ?? resultTrace?.terminalEvidence ?? previous?.terminalEvidence,
    traceSummary: result.traceSummary ?? resultTrace?.traceSummary ?? previous?.traceSummary,
    agentRun: agentRun ?? undefined,
    error: normalizeAgentError(result.error ?? resultTrace?.error ?? previous?.error) ?? undefined,
    projection: projectionFromResult(result) ?? normalizeProjectionDiagnostic(resultTrace?.projection ?? resultTrace) ?? previous?.projection ?? null,
    projectionStatus: result.projectionStatus ?? resultTrace?.projectionStatus ?? previous?.projectionStatus ?? null,
    projectionHealth: result.projectionHealth ?? resultTrace?.projectionHealth ?? previous?.projectionHealth ?? null,
    staleMs: result.staleMs ?? resultTrace?.staleMs ?? previous?.staleMs ?? null,
    blocker: result.blocker ?? resultTrace?.blocker ?? previous?.blocker ?? null,
    timing,
    startedAt: timing?.startedAt ?? null,
    lastEventAt: timing?.lastEventAt ?? null,
    finishedAt: timing?.finishedAt ?? null,
    durationMs: timing?.durationMs ?? null,
    runnerKind: firstNonEmptyString(agentRun?.adapter, resultTrace?.runnerKind, previous?.runnerKind) ?? undefined,
    sessionMode: firstNonEmptyString(agentRun?.backendProfile, resultTrace?.sessionMode, previous?.sessionMode) ?? undefined,
    lastEventLabel: firstNonEmptyString(result.lastEventLabel, resultTrace?.lastEventLabel, previous?.lastEventLabel) ?? undefined,
    updatedAt: new Date().toISOString()
  } as NonNullable<ChatMessage["runnerTrace"]>;
  if (traceDetailStatus) nextTrace.status = traceDetailStatus;
  const traceStatus = firstNonEmptyString(result.traceStatus, resultTrace?.traceStatus);
  if (traceStatus) nextTrace.traceStatus = traceStatus;
  return mergeRunnerTrace(previous, nextTrace);
}

export function normalizeMessageRunnerTrace(message: ChatMessage): ChatMessage["runnerTrace"] {
  const trace = recordValue(message.runnerTrace);
  const agentRun = agentRunFromMessage(message);
  const error = normalizeAgentError(message.error ?? trace?.error);
  const projection = projectionFromMessage(message, trace as ChatMessage["runnerTrace"] | null);
  const timing = normalizeTimingProjection(message);
  if (!trace && !agentRun && !error && !projection) return message.runnerTrace ?? null;
  return {
    ...(trace ?? {}),
    traceId: message.traceId ?? trace?.traceId,
    sessionId: message.sessionId ?? trace?.sessionId,
    threadId: message.threadId ?? trace?.threadId,
    agentRun: agentRun ?? trace?.agentRun,
    error: error ?? trace?.error,
    projection,
    projectionStatus: projection?.projectionStatus ?? trace?.projectionStatus,
    projectionHealth: projection?.projectionHealth ?? trace?.projectionHealth,
    staleMs: projection?.staleMs ?? trace?.staleMs,
    blocker: projection?.blocker ?? trace?.blocker,
    timing,
    startedAt: timing?.startedAt ?? null,
    lastEventAt: timing?.lastEventAt ?? null,
    finishedAt: timing?.finishedAt ?? null,
    durationMs: timing?.durationMs ?? null,
    runnerKind: agentRun?.adapter ?? trace?.runnerKind,
    sessionMode: agentRun?.backendProfile ?? trace?.sessionMode
  } as ChatMessage["runnerTrace"];
}

export function normalizeTimingProjection(value: unknown): WorkbenchTurnTimingProjection | null {
  const record = recordValue(value);
  if (!record) return null;
  const source = recordValue(record.timing) ?? {};
  const startedAt = firstNonEmptyString(source.startedAt, record.startedAt);
  const lastEventAt = firstNonEmptyString(source.lastEventAt, record.lastEventAt);
  const finishedAt = firstNonEmptyString(source.finishedAt, record.finishedAt);
  const durationMs = firstFiniteNumber(source.durationMs, record.durationMs);
  const observedAt = firstNonEmptyString(source.observedAt, record.observedAt);
  const lastEventAgeMs = firstFiniteNumber(source.lastEventAgeMs, record.lastEventAgeMs);
  if (!startedAt && !lastEventAt && !finishedAt && durationMs == null && lastEventAgeMs == null) return null;
  return { ...source, startedAt: startedAt ?? null, lastEventAt: lastEventAt ?? null, finishedAt: finishedAt ?? null, durationMs: durationMs ?? null, observedAt: observedAt ?? null, lastEventAgeMs: lastEventAgeMs ?? null, valuesRedacted: source.valuesRedacted !== false } as WorkbenchTurnTimingProjection;
}

export function messageTimingPatch(value: unknown): Partial<ChatMessage> {
  const timing = normalizeTimingProjection(value);
  if (!timing) return {};
  return messageTimingPatchFromProjection(timing);
}

export function messageTimingPatchFromProjection(timing: WorkbenchTurnTimingProjection): Partial<ChatMessage> {
  return { timing, startedAt: timing.startedAt ?? null, lastEventAt: timing.lastEventAt ?? null, finishedAt: timing.finishedAt ?? null, durationMs: timing.durationMs ?? null };
}

export function messageTimingPatchForMerge(message: ChatMessage, value: unknown): Partial<ChatMessage> {
  const incomingTiming = normalizeTimingProjection(value);
  if (!incomingTiming || !isTerminalTimingSource(value)) return messageTimingPatch(message);
  const existingTiming = normalizeTimingProjection(message);
  const existingDuration = firstPositiveFiniteNumber(existingTiming?.durationMs);
  if (isTerminalMessageStatus(message.status) && existingTiming && existingDuration !== null) return messageTimingPatchFromProjection(existingTiming);
  const incomingDuration = firstPositiveFiniteNumber(incomingTiming.durationMs) ?? positiveDurationBetween(incomingTiming.startedAt, incomingTiming.finishedAt) ?? positiveDurationBetween(incomingTiming.startedAt, incomingTiming.lastEventAt);
  if (incomingDuration !== null) return messageTimingPatchFromProjection({ ...(existingTiming ?? {}), ...incomingTiming, durationMs: incomingDuration, valuesRedacted: incomingTiming.valuesRedacted !== false && existingTiming?.valuesRedacted !== false });
  const runningDuration = isTerminalMessageStatus(message.status) ? null : runningDurationFromTiming(existingTiming);
  if (runningDuration !== null) {
    const finishedAt = incomingTiming.finishedAt ?? incomingTiming.lastEventAt ?? null;
    if (!finishedAt) return terminalMessageTimingPatchForNormalize(value);
    return messageTimingPatchFromProjection({ ...(incomingTiming ?? {}), startedAt: existingTiming?.startedAt ?? incomingTiming.startedAt ?? null, lastEventAt: incomingTiming.lastEventAt ?? finishedAt, finishedAt, durationMs: runningDuration, valuesRedacted: incomingTiming.valuesRedacted !== false && existingTiming?.valuesRedacted !== false });
  }
  return terminalMessageTimingPatchForNormalize(value);
}

export function messageTimingSealPatchForProjectionMerge(message: ChatMessage, previous: ChatMessage | null): Partial<ChatMessage> {
  if (previous && isProjectionRunningAgentMessageForTiming(previous) && isProjectionRunningAgentMessageForTiming(message)) return messageRunningSealPatchForProjectionMerge(message, previous);
  if (previous && isProjectionRunningAgentMessageForTiming(previous) && isTerminalMessageStatus(message.status)) return messageTerminalFromRunningSealPatchForProjectionMerge(message, previous);
  return messageTerminalSealPatchForProjectionMerge(message, previous);
}

function messageRunningSealPatchForProjectionMerge(message: ChatMessage, previous: ChatMessage): Partial<ChatMessage> {
  const previousTiming = normalizeTimingProjection(previous);
  if (!previousTiming?.startedAt) return messageTimingPatch(message);
  const incomingTiming = normalizeTimingProjection(message);
  const previousStartedMs = timestampMs(previousTiming.startedAt);
  const incomingStartedMs = timestampMs(incomingTiming?.startedAt);
  const startedAt = incomingStartedMs !== null && previousStartedMs !== null && incomingStartedMs < previousStartedMs
    ? incomingTiming?.startedAt ?? previousTiming.startedAt
    : previousTiming.startedAt;
  const previousLastMs = timestampMs(previousTiming.lastEventAt);
  const incomingLastMs = timestampMs(incomingTiming?.lastEventAt);
  const lastEventAt = incomingLastMs !== null && (previousLastMs === null || incomingLastMs >= previousLastMs)
    ? incomingTiming?.lastEventAt ?? previousTiming.lastEventAt ?? startedAt
    : previousTiming.lastEventAt ?? incomingTiming?.lastEventAt ?? startedAt;
  const timing = {
    ...previousTiming,
    ...(incomingTiming ?? {}),
    startedAt,
    lastEventAt,
    finishedAt: null,
    durationMs: null,
    valuesRedacted: incomingTiming?.valuesRedacted !== false && previousTiming.valuesRedacted !== false,
  } as WorkbenchTurnTimingProjection;
  return messageTimingPatchFromProjection(timing);
}

function isProjectionRunningAgentMessageForTiming(message: ChatMessage): boolean {
  if (message.role !== "agent") return false;
  const status = normalizedStatusText(message.status);
  if (status && isTerminalMessageStatus(status)) return false;
  return status === null || isTraceActiveStatus(status) || message.traceAutoLifecycle === "running";
}

function messageTerminalFromRunningSealPatchForProjectionMerge(message: ChatMessage, previous: ChatMessage): Partial<ChatMessage> {
  const incomingPatch = terminalMessageTimingPatchForNormalize(message);
  const incomingTiming = normalizeTimingProjection(incomingPatch) ?? normalizeTimingProjection(message);
  const previousTiming = normalizeTimingProjection(previous);
  const runningDuration = runningDurationFromTiming(previousTiming);
  if (runningDuration === null) return incomingPatch;
  const incomingDuration = firstPositiveFiniteNumber(incomingTiming?.durationMs) ?? positiveDurationBetween(incomingTiming?.startedAt, incomingTiming?.finishedAt) ?? positiveDurationBetween(incomingTiming?.startedAt, incomingTiming?.lastEventAt);
  const durationMs = Math.max(incomingDuration ?? 0, runningDuration);
  const finishedAt = incomingTiming?.finishedAt ?? incomingTiming?.lastEventAt ?? null;
  if (!finishedAt) return incomingPatch;
  const timing = {
    ...(incomingTiming ?? {}),
    startedAt: previousTiming?.startedAt ?? incomingTiming?.startedAt ?? null,
    lastEventAt: incomingTiming?.lastEventAt ?? finishedAt,
    finishedAt,
    durationMs,
    valuesRedacted: incomingTiming?.valuesRedacted !== false && previousTiming?.valuesRedacted !== false,
  } as WorkbenchTurnTimingProjection;
  return messageTimingPatchFromProjection(timing);
}

function messageTerminalSealPatchForProjectionMerge(message: ChatMessage, previous: ChatMessage | null): Partial<ChatMessage> {
  const incomingTimingPatch = messageTimingPatch(message);
  if (!previous || !isTerminalMessageStatus(previous.status)) return incomingTimingPatch;
  const patch: Partial<ChatMessage> = { status: previous.status, traceAutoLifecycle: previous.traceAutoLifecycle ?? "terminal" };
  if (typeof previous.text === "string" && previous.text.trim()) patch.text = previous.text;
  const finalResponse = (previous as Record<string, unknown>).finalResponse;
  if (finalResponse) patch.finalResponse = finalResponse;
  const previousTiming = normalizeTimingProjection(previous);
  if (firstPositiveFiniteNumber(previousTiming?.durationMs) === null) return { ...terminalMessageTimingPatchForNormalize(message), ...patch };
  return { ...messageTimingPatch(previous), ...patch };
}

export function terminalMessageTimingPatchForNormalize(value: unknown): Partial<ChatMessage> {
  const timing = normalizeTimingProjection(value);
  if (!timing) return {};
  const record = recordValue(value);
  const durationMs = firstPositiveFiniteNumber(timing.durationMs)
    ?? positiveDurationBetween(timing.startedAt, timing.finishedAt)
    ?? positiveDurationBetween(timing.startedAt, timing.lastEventAt)
    ?? positiveDurationBetween(timing.startedAt, record?.updatedAt)
    ?? (timing.durationMs === 0 ? 0 : null);
  return messageTimingPatchFromProjection({ ...timing, durationMs });
}

function isTerminalTimingSource(value: unknown): boolean {
  const record = recordValue(value);
  if (!record) return false;
  const runnerTrace = recordValue(record.runnerTrace);
  return record.terminal === true || isTerminalMessageStatus(record.status) || isTerminalMessageStatus(record.traceStatus) || isTerminalMessageStatus(runnerTrace?.status) || isTerminalMessageStatus(runnerTrace?.traceStatus) || normalizeTimingProjection(value)?.finishedAt != null;
}

function runningDurationFromTiming(timing: WorkbenchTurnTimingProjection | null): number | null {
  const startedMs = timestampMs(timing?.startedAt);
  if (startedMs === null) return null;
  const duration = Math.trunc(Date.now() - startedMs);
  return duration > 0 ? Math.max(1000, duration) : 1000;
}

export function positiveDurationBetween(startedAt: unknown, finishedAt: unknown): number | null {
  const startedMs = timestampMs(startedAt);
  const finishedMs = timestampMs(finishedAt);
  if (startedMs === null || finishedMs === null || finishedMs <= startedMs) return null;
  return Math.trunc(finishedMs - startedMs);
}

function timestampMs(value: unknown): number | null {
  if (typeof value !== "string") return null;
  const text = value.trim();
  if (!text) return null;
  const parsed = Date.parse(text);
  return Number.isFinite(parsed) ? parsed : null;
}

export function messageStatusPatchForTerminalMerge(message: ChatMessage, resultStatus: string | null, terminal: boolean): Partial<ChatMessage> {
  if (isTerminalMessageStatus(message.status) && (!terminal || !isTerminalMessageStatus(resultStatus))) return { status: message.status, traceAutoLifecycle: message.traceAutoLifecycle ?? "terminal" };
  if (!terminal || isTerminalMessageStatus(message.status)) return {};
  if (resultStatus && isTerminalMessageStatus(resultStatus)) return { status: resultStatus as ChatMessage["status"] };
  return {};
}

export function turnResultTerminalSealBlocked(result: AgentChatResultResponse | TraceSnapshot | null | undefined): boolean {
  const record = recordValue(result);
  if (!record) return false;
  const status = normalizedStatusText(record.status) ?? null;
  const explicitBlocked = record.terminalSealBlocked === true || firstNonEmptyString(record.waitingFor) === "final_response";
  if (!explicitBlocked && status !== "completed") return false;
  if (terminalFinalResponseTextFromTurnResult(record as AgentChatResultResponse)) return false;
  return explicitBlocked || status === "completed";
}

export function turnResultStatusForMerge(result: AgentChatResultResponse | TraceSnapshot | null | undefined): string | null {
  const record = recordValue(result);
  if (!record) return null;
  if (turnResultTerminalSealBlocked(result)) return "running";
  return normalizedStatusText(record.status) ?? null;
}

export function turnResultIsTerminalForMerge(result: AgentChatResultResponse | TraceSnapshot | null | undefined): boolean {
  const record = recordValue(result);
  if (!record || turnResultTerminalSealBlocked(result)) return false;
  return record.terminal === true || isTerminalMessageStatus(normalizedStatusText(record.status));
}

export function terminalMessagePatchFromTurnResult(message: ChatMessage, result: AgentChatResultResponse): Partial<ChatMessage> | null {
  const resultStatus = turnResultStatusForMerge(result);
  const terminal = turnResultIsTerminalForMerge(result);
  if (!terminal) return null;
  const resultError = normalizeAgentError(result.error ?? null);
  const resultProjection = projectionFromResult(result);
  const mergedRunnerTrace = mergeTerminalResultTrace(message.runnerTrace, result);
  const clearCompletedDiagnostics = shouldClearCompletedTurnDiagnostics(resultStatus, resultError);
  const runnerTrace = clearCompletedDiagnostics ? clearRunnerTraceTransientDiagnostics(mergedRunnerTrace) : mergedRunnerTrace;
  const error = resultError ?? (clearCompletedDiagnostics ? null : normalizeAgentError(runnerTrace?.error ?? message.error));
  const projection = clearCompletedDiagnostics ? nonBlockingProjection(resultProjection) : resultProjection ?? runnerTrace.projection ?? message.projection ?? null;
  const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
  const bodyPatch = terminalMessageBodyPatchFromTurnResult(message, result, resultStatus);
  return {
    ...messageTimingPatchForMerge(message, result),
    ...messageStatusPatchForTerminalMerge(message, resultStatus, terminal),
    ...bodyPatch,
    runnerTrace,
    error,
    projection,
    projectionStatus: projection?.projectionStatus ?? null,
    projectionHealth: projection?.projectionHealth ?? null,
    blocker: projection?.blocker ?? null,
    agentRun: agentRun ?? undefined,
    traceAutoLifecycle: "terminal",
    updatedAt: new Date().toISOString()
  };
}

export function terminalAuthorityMessageFromTurnResult(result: AgentChatResultResponse): ChatMessage | null {
  const traceId = firstNonEmptyString(result.traceId);
  if (!traceId) return null;
  const now = new Date().toISOString();
  const sessionId = firstNonEmptyString(result.sessionId) ?? null;
  const threadId = firstNonEmptyString(result.threadId) ?? null;
  const createdAt = firstNonEmptyString(result.startedAt, result.createdAt, result.updatedAt, now) ?? now;
  const messageId = `msg_${traceId}_terminal_authority`;
  const seed: ChatMessage = {
    id: messageId,
    messageId,
    role: "agent",
    title: "Code Agent",
    text: "",
    status: "running",
    traceId,
    sessionId,
    threadId,
    createdAt,
    updatedAt: now,
    traceAutoLifecycle: "running",
    runnerTrace: {
      traceId,
      sessionId: sessionId ?? undefined,
      threadId: threadId ?? undefined,
      status: "running",
      updatedAt: now
    }
  };
  const patch = terminalMessagePatchFromTurnResult(seed, result);
  if (!patch) return null;
  const message = { ...seed, ...patch };
  return messageHasTerminalResponse(message) ? message : null;
}

function terminalMessageBodyPatchFromTurnResult(message: ChatMessage, result: AgentChatResultResponse, resultStatus: string | null): Partial<ChatMessage> {
  const status = normalizedStatusText(resultStatus);
  if (!isTerminalMessageStatus(status)) return {};
  const finalText = terminalFinalResponseTextFromTurnResult(result);
  if (!finalText) return {};
  const traceId = firstNonEmptyString(result.traceId, message.traceId, message.runnerTrace?.traceId) ?? null;
  return {
    finalResponse: {
      text: finalText,
      status,
      traceId,
      sealed: true,
      source: "turn-result",
      valuesRedacted: true
    },
    text: projectedAgentMessageText({ status, finalText })
  };
}

function terminalFinalResponseTextFromTurnResult(result: AgentChatResultResponse): string | null {
  const record = recordValue(result);
  const direct = firstNonEmptyString(
    finalResponseText(record?.finalResponse),
    messageText(record?.assistantText),
    messageText(record?.finalText),
    messageText(record?.reply),
    messageText(record?.text),
    messageText(record?.content),
    messageText(record?.message)
  );
  if (direct) return direct;
  const resultTrace = recordValue(record?.runnerTrace);
  return firstNonEmptyString(
    terminalEvidenceFinalText(record?.terminalEvidence),
    terminalEvidenceFinalText(resultTrace?.terminalEvidence),
    terminalAssistantFinalEventText(firstArray(record?.events, record?.traceEvents, resultTrace?.events))
  ) ?? null;
}

function terminalEvidenceFinalText(value: unknown): string | null {
  const record = recordValue(value);
  if (!record) return null;
  return firstNonEmptyString(
    finalResponseText(record.finalResponse),
    messageText(record.assistantText),
    messageText(record.finalText),
    messageText(record.reply),
    messageText(record.text),
    messageText(record.content),
    messageText(record.message),
    terminalEvidenceFinalText(record.evidence),
    terminalEvidenceFinalText(record.payload)
  ) ?? null;
}

function terminalAssistantFinalEventText(events: TraceEvent[]): string | null {
  for (const event of [...events].reverse()) {
    if (!traceEventHasTerminalEvidence(event)) continue;
    const record = recordValue(event);
    if (!record) continue;
    const payload = recordValue(record.payload);
    const text = firstNonEmptyString(
      finalResponseText(record.finalResponse),
      messageText(record.assistantText),
      messageText(record.finalText),
      messageText(record.reply),
      messageText(record.text),
      messageText(record.content),
      messageText(record.message),
      finalResponseText(payload?.finalResponse),
      messageText(payload?.assistantText),
      messageText(payload?.finalText),
      messageText(payload?.reply),
      messageText(payload?.text),
      messageText(payload?.content),
      messageText(payload?.message)
    );
    if (text) return text;
  }
  return null;
}

export function firstPositiveFiniteNumber(...values: unknown[]): number | null {
  for (const value of values) {
    const number = firstFiniteNumber(value);
    if (number !== undefined && number > 0) return number;
  }
  return null;
}

export function maxFiniteNumber(...values: unknown[]): number | null {
  let max: number | null = null;
  for (const value of values) {
    const number = Number(value);
    if (!Number.isFinite(number) || number < 0) continue;
    const normalized = Math.trunc(number);
    max = max === null ? normalized : Math.max(max, normalized);
  }
  return max;
}

export function projectionFromResult(result: AgentChatResultResponse | TraceSnapshot | Record<string, unknown> | null | undefined): ProjectionDiagnostic | null {
  return normalizeProjectionDiagnostic(result) ?? normalizeProjectionDiagnostic(recordValue(result)?.runnerTrace) ?? null;
}

export function projectionFromMessage(message: ChatMessage, trace: ChatMessage["runnerTrace"] | null | undefined): ProjectionDiagnostic | null {
  return normalizeProjectionDiagnostic(message.projection ?? message) ?? normalizeProjectionDiagnostic(trace?.projection ?? trace) ?? null;
}

export function shouldSuppressTransientWorkbenchReadFailure(result: ApiResult<unknown>): boolean {
  const diagnostic = normalizeErrorDiagnostic(result.diagnostic, result.apiError?.diagnostic);
  const code = firstStringOrNumber(result.apiError?.code, diagnostic?.code);
  const category = firstNonEmptyString(result.apiError?.category, diagnostic?.category);
  const source = firstNonEmptyString(result.apiError?.source, diagnostic?.source);
  if (code === "workbench_detail_read_cooldown" || code === "workbench_detail_read_throttled" || code === "workbench_read_hydration_cooldown" || code === "workbench_read_hydration_throttled") return true;
  return result.status === 0 && source === "browser" && code === "browser_network_error" && category === "network";
}

export function agentErrorFromApiFailure(result: ApiResult<unknown>, projection: ProjectionDiagnostic, fallback: string): ChatMessage["error"] {
  return normalizeAgentError(result.apiError ?? projection.apiError ?? projection.blocker ?? { message: result.error ?? fallback, diagnostic: result.diagnostic ?? projection.diagnostic }) ?? { message: result.error ?? fallback, diagnostic: result.diagnostic ?? projection.diagnostic };
}

export function messageNeedsTerminalDiagnostics(message: ChatMessage): boolean {
  if (message.role !== "agent") return false;
  if (!isTerminalMessageStatus(message.status)) return false;
  if (messageHasTerminalResponse(message)) return false;
  const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
  if (!traceId) return false;
  const agentRun = agentRunFromMessage(message);
  const error = normalizeAgentError(message.error ?? message.runnerTrace?.error);
  return !agentRun || (message.status !== "completed" && !error);
}

export function messageNeedsTraceDetailRead(message: ChatMessage): boolean {
  if (message.role !== "agent") return false;
  const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
  if (!traceId) return false;
  if (!messageHasActiveTraceForDetailRead(message)) return false;
  const trace = message.runnerTrace;
  const events = Array.isArray(trace?.events) ? trace.events : [];
  const eventCount = firstFiniteNumber(trace?.eventCount) ?? events.length;
  if (trace?.fullTraceLoaded === true && trace.eventsCompacted !== true) return events.length === 0 && (eventCount > 0 || isTraceActiveStatus(trace?.status) || isTraceActiveStatus(message.status));
  return events.length === 0 || trace?.eventsCompacted === true || trace?.fullTraceLoaded !== true;
}

function messageHasActiveTraceForDetailRead(message: ChatMessage): boolean {
  const trace = message.runnerTrace;
  return isTraceActiveStatus(message.status)
    || isTraceActiveStatus(trace?.status)
    || isTraceActiveStatus(trace?.traceStatus);
}

export function traceHasTerminalResponse(traceId: string | null | undefined, source: ChatMessage[]): boolean {
  const id = firstNonEmptyString(traceId);
  if (!id) return false;
  return source.some((message) => firstNonEmptyString(message.traceId, message.runnerTrace?.traceId) === id && messageHasTerminalResponse(message));
}

export function messageHasTerminalResponse(message: ChatMessage | null | undefined): boolean {
  if (!message || message.role !== "agent") return false;
  if (!isTerminalMessageStatus(message.status)) return false;
  return Boolean(firstNonEmptyString(message.text, messageText((message as Record<string, unknown>).content), finalResponseText((message as Record<string, unknown>).finalResponse)));
}

export function traceHasEvents(trace: ChatMessage["runnerTrace"]): boolean {
  return Array.isArray(trace?.events) && trace.events.length > 0;
}

export function agentRunFromResult(result: AgentChatResultResponse, runnerTrace: ChatMessage["runnerTrace"]): AgentRunProvenance | null {
  return asAgentRun(result.agentRun ?? runnerTrace?.agentRun);
}

export function agentRunFromMessage(message: ChatMessage): AgentRunProvenance | null {
  return asAgentRun((message as Record<string, unknown>).agentRun ?? message.runnerTrace?.agentRun);
}

export function asAgentRun(value: unknown): AgentRunProvenance | null {
  return value && typeof value === "object" ? value as AgentRunProvenance : null;
}

export function normalizeAgentError(value: unknown): ChatMessage["error"] | null {
  if (!value) return null;
  if (typeof value === "string") return value.trim() ? { message: value.trim() } : null;
  if (value && typeof value === "object") {
    const record = value as Record<string, unknown>;
    const diagnostic = normalizeErrorDiagnostic(record.diagnostic, recordValue(record.apiError)?.diagnostic, recordValue(record.error)?.diagnostic);
    const message = firstNonEmptyString(record.message, record.userMessage, record.reason, record.detail, record.error);
    const code = firstStringOrNumber(record.code, record.failureKind, record.name, diagnostic?.code);
    const category = firstNonEmptyString(record.category, diagnostic?.category, record.layer, record.type);
    const providerStatus = typeof record.providerStatus === "number" ? record.providerStatus : Number.isFinite(Number(record.providerStatus)) ? Number(record.providerStatus) : undefined;
    return { ...record, code: code ?? undefined, message: message ?? undefined, userMessage: firstNonEmptyString(record.userMessage, message), category: category ?? undefined, layer: firstNonEmptyString(record.layer, diagnostic?.layer), route: firstNonEmptyString(record.route, diagnostic?.route), traceId: firstNonEmptyString(record.traceId, diagnostic?.traceId), requestId: firstNonEmptyString(record.requestId, diagnostic?.requestId), source: firstNonEmptyString(record.source, diagnostic?.source), retryable: firstBoolean(record.retryable, diagnostic?.retryable), diagnostic, providerStatus };
  }
  return { message: String(value) };
}

export function agentErrorDisplayText(value: unknown): string | null {
  const error = normalizeAgentError(value);
  if (!error) return null;
  return firstNonEmptyString(error.message, typeof error.userMessage === "string" ? error.userMessage : null, error.code ? `Code Agent 请求失败：${error.code}` : null);
}

export function isTerminalMessageStatus(value: unknown): boolean {
  return ["completed", "failed", "blocked", "timeout", "canceled", "cancelled", "stale", "thread-resume-failed"].includes(String(value ?? "").trim().toLowerCase().replace(/_/gu, "-"));
}

function traceEventHasTerminalEvidence(event: unknown): boolean {
  const record = recordValue(event);
  return record?.terminal === true || record?.final === true || record?.replyAuthority === true;
}

function traceSnapshotHasTerminalEvidence(snapshot: unknown): boolean {
  const record = recordValue(snapshot);
  if (!record) return false;
  if (record.terminal === true || record.terminalEvidence) return true;
  const events = Array.isArray(record.events) ? record.events : [];
  return events.some((event) => traceEventHasTerminalEvidence(event));
}

export function traceResultHasTerminalEvidence(result: unknown): boolean {
  const record = recordValue(result);
  return traceSnapshotHasTerminalEvidence(result) || traceSnapshotHasTerminalEvidence(record?.runnerTrace);
}

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

function firstArray(...values: unknown[]): TraceEvent[] {
  for (const value of values) {
    if (Array.isArray(value)) return value as TraceEvent[];
  }
  return [];
}

export function firstFiniteNumber(...values: unknown[]): number | undefined {
  for (const value of values) {
    const number = typeof value === "number" ? value : Number(value);
    if (Number.isFinite(number)) return number;
  }
  return undefined;
}

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

function firstBoolean(...values: unknown[]): boolean | undefined {
  for (const value of values) {
    if (typeof value === "boolean") return value;
  }
  return undefined;
}

export function finalResponseText(value: unknown): string | null {
  if (!value || typeof value !== "object") return null;
  const record = value as Record<string, unknown>;
  return firstNonEmptyString(messageText(record.text), messageText(record.content), messageText(record.message));
}

export function projectedAgentMessageText(input: { status: unknown; finalText?: string | null; errorText?: string | null; baseText?: string | null }): string {
  const text = firstNonEmptyString(input.finalText, input.baseText);
  if (!isFailureTerminalStatus(input.status)) return text ?? "";
  return firstNonEmptyString(input.errorText, text) ?? "";
}

function isFailureTerminalStatus(value: unknown): boolean {
  return ["failed", "blocked", "timeout", "canceled", "cancelled", "stale", "thread-resume-failed"].includes(String(value ?? "").trim().toLowerCase().replace(/_/gu, "-"));
}

export function messageText(value: unknown): string | null {
  if (typeof value === "string") return value.trim() || null;
  if (typeof value === "number" || typeof value === "boolean") return String(value);
  if (Array.isArray(value)) return value.map(messageText).filter((item): item is string => Boolean(item)).join("\n") || null;
  if (value && typeof value === "object") {
    const record = value as Record<string, unknown>;
    return firstNonEmptyString(messageText(record.text), messageText(record.content), messageText(record.message), messageText(record.summary), messageText(record.preview), messageText(record.title));
  }
  return null;
}

export function isTraceActiveStatus(status: unknown): boolean {
  return ["accepted", "pending", "queued", "dispatching", "streaming", "processing", "running", "retrying", "busy", "creating"].includes(String(status ?? "").trim().toLowerCase().replace(/_/gu, "-"));
}
