/**
 * F1 — per-task abort registry. A Stop on an external-agent task must reach the
 * in-flight run and abort its child / prompt. Each external run-function registers
 * its per-turn AbortController here keyed by task id; the cancel route calls
 * `cancel(taskId)` to fire it; the run-function's `.finally` deletes the entry.
 *
 * Idempotent by construction:
 *   - `cancel()` on an already-aborted controller no-ops (AbortController.abort()
 *     is idempotent) → a rapid double-Stop is safe.
 *   - `cancel()` on an unknown / already-finished task returns false → a
 *     cancel-after-natural-exit (entry already deleted) and a Stop on a native
 *     boocode task (never registered) are both safe no-ops.
 *
 * Pure (no DB / child / IO) so the abort wiring + idempotency contract is
 * unit-testable in isolation — mirrors the turn-guard / lifecycle-decisions
 * pure-helper precedent.
 */
export interface CancelRegistry {
  /** Create + store an AbortController for this task, returning it for the run. */
  register(taskId: string): AbortController;
  /** Abort the task's in-flight run. Returns false when no controller is registered. */
  cancel(taskId: string): boolean;
  /** Drop the task's entry (called from the run's `.finally`). No-op if absent. */
  delete(taskId: string): void;
  /** Whether a controller is currently registered for this task. */
  has(taskId: string): boolean;
}

export function createCancelRegistry(): CancelRegistry {
  const controllers = new Map<string, AbortController>();
  return {
    register(taskId) {
      const ac = new AbortController();
      controllers.set(taskId, ac);
      return ac;
    },
    cancel(taskId) {
      const ac = controllers.get(taskId);
      if (!ac) return false;
      ac.abort();
      return true;
    },
    delete(taskId) {
      controllers.delete(taskId);
    },
    has(taskId) {
      return controllers.has(taskId);
    },
  };
}
