/*
 * SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-18-p0-unique-projection; draft-2026-06-20-p1-zero-split-durable-realtime; PJ2026-010205 HWLAB接入 draft-2026-06-17-r0.
 * 职责: WorkbenchProjectionWriter 组件入口。唯一封装 Code Agent/AgentRun facts 到 Workbench session projection facts 的持久化写入。
 */
import { createHash } from "node:crypto";

import { defaultCodeAgentTraceStore } from "./code-agent-trace-store.ts";
import { safeTraceId } from "./server-http-utils.ts";
import { createWorkbenchTurnProjection, normalizeWorkbenchStatus, projectionDiagnostics, TERMINAL_STATUSES } from "./workbench-turn-projection.ts";

export async function writeWorkbenchProjectionSession({ accessController, runtimeStore = null, traceStore = defaultCodeAgentTraceStore, traceId, ownerUserId, ownerRole = null, sessionId = null, projectId = null, conversationId = null, threadId = null, status = "active", session = {}, payload = null, params = {}, preserveLastTraceId = false } = {}) {
  if (!ownerUserId || typeof accessController?.recordAgentSessionOwner !== "function") return null;
  const safeId = safeTraceId(traceId);
  try {
    const ownerRecord = await accessController.recordAgentSessionOwner({
      ownerUserId,
      ownerRole,
      sessionId,
      projectId,
      conversationId,
      threadId,
      traceId: preserveLastTraceId ? undefined : safeId,
      status,
      session
    });
    await writeWorkbenchProjectionFacts({
      runtimeStore,
      traceStore,
      traceId: safeId,
      ownerUserId,
      ownerRole,
      sessionId: ownerRecord?.id ?? sessionId,
      projectId: ownerRecord?.projectId ?? projectId,
      conversationId: ownerRecord?.conversationId ?? conversationId,
      threadId: ownerRecord?.threadId ?? threadId,
      status: ownerRecord?.status ?? status,
      session: ownerRecord?.session ?? session,
      payload,
      params
    });
    return ownerRecord;
  } catch (error) {
    if (safeId) appendProjectionDiagnostic(traceStore, safeId, {
      type: "session-owner",
      status: "degraded",
      label: "projection-writer:session-owner:persist-failed",
      errorCode: "workbench_projection_session_persist_failed",
      message: error?.message ?? "Workbench projection session persistence failed.",
      valuesPrinted: false
    });
    return null;
  }
}

export async function writeWorkbenchSessionAdmissionFact({ runtimeStore = null, session = null, ownerUserId = null, ownerRole = null, projectId = null, conversationId = null, threadId = null, status = "idle", now = new Date().toISOString() } = {}) {
  if (typeof runtimeStore?.writeWorkbenchFacts !== "function" || !session || typeof session !== "object") return null;
  const sessionId = textValue(session.id ?? session.sessionId);
  if (!sessionId) return null;
  const createdAt = timestampValue(session.startedAt ?? session.createdAt ?? session.session?.createdAt ?? now);
  const updatedAt = timestampValue(session.updatedAt ?? createdAt);
  const normalizedStatus = normalizeWorkbenchStatus(session.status ?? session.session?.sessionStatus ?? status) || "idle";
  const fact = {
    sessionId,
    ownerUserId: textValue(session.ownerUserId ?? ownerUserId) || null,
    ownerRole: textValue(session.ownerRole ?? ownerRole) || null,
    projectId: textValue(session.projectId ?? projectId) || null,
    agentId: textValue(session.agentId) || "hwlab-code-agent",
    conversationId: textValue(session.conversationId ?? conversationId) || null,
    threadId: textValue(session.threadId ?? threadId) || null,
    status: normalizedStatus,
    lastTraceId: safeTraceId(session.lastTraceId ?? session.traceId) || null,
    projectedSeq: 0,
    sourceSeq: 0,
    sourceEventId: `${sessionId}:session-admission`,
    terminal: false,
    sealed: false,
    sessionJson: {
      ...(session.session && typeof session.session === "object" ? session.session : {}),
      sessionStatus: normalizedStatus,
      source: textValue(session.session?.source) || "manual-session-create",
      providerProfile: textValue(session.session?.providerProfile) || null,
      valuesRedacted: true,
      secretMaterialStored: false
    },
    createdAt,
    updatedAt,
    valuesRedacted: true
  };
  return runtimeStore.writeWorkbenchFacts({ facts: { sessions: [fact] } }, {
    sessionId,
    ownerUserId: fact.ownerUserId,
    projectId: fact.projectId,
    valuesPrinted: false
  });
}

export async function writeWorkbenchProjectionEvent({ runtimeStore = null, event = {}, requestMeta = {} } = {}) {
  if (typeof runtimeStore?.writeWorkbenchFacts !== "function") return null;
  if (typeof runtimeStore?.allocateWorkbenchProjectedSeq !== "function") throw new Error("Workbench projection event writer requires a durable projectedSeq allocator.");
  const traceId = safeTraceId(event.traceId ?? requestMeta.traceId);
  if (!traceId) return null;
  const projectedAt = new Date().toISOString();
  const sourceSeq = nonNegativeInteger(event.sourceSeq ?? event.seq);
  const occurredAt = timestampValue(event.createdAt ?? event.occurredAt ?? event.updatedAt ?? projectedAt);
  const sessionId = textValue(event.sessionId ?? requestMeta.sessionId) || null;
  const turnId = textValue(event.turnId) || traceId;
  const terminal = event.terminal === true || TERMINAL_STATUSES.has(normalizeWorkbenchStatus(event.status));
  const status = terminal ? normalizeWorkbenchStatus(event.status) : "running";
  const previousCheckpoint = await latestWorkbenchCheckpoint(runtimeStore, traceId);
  const previousTiming = normalizeTimingProjection(previousCheckpoint?.timing) ?? normalizeTimingProjection(previousCheckpoint);
  const explicitStartedAt = optionalTimestampValue(event.startedAt ?? event.traceStartedAt ?? event.runnerStartedAt);
  const startedAt = explicitStartedAt ?? previousTiming?.startedAt ?? (sourceSeq <= 1 ? occurredAt : null);
  const lastEventAt = latestTimestamp(previousTiming?.lastEventAt, occurredAt, projectedAt);
  const finishedAt = terminal ? latestTimestamp(previousTiming?.lastEventAt, optionalTimestampValue(event.finishedAt), occurredAt, projectedAt) : null;
  const timing = eventTimingProjection({ startedAt, lastEventAt, finishedAt, terminal });
  const sourceEventId = workbenchSourceEventId(event, { traceId, sourceSeq, occurredAt });
  const allocation = await runtimeStore.allocateWorkbenchProjectedSeq({
    traceId,
    sessionId,
    turnId,
    runId: textValue(event.runId) || null,
    commandId: textValue(event.commandId) || null,
    sourceSeq,
    sourceEventId,
    eventType: textValue(event.eventType ?? event.type ?? event.label) || "event",
    occurredAt,
    updatedAt: projectedAt
  }, {
    traceId,
    sessionId,
    agentSessionId: requestMeta.agentSessionId ?? sessionId,
    valuesPrinted: false
  });
  const projectedSeq = nonNegativeInteger(allocation?.projectedSeq);
  if (projectedSeq <= 0) throw new Error("Workbench projection allocator returned an invalid projectedSeq.");
  const eventId = textValue(event.id) || stableFactId("wte", { traceId, sourceEventId });
  const messageFinalText = finalResponseTextValue(event.finalResponse, event.assistantText, event.reply);
  const messageFact = sessionId ? normalizeMessageFact({
    messageId: eventProjectionAssistantMessageId(traceId, event),
    role: "agent",
    status,
    traceId,
    turnId,
    text: terminal && messageFinalText ? messageFinalText : "",
    projectedSeq,
    sourceSeq,
    sourceEventId,
    terminal,
    sealed: terminal,
    timing,
    startedAt: timing.startedAt,
    lastEventAt: timing.lastEventAt,
    finishedAt: timing.finishedAt,
    durationMs: timing.durationMs,
    createdAt: occurredAt,
    updatedAt: projectedAt,
    source: "workbench-event-projection"
  }, 0, { traceId, sessionId, turnId, terminal, terminalStatus: status, finalText: messageFinalText, timestamp: projectedAt, timing }) : null;
  const facts = {
    sessions: sessionId ? [{
      sessionId,
      status,
      lastTraceId: traceId,
      projectedSeq,
      sourceSeq,
      sourceEventId,
      terminal,
      sealed: terminal,
      timing,
      startedAt: timing.startedAt,
      lastEventAt: timing.lastEventAt,
      finishedAt: timing.finishedAt,
      durationMs: timing.durationMs,
      updatedAt: projectedAt
    }] : [],
    traceEvents: [{
      ...event,
      id: eventId,
      traceId,
      sessionId,
      turnId,
      messageId: textValue(event.messageId) || null,
      sourceSeq,
      sourceEventId,
      projectedSeq,
      eventType: textValue(event.eventType ?? event.type ?? event.label) || "event",
      terminal,
      sealed: terminal,
      timing,
      startedAt: timing.startedAt,
      lastEventAt: timing.lastEventAt,
      finishedAt: timing.finishedAt,
      durationMs: timing.durationMs,
      occurredAt,
      updatedAt: projectedAt
    }],
    messages: messageFact ? [messageFact] : [],
    checkpoints: [{
      traceId,
      sessionId,
      turnId,
      runId: textValue(event.runId) || null,
      commandId: textValue(event.commandId) || null,
      projectedSeq,
      sourceSeq,
      sourceEventId,
      projectionStatus: terminal ? "caught_up" : "projecting",
      projectionHealth: "healthy",
      terminal,
      sealed: terminal,
      timing,
      startedAt: timing.startedAt,
      lastEventAt: timing.lastEventAt,
      finishedAt: timing.finishedAt,
      durationMs: timing.durationMs,
      diagnostic: {
        lastEventLabel: textValue(event.label) || null,
        lastEventStatus: textValue(event.status) || null,
        valuesRedacted: true
      },
      updatedAt: projectedAt
    }]
  };
  return runtimeStore.writeWorkbenchFacts({ facts }, {
    traceId,
    sessionId,
    agentSessionId: requestMeta.agentSessionId ?? sessionId,
    valuesPrinted: false
  });
}

function eventProjectionAssistantMessageId(traceId, event = {}) {
  const explicit = textValue(event.messageId ?? event.assistantMessageId ?? event.assistantMessage?.messageId);
  if (explicit) return explicit;
  const traceSuffix = (safeTraceId(traceId) || String(traceId || "trace"))
    .replace(/^trc_/u, "")
    .replace(/[^A-Za-z0-9_.:-]/gu, "_")
    .slice(0, 48) || "trace";
  return `msg_${traceSuffix}_agent`;
}

async function writeWorkbenchProjectionFacts({ runtimeStore = null, traceStore = defaultCodeAgentTraceStore, traceId = null, ownerUserId = null, ownerRole = null, sessionId = null, projectId = null, conversationId = null, threadId = null, status = "active", session = {}, payload = null, params = {} } = {}) {
  if (typeof runtimeStore?.writeWorkbenchFacts !== "function") return null;
  const facts = buildWorkbenchProjectionFacts({ traceId, ownerUserId, ownerRole, sessionId, projectId, conversationId, threadId, status, session, payload, params });
  if (isEmptyFacts(facts)) return null;
  try {
    return await runtimeStore.writeWorkbenchFacts({ facts }, {
      traceId: facts.checkpoints[0]?.traceId ?? traceId,
      sessionId: facts.sessions[0]?.sessionId ?? sessionId,
      ownerUserId,
      projectId,
      valuesPrinted: false
    });
  } catch (error) {
    const safeId = safeTraceId(traceId);
    if (safeId) appendProjectionDiagnostic(traceStore, safeId, {
      type: "facts",
      status: "degraded",
      label: "projection-writer:facts:persist-failed",
      errorCode: error?.code ?? "workbench_facts_persist_failed",
      message: error?.message ?? "Workbench facts persistence failed.",
      terminal: false
    });
    return null;
  }
}

function buildWorkbenchProjectionFacts({ traceId = null, ownerUserId = null, ownerRole = null, sessionId = null, projectId = null, conversationId = null, threadId = null, status = "active", session = {}, payload = null, params = {} } = {}) {
  const safeId = safeTraceId(traceId ?? session?.traceId ?? payload?.traceId ?? params?.traceId);
  const resolvedSessionId = textValue(sessionId ?? session?.sessionId ?? payload?.sessionId ?? params?.sessionId) || null;
  const resolvedConversationId = textValue(conversationId ?? session?.conversationId ?? payload?.conversationId ?? params?.conversationId) || null;
  const resolvedThreadId = textValue(threadId ?? session?.threadId ?? payload?.threadId ?? params?.threadId) || null;
  const agentRun = payload?.agentRun ?? session?.agentRun ?? null;
  const timestamp = timestampValue(payload?.updatedAt ?? session?.updatedAt ?? payload?.createdAt ?? session?.createdAt);
  const normalizedStatus = normalizeWorkbenchStatus(payload?.status ?? session?.sessionStatus ?? status);
  const projection = createWorkbenchTurnProjection({ traceId: safeId, result: payload, session: { id: resolvedSessionId, status: normalizedStatus, session }, trace: payload?.runnerTrace ?? null });
  const projectedStatus = normalizeWorkbenchStatus(projection.status ?? normalizedStatus);
  const terminal = projection.terminal === true;
  const terminalStatus = terminal ? (TERMINAL_STATUSES.has(projectedStatus) ? projectedStatus : normalizedStatus) : projectedStatus;
  const timing = projectionTimingForStatus(projection.timing, terminal);
  const diagnostic = projectionDiagnostics({ traceId: safeId, result: payload, trace: payload?.runnerTrace ?? null, projection });
  const finalText = finalResponseTextValue(projection.finalResponse, payload?.finalResponse, session?.finalResponse, payload?.assistantText);
  const messages = normalizeMessages(workbenchProjectionInputMessages({ session, payload, params, traceId: safeId, timestamp, terminal, terminalStatus, finalText }), {
    traceId: safeId,
    sessionId: resolvedSessionId,
    conversationId: resolvedConversationId,
    threadId: resolvedThreadId,
    terminal,
    terminalStatus,
    finalText,
    timestamp,
    timing
  });
  return {
    sessions: resolvedSessionId ? [{
      sessionId: resolvedSessionId,
      ownerUserId,
      ownerRole,
      projectId: projectId ?? session?.projectId ?? payload?.projectId ?? params?.projectId ?? null,
      conversationId: resolvedConversationId,
      threadId: resolvedThreadId,
      status: terminal ? terminalStatus : "running",
      lastTraceId: safeId,
      projectedSeq: nonNegativeInteger(projection.lastProjectedSeq),
      sourceSeq: nonNegativeInteger(projection.lastProjectedSeq),
      sourceEventId: safeId,
      terminal,
      sealed: terminal,
      sessionJson: session,
      timing,
      startedAt: timing.startedAt,
      lastEventAt: timing.lastEventAt,
      finishedAt: timing.finishedAt,
      durationMs: timing.durationMs,
      createdAt: timestampValue(session?.createdAt ?? payload?.createdAt ?? timestamp),
      updatedAt: timestamp
    }] : [],
    messages,
    parts: messages.flatMap((message) => message.text ? [{
      partId: stableFactId("wpt", { messageId: message.messageId, index: 0 }),
      messageId: message.messageId,
      sessionId: message.sessionId,
      turnId: message.turnId,
      traceId: message.traceId,
      partIndex: 0,
      partType: "text",
      status: message.status,
      projectedSeq: message.projectedSeq,
      sourceSeq: message.sourceSeq,
      sourceEventId: message.sourceEventId,
      terminal: message.terminal,
      sealed: message.sealed,
      text: message.text,
      updatedAt: message.updatedAt
    }] : []),
    turns: safeId && resolvedSessionId ? [{
      turnId: projection.turnId ?? safeId,
      sessionId: resolvedSessionId,
      traceId: safeId,
      messageId: messages.find((message) => message.role !== "user")?.messageId ?? null,
      status: projection.status ?? normalizedStatus,
      projectedSeq: nonNegativeInteger(projection.lastProjectedSeq),
      sourceSeq: nonNegativeInteger(projection.lastProjectedSeq),
      sourceEventId: safeId,
      terminal: projection.terminal,
      sealed: projection.terminal,
      finalResponse: projection.finalResponse,
      diagnostic,
      timing,
      startedAt: timing.startedAt,
      lastEventAt: timing.lastEventAt,
      finishedAt: timing.finishedAt,
      durationMs: timing.durationMs,
      updatedAt: timestamp
    }] : [],
    checkpoints: safeId ? [{
      traceId: safeId,
      sessionId: resolvedSessionId,
      turnId: projection.turnId ?? safeId,
      runId: textValue(agentRun?.runId ?? projection.sourceRunId) || null,
      commandId: textValue(agentRun?.commandId ?? projection.sourceCommandId) || null,
      projectedSeq: nonNegativeInteger(projection.lastProjectedSeq),
      sourceSeq: nonNegativeInteger(agentRun?.lastSeq ?? projection.lastProjectedSeq),
      sourceEventId: safeId,
      projectionStatus: projection.terminal ? "caught_up" : "projecting",
      projectionHealth: diagnostic.projectionHealth === "unavailable" ? "unavailable" : diagnostic.projectionHealth === "stalled" ? "stalled" : "healthy",
      terminal: projection.terminal,
      sealed: projection.terminal,
      diagnostic,
      timing,
      startedAt: timing.startedAt,
      lastEventAt: timing.lastEventAt,
      finishedAt: timing.finishedAt,
      durationMs: timing.durationMs,
      updatedAt: timestamp
    }] : []
  };
}

function workbenchProjectionInputMessages({ session = {}, payload = {}, params = {}, traceId = null, timestamp = null, terminal = false, terminalStatus = "completed", finalText = null } = {}) {
  if (Array.isArray(session?.messages) && session.messages.length > 0) return session.messages;
  if (Array.isArray(payload?.messages) && payload.messages.length > 0) return payload.messages;
  if (!traceId) return [];
  const turnId = textValue(payload?.turnId) || traceId;
  const userText = textValue(params?.message ?? params?.prompt ?? payload?.prompt ?? payload?.message);
  const userMessageId = textValue(payload?.userMessageId ?? payload?.userMessage?.messageId) || stableFactId("msg", { traceId, role: "user" });
  const assistantMessageId = textValue(payload?.assistantMessageId ?? payload?.messageId ?? payload?.assistantMessage?.messageId) || stableFactId("msg", { traceId, role: "agent" });
  const createdAt = timestampValue(timestamp ?? payload?.createdAt ?? payload?.updatedAt);
  const messages = [];
  if (userText) {
    messages.push({
      messageId: userMessageId,
      role: "user",
      status: "sent",
      text: userText,
      traceId,
      turnId,
      createdAt,
      updatedAt: createdAt,
      valuesPrinted: false
    });
  }
  messages.push({
    messageId: assistantMessageId,
    role: "agent",
    status: terminal ? terminalStatus : "running",
    text: terminal && finalText ? finalText : "",
    traceId,
    turnId,
    timing: payload?.timing,
    startedAt: payload?.startedAt,
    lastEventAt: payload?.lastEventAt,
    finishedAt: payload?.finishedAt,
    durationMs: payload?.durationMs,
    createdAt,
    updatedAt: createdAt,
    valuesPrinted: false
  });
  return messages;
}

function normalizeMessages(messages, context) {
  const normalized = Array.isArray(messages) ? messages.map((message, index) => normalizeMessageFact(message, index, context)).filter(Boolean) : [];
  if (context?.terminal && context?.finalText && context?.traceId && !normalized.some((message) => message.role !== "user" && message.traceId === context.traceId)) {
    const projected = normalizeMessageFact({ role: "agent", status: context.terminalStatus ?? "completed", text: context.finalText, traceId: context.traceId, source: "workbench-terminal-projection" }, normalized.length, context);
    if (projected) normalized.push(projected);
  }
  return normalized;
}

function normalizeMessageFact(message = {}, index, { traceId, sessionId, conversationId, threadId, terminal, terminalStatus = "completed", finalText = null, timestamp, timing: contextTiming }) {
  const messageId = textValue(message.messageId ?? message.id) || stableFactId("msg", { traceId, index, role: message.role });
  if (!sessionId || !messageId) return null;
  const role = textValue(message.role) || "agent";
  const messageTraceId = safeTraceId(message.traceId ?? message.turnId);
  const appliesToContextTrace = role !== "user" && Boolean(traceId && (!messageTraceId || messageTraceId === traceId));
  const contextTerminalStatus = normalizeWorkbenchStatus(terminalStatus) || "completed";
  const status = appliesToContextTrace && terminal ? contextTerminalStatus : normalizeWorkbenchStatus(message.status ?? (terminal && role !== "user" ? contextTerminalStatus : role === "user" ? "sent" : "running"));
  const messageTerminal = role !== "user" && TERMINAL_STATUSES.has(status);
  const messageTiming = normalizeTimingProjection(message.timing) ?? normalizeTimingProjection(message);
  const contextTimingProjection = appliesToContextTrace ? normalizeTimingProjection(contextTiming) : null;
  const timing = role === "user" ? messageTiming ?? emptyTimingProjection() : contextTimingProjection ?? messageTiming ?? emptyTimingProjection();
  const terminalCompleted = messageTerminal && status === "completed";
  const text = appliesToContextTrace && terminal && finalText ? finalText : textValue(message.text ?? message.content ?? message.message);
  return {
    ...message,
    messageId,
    sessionId,
    conversationId,
    threadId,
    turnId: textValue(message.turnId) || traceId,
    traceId: safeTraceId(message.traceId ?? traceId) ?? traceId,
    role,
    status,
    projectedSeq: nonNegativeInteger(message.projectedSeq ?? message.seq ?? index + 1),
    sourceSeq: nonNegativeInteger(message.sourceSeq ?? message.seq ?? index + 1),
    sourceEventId: textValue(message.sourceEventId) || `${messageId}:0`,
    terminal: messageTerminal,
    sealed: messageTerminal,
    text,
    timing,
    startedAt: timing.startedAt,
    lastEventAt: timing.lastEventAt,
    finishedAt: timing.finishedAt,
    durationMs: timing.durationMs,
    createdAt: timestampValue(message.createdAt ?? timestamp),
    updatedAt: timestampValue(message.updatedAt ?? timestamp),
    ...(terminalCompleted ? { error: null, blocker: null, projection: null, projectionStatus: null, projectionHealth: null, staleMs: null } : {}),
    valuesPrinted: false
  };
}

function finalResponseTextValue(...values) {
  for (const value of values) {
    if (value && typeof value === "object") {
      const nested = finalResponseTextValue(value.text, value.content, value.message, value.summary, value.preview);
      if (nested) return nested;
      continue;
    }
    const text = textValue(value);
    if (text) return text;
  }
  return null;
}

async function latestWorkbenchCheckpoint(runtimeStore, traceId) {
  if (typeof runtimeStore?.queryWorkbenchFacts !== "function") return null;
  const result = await runtimeStore.queryWorkbenchFacts({ traceId, families: ["checkpoints"], limit: 1 });
  return Array.isArray(result?.facts?.checkpoints) ? result.facts.checkpoints[0] ?? null : null;
}

function projectionTimingForStatus(value, terminal) {
  const timing = normalizeTimingProjection(value) ?? emptyTimingProjection();
  if (terminal) return timing;
  return { ...timing, finishedAt: null, durationMs: null, valuesRedacted: timing.valuesRedacted !== false };
}

function eventTimingProjection({ startedAt = null, lastEventAt = null, finishedAt = null, terminal = false } = {}) {
  const normalizedStartedAt = optionalTimestampValue(startedAt);
  const normalizedLastEventAt = optionalTimestampValue(lastEventAt);
  const normalizedFinishedAt = terminal ? optionalTimestampValue(finishedAt ?? normalizedLastEventAt) : null;
  return {
    startedAt: normalizedStartedAt,
    lastEventAt: normalizedLastEventAt,
    finishedAt: normalizedFinishedAt,
    durationMs: terminal ? elapsedBetween(normalizedStartedAt, normalizedFinishedAt ?? normalizedLastEventAt) : null,
    valuesRedacted: true
  };
}

function normalizeTimingProjection(value) {
  const source = value && typeof value === "object" ? value : null;
  if (!source) return null;
  const startedAt = optionalTimestampValue(source.startedAt);
  const lastEventAt = optionalTimestampValue(source.lastEventAt);
  const finishedAt = optionalTimestampValue(source.finishedAt);
  const durationMs = durationValue(source.durationMs);
  if (!startedAt && !lastEventAt && !finishedAt && durationMs === null) return null;
  return { ...source, startedAt, lastEventAt, finishedAt, durationMs, valuesRedacted: source.valuesRedacted !== false };
}

function emptyTimingProjection() {
  return { startedAt: null, lastEventAt: null, finishedAt: null, durationMs: null, valuesRedacted: true };
}

function durationValue(value) {
  const number = Number(value);
  return Number.isFinite(number) && number >= 0 ? Math.trunc(number) : null;
}

function elapsedBetween(startedAt, endedAt) {
  const start = Date.parse(String(startedAt ?? ""));
  const end = Date.parse(String(endedAt ?? ""));
  if (!Number.isFinite(start) || !Number.isFinite(end) || end < start) return null;
  return Math.trunc(end - start);
}

function latestTimestamp(...values) {
  let latest = null;
  let latestMs = Number.NEGATIVE_INFINITY;
  for (const value of values) {
    const timestamp = optionalTimestampValue(value);
    const ms = Date.parse(String(timestamp ?? ""));
    if (!Number.isFinite(ms) || ms < latestMs) continue;
    latest = timestamp;
    latestMs = ms;
  }
  return latest;
}

function isEmptyFacts(facts) {
  return Object.values(facts).every((items) => !Array.isArray(items) || items.length === 0);
}

function stableFactId(prefix, value) {
  return `${prefix}_${createHash("sha256").update(JSON.stringify(sortJson(value))).digest("hex").slice(0, 32)}`;
}

function workbenchSourceEventId(event = {}, { traceId, sourceSeq, occurredAt } = {}) {
  const explicit = textValue(event.sourceEventId ?? event.id);
  if (explicit) return explicit;
  const source = textValue(event.source);
  const label = textValue(event.label ?? event.type ?? event.eventType) || "event";
  if (source && sourceSeq > 0) return `${source}:${sourceSeq}:${label}`;
  return stableFactId("wte_source", {
    traceId,
    source: source || null,
    sourceSeq,
    label,
    eventType: textValue(event.eventType ?? event.type) || null,
    status: textValue(event.status) || null,
    itemId: textValue(event.itemId ?? event.messageId) || null,
    runId: textValue(event.runId) || null,
    commandId: textValue(event.commandId) || null,
    occurredAt
  });
}

function sortJson(value) {
  if (Array.isArray(value)) return value.map(sortJson);
  if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortJson(value[key])]));
  return value;
}

function nonNegativeInteger(value) {
  const parsed = Number.parseInt(value ?? "", 10);
  return Number.isInteger(parsed) && parsed >= 0 ? parsed : 0;
}

function timestampValue(value) {
  const ms = Date.parse(String(value ?? ""));
  return Number.isFinite(ms) ? new Date(ms).toISOString() : new Date().toISOString();
}

function optionalTimestampValue(value) {
  const ms = Date.parse(String(value ?? ""));
  return Number.isFinite(ms) ? new Date(ms).toISOString() : null;
}

function textValue(value) {
  return String(value ?? "").trim();
}

export function appendProjectionDiagnostic(traceStore = defaultCodeAgentTraceStore, traceId, event = {}) {
  const safeId = safeTraceId(traceId);
  if (!safeId || typeof traceStore?.append !== "function") return null;
  return traceStore.append(safeId, {
    type: "projection-diagnostic",
    status: "degraded",
    label: "projection:diagnostic",
    ...event,
    valuesPrinted: false
  });
}
