// SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-07-12-p0-admission-failure-identity.
// Responsibility: Project a rejected HTTP admission as one local assistant message, never as a Kafka/trace event.

import type { ChatMessage, ProjectionDiagnostic, WorkbenchTurnTimingProjection } from "@/types";

export interface RejectedWorkbenchAdmissionInput {
  message: string;
  error: ChatMessage["error"];
  projection: ProjectionDiagnostic;
  submittedAt: string;
  finishedAt?: string;
}

export function projectRejectedWorkbenchAdmission(message: ChatMessage, input: RejectedWorkbenchAdmissionInput): ChatMessage {
  const finishedAt = timestamp(input.finishedAt) ?? new Date().toISOString();
  const startedAt = timestamp(input.submittedAt) ?? finishedAt;
  const durationMs = Math.max(0, Date.parse(finishedAt) - Date.parse(startedAt));
  const sourceError = input.error ?? {};
  const failureIdentity = firstText(sourceError.failureIdentity, `admission:${message.messageId || message.id}`) ?? `admission:${message.id}`;
  const admissionState = firstText(sourceError.admissionState, "rejected-before-durable-dispatch") ?? "rejected-before-durable-dispatch";
  const error = {
    ...sourceError,
    failureKind: firstText(sourceError.failureKind, sourceError.code, "submission-unavailable"),
    reason: firstText(sourceError.reason, "unknown-dispatch-failure"),
    recoveryAction: firstText(sourceError.recoveryAction, sourceError.retryable === true ? "retry-submit" : "inspect-agentrun-diagnostic"),
    failureIdentity,
    primaryPresentation: "assistant-message",
    admissionState,
    traceId: firstText(sourceError.traceId, message.traceId),
    runId: firstText(sourceError.runId),
    commandId: firstText(sourceError.commandId),
    dispatchIntentId: firstText(sourceError.dispatchIntentId),
    valuesPrinted: false
  } as NonNullable<ChatMessage["error"]>;
  const timing = {
    startedAt,
    lastEventAt: finishedAt,
    finishedAt,
    durationMs,
    valuesRedacted: true
  } as WorkbenchTurnTimingProjection;
  return {
    ...message,
    status: "failed",
    text: input.message,
    error,
    projection: input.projection,
    projectionStatus: input.projection.projectionStatus ?? null,
    projectionHealth: input.projection.projectionHealth ?? null,
    blocker: input.projection.blocker ?? null,
    runnerTrace: null,
    traceEvents: [],
    traceAutoLifecycle: null,
    admission: {
      state: admissionState,
      durableDispatch: false,
      runId: error.runId ?? null,
      commandId: error.commandId ?? null,
      dispatchIntentId: error.dispatchIntentId ?? null,
      valuesPrinted: false
    },
    failureIdentity,
    timing,
    startedAt,
    lastEventAt: finishedAt,
    finishedAt,
    durationMs,
    updatedAt: finishedAt
  };
}

function timestamp(value: unknown): string | null {
  if (typeof value !== "string" || !value.trim() || !Number.isFinite(Date.parse(value))) return null;
  return new Date(Date.parse(value)).toISOString();
}

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