// Recovery scenarios — named mappings from WorldStartupFailureKind (or a
// special non-FSM signal) to a deterministic RecoveryRecipe.
//
// Order within each recipe is load-bearing: steps execute in sequence,
// first failure short-circuits. Designed for ONE bounded auto-attempt;
// callers MUST NOT retry a scenario — the engine's idempotency guard
// enforces this at the (worldId, failureKind) level.
//
// The 'stale-branch' scenario has no failureKind (null) — it is triggered
// by a non-FSM signal (e.g. CI indicating the branch is stale). The engine
// accepts null as a valid key but treats it as a distinct bucket.

/**
 * @typedef {import('./recipes.mjs').RecoveryStep}  RecoveryStep
 * @typedef {import('./recipes.mjs').RecoveryRecipe} RecoveryRecipe
 * @typedef {import('../lifecycle/failure-kinds.mjs').WorldStartupFailureKind | null} FailureKindOrNull
 */

/**
 * @typedef {object} FailureScenario
 * @property {string}           name         — kebab-case identifier
 * @property {FailureKindOrNull} failureKind  — the FSM bucket this scenario handles (null = non-FSM trigger)
 * @property {string}           description  — one-line human summary
 * @property {RecoveryRecipe}   recipe
 */

/** @type {readonly FailureScenario[]} */
export const FAILURE_SCENARIOS = Object.freeze([
  {
    name: 'trust-gate-stuck',
    failureKind: 'TrustGateUnanswered',
    description: 'Agent reached TrustRequired but no trust approval arrived within the timeout.',
    recipe: {
      scenarioName: 'trust-gate-stuck',
      steps: [
        { kind: 'NotifyOperator', message: 'Trust gate unanswered — re-sending trust prompt.' },
        { kind: 'ResendTrustPrompt' },
        { kind: 'WaitFor', durationMs: 30_000 },
      ],
    },
  },
  {
    name: 'prompt-misdelivery',
    failureKind: 'PromptMisdelivery',
    description: 'Dispatch was sent but the agent never received it (transport mismatch).',
    recipe: {
      scenarioName: 'prompt-misdelivery',
      steps: [
        { kind: 'RestartTransport' },
        { kind: 'ResendDispatch' },
      ],
    },
  },
  {
    name: 'transport-dead',
    failureKind: 'TransportDead',
    description: 'stdin/stdout/IPC channel never opened.',
    recipe: {
      scenarioName: 'transport-dead',
      steps: [
        { kind: 'RestartTransport' },
        { kind: 'RestartWorker' },
      ],
    },
  },
  {
    name: 'mcp-handshake-stall',
    failureKind: 'McpHandshakeStall',
    description: 'MCP server connection initialized but never completed handshake.',
    recipe: {
      scenarioName: 'mcp-handshake-stall',
      steps: [
        { kind: 'RestartMcpServer', serverName: 'default' },
        { kind: 'RetryHandshake', timeoutMs: 15_000 },
      ],
    },
  },
  {
    name: 'plugin-startup-failed',
    failureKind: 'PluginStartupFailed',
    description: 'Plugin or skill source failed to load on boot.',
    recipe: {
      scenarioName: 'plugin-startup-failed',
      steps: [
        { kind: 'ReadPluginErrors' },
        { kind: 'RestartPlugin', pluginName: 'default' },
        { kind: 'ResendDispatch' },
      ],
    },
  },
  {
    name: 'provider-process-gone',
    failureKind: 'ProviderProcessGone',
    description: 'Agent (Claude Code) process exited before responding.',
    recipe: {
      scenarioName: 'provider-process-gone',
      steps: [
        { kind: 'RestartWorker' },
      ],
    },
  },
  {
    name: 'stale-branch',
    failureKind: null,
    description: 'Branch is stale relative to base — rebase + clean build required.',
    recipe: {
      scenarioName: 'stale-branch',
      steps: [
        { kind: 'RebaseBranch' },
        { kind: 'CleanBuild' },
      ],
    },
  },
]);

/**
 * Find the scenario that handles a given failureKind (or null for non-FSM triggers).
 *
 * @param {FailureKindOrNull} failureKind
 * @returns {FailureScenario | undefined}
 */
export function findScenarioForKind(failureKind) {
  return FAILURE_SCENARIOS.find((s) => s.failureKind === failureKind);
}
