// classifyStartupFailure — pure mapping from evidence shape to bucket.
//
// Precedence rules (walked top-down; first match wins):
//
//   1. processExitCode !== undefined        → ProviderProcessGone
//      The agent process is dead; nothing else matters. This is the
//      highest-confidence signal because it's observable from outside
//      the container (docker exit code, child_process exit).
//
//   2. pluginErrors.length > 0              → PluginStartupFailed
//      Boot-time stderr from a plugin/skill source is definitive.
//      Comes before transport/handshake checks because a failed
//      plugin can leave transport+mcp in 'pending' permanently.
//
//   3. transportStatus === 'failed'         → TransportDead
//      Channel-open never succeeded — agent is alive but unreachable.
//
//   4. mcpHandshakeStatus === 'failed'      → McpHandshakeStall
//      Channel opened, MCP handshake explicitly failed.
//
//   5. mcpHandshakeStatus === 'pending'
//      AND elapsedSecondsSinceCreation > 30 → McpHandshakeStall
//      Time-bounded inference: a never-completed handshake after 30s
//      is the stall signal even without an explicit failure marker.
//
//   6. lastPhase === 'TrustRequired'
//      AND elapsedSecondsSinceCreation > 10 → TrustGateUnanswered
//      Agent reached the trust gate; no approval ever came back.
//      10s is the operator's attention budget — past that, the
//      agent is silently stuck on a human gate.
//
//   7. promptSentAt !== undefined
//      AND firstThoughtAt === undefined     → PromptMisdelivery
//      Dispatch landed on the host side but the agent never produced
//      a first thought — the prompt didn't reach the agent process.
//
//   8. lastPhase === 'TrustRequired'        → TrustGateUnanswered (fallback)
//      Stuck at the trust gate even under 10s — still the most likely
//      explanation for a Failed transition from that phase.
//
//   9. fallthrough                          → PromptMisdelivery
//      The classifier is total: every Failed transition gets a bucket.
//      PromptMisdelivery is the most operator-actionable "we don't
//      know why but the dispatch path is the prime suspect" default.
//
// Tests in __tests__/classify.test.mjs assert exactly one case per
// bucket. The function is pure: no I/O, no side effects, deterministic
// — same evidence in always yields the same bucket out.

import { WorldStartupFailureKind } from './failure-kinds.mjs';

const MCP_HANDSHAKE_STALL_THRESHOLD_SECONDS = 30;
const TRUST_GATE_UNANSWERED_THRESHOLD_SECONDS = 10;

/**
 * Map a WorldStartupEvidence bundle to its WorldStartupFailureKind.
 *
 * @param {import('./evidence.mjs').WorldStartupEvidence} evidence
 * @returns {import('./failure-kinds.mjs').WorldStartupFailureKind}
 */
export function classifyStartupFailure(evidence) {
  // 1. Process exited — terminal signal, short-circuits all other checks.
  if (evidence.processExitCode !== undefined) {
    return WorldStartupFailureKind.ProviderProcessGone;
  }

  // 2. Plugin boot errors — definitive boot-time failure.
  if (evidence.pluginErrors.length > 0) {
    return WorldStartupFailureKind.PluginStartupFailed;
  }

  // 3. Transport explicitly failed — agent alive but unreachable.
  if (evidence.transportStatus === 'failed') {
    return WorldStartupFailureKind.TransportDead;
  }

  // 4. MCP handshake explicitly failed.
  if (evidence.mcpHandshakeStatus === 'failed') {
    return WorldStartupFailureKind.McpHandshakeStall;
  }

  // 5. MCP handshake pending past threshold — inferred stall.
  if (
    evidence.mcpHandshakeStatus === 'pending' &&
    evidence.elapsedSecondsSinceCreation > MCP_HANDSHAKE_STALL_THRESHOLD_SECONDS
  ) {
    return WorldStartupFailureKind.McpHandshakeStall;
  }

  // 6. Stuck on trust gate past operator-attention threshold.
  if (
    evidence.lastPhase === 'TrustRequired' &&
    evidence.elapsedSecondsSinceCreation > TRUST_GATE_UNANSWERED_THRESHOLD_SECONDS
  ) {
    return WorldStartupFailureKind.TrustGateUnanswered;
  }

  // 7. Prompt sent but agent never produced a first thought.
  if (evidence.promptSentAt !== undefined && evidence.firstThoughtAt === undefined) {
    return WorldStartupFailureKind.PromptMisdelivery;
  }

  // 8. Still at trust gate under threshold — bucket as trust-gate.
  if (evidence.lastPhase === 'TrustRequired') {
    return WorldStartupFailureKind.TrustGateUnanswered;
  }

  // 9. Total-function fallback.
  return WorldStartupFailureKind.PromptMisdelivery;
}
