/**
 * v2.6 Phase 2 — warm-vs-one-shot routing predicate for ACP chat agents.
 *
 * The warm ACP backend keys its persistent process + ACP session on (chat_id,
 * agent) — exactly like the opencode-server backend. A task therefore only routes
 * to the warm pool when it carries BOTH a `session_id` and a `chat_id`, i.e. it
 * came from a real chat tab (the coder message route + skills route stamp both).
 *
 * Session-less creators — arena contestants, MCP-created tasks, generic
 * `POST /api/tasks`, `new_task` — leave one or both null. Those keep the existing
 * one-shot worktree-per-task ACP path (`runExternalAgent`), which spawns a fresh
 * `goose acp` / `qwen --acp` / `reasonix acp` per turn and never holds a warm process. Routing them
 * warm would either synthesize a degenerate (null, agent) key or create a chat per
 * arena contestant — neither is wanted, so they stay one-shot.
 *
 * Pure, so it's unit-testable; the dispatcher consumes it.
 */
const WARM_CAPABLE_AGENTS = new Set(['goose', 'qwen', 'reasonix']);

export function shouldUseWarmBackend(task: {
  agent: string | null;
  session_id: string | null;
  chat_id: string | null;
}): boolean {
  if (!task.agent || !WARM_CAPABLE_AGENTS.has(task.agent)) return false;
  return task.session_id != null && task.chat_id != null;
}

/**
 * Map an ACP prompt `stopReason` to the backend's ok/fail contract (TurnResult.ok).
 *
 * ACP's `StopReason` union includes normal completions (`end_turn`, `max_tokens`,
 * `max_turn_requests`) and abnormal ones (`refusal`, `cancelled`). Only the latter
 * two read as a failed turn; everything else (including an undefined/absent reason,
 * which we default to `end_turn`) is a successful completion. Pure so it's testable
 * independently of the warm process.
 */
export function isTurnOkForStopReason(stopReason: string | null | undefined): boolean {
  const reason = stopReason ?? 'end_turn';
  return reason !== 'refusal' && reason !== 'cancelled';
}
