// W4 — Halt-shape detection for the host-cp chunk-write proxy.
//
// When plan-DO's dispatchPlanningAgent (W1) trips a guardrail, it
// emits a chunk with chunk_type='goal_mode_assumption' and content
// matching: `[assumption: <cap>-tripped — spent $X.XXXX of $Y]` (or
// similar shape per GuardrailState.haltChunkText()).
//
// host-cp's /api/plan-chat proxy passes the chunk through to the
// chunks substrate AND, if it detects a halt-shaped chunk, broadcasts
// a typed `plan.halted` event on host-stream so the SPA's
// PlanHaltBanner subscriber fires.
//
// Extracted as a pure fn so it can be unit-tested without booting
// the host-cp server.

const HALT_RE =
  /^\[assumption:\s*(usd|turns|tool_calls|wall_clock)-tripped(?:\s*—\s*spent\s*\$([0-9.]+))?/;

/**
 * Detect a halt-shaped chunk + extract its components.
 *
 * Returns null when:
 *   - chunk is null/undefined
 *   - chunk_type isn't 'goal_mode_assumption'
 *   - content doesn't match the halt regex
 *
 * Returns the parsed payload otherwise. Caller broadcasts this as
 * the `plan.halted` event payload.
 */
export function detectHaltChunk(chunk) {
  if (!chunk || typeof chunk !== 'object') return null;
  if (chunk.chunk_type !== 'goal_mode_assumption') return null;
  if (typeof chunk.chunk !== 'string') return null;
  const m = chunk.chunk.match(HALT_RE);
  if (!m) return null;
  return {
    plan_id: chunk.session_id ?? 'unknown',
    operator_id: chunk.operator_id ?? 'unknown',
    halt_reason: m[1],
    usd_spent_so_far: m[2] ? Number.parseFloat(m[2]) : undefined,
    halted_at: Date.now(),
  };
}
