// Copyright (c) 2026 defconxt. All rights reserved.
// Licensed under AGPL-3.0 — see LICENSE file for details.
// NERF is a trademark of defconxt.

/**
 * Circuit Breaker — 3-strike architecture rule.
 *
 * Tracks consecutive failures per issue ID. After 3 failed attempts
 * on the same issue, halts execution and surfaces an architecture
 * review question instead of attempting Fix #4.
 *
 * Source: Superpowers systematic-debugging "Three-Fix Architecture Questioning Rule"
 *
 * @module gates/circuit-breaker
 */

// ---------------------------------------------------------------------------
// ThreeStrikeBreaker
// ---------------------------------------------------------------------------

/**
 * @typedef {Object} IssueState
 * @property {number} consecutiveFailures — Current streak of failures
 * @property {string[]} failureDescriptions — Description of each failure
 * @property {string} lastAttempt — ISO timestamp of last attempt
 * @property {number} totalAttempts — Total attempts (including successes)
 */

export class ThreeStrikeBreaker {
  /**
   * @param {{ threshold?: number }} [opts]
   */
  constructor(opts = {}) {
    /** @type {Map<string, IssueState>} */
    this._issues = new Map();
    this._threshold = opts.threshold ?? 3;
  }

  /**
   * Record an attempt on an issue.
   * @param {string} issueId — Unique identifier for the issue
   * @param {boolean} success — Whether the attempt succeeded
   * @param {string} [description] — Description of what was tried
   */
  recordAttempt(issueId, success, description = '') {
    let state = this._issues.get(issueId);
    if (!state) {
      state = { consecutiveFailures: 0, failureDescriptions: [], lastAttempt: '', totalAttempts: 0 };
      this._issues.set(issueId, state);
    }

    state.totalAttempts++;
    state.lastAttempt = new Date().toISOString();

    if (success) {
      state.consecutiveFailures = 0;
      state.failureDescriptions = [];
    } else {
      state.consecutiveFailures++;
      if (description) {
        state.failureDescriptions.push(description);
      }
    }
  }

  /**
   * Check if the circuit breaker should halt for this issue.
   * @param {string} issueId
   * @returns {boolean}
   */
  shouldHalt(issueId) {
    const state = this._issues.get(issueId);
    if (!state) return false;
    return state.consecutiveFailures >= this._threshold;
  }

  /**
   * Get the architecture review question for a halted issue.
   * @param {string} issueId
   * @returns {{ halted: boolean, question: string, failures: string[], totalAttempts: number }}
   */
  getArchitectureQuestion(issueId) {
    const state = this._issues.get(issueId);
    if (!state || state.consecutiveFailures < this._threshold) {
      return { halted: false, question: '', failures: [], totalAttempts: 0 };
    }

    const failureList = state.failureDescriptions
      .map((d, i) => `  ${i + 1}. ${d}`)
      .join('\n');

    const question = [
      `CIRCUIT BREAKER: ${this._threshold} consecutive failures on "${issueId}".`,
      '',
      'Failed attempts:',
      failureList || '  (no descriptions recorded)',
      '',
      'Before attempting another fix, answer these questions:',
      '  1. Is this pattern fundamentally sound?',
      '  2. Are we sticking with it through sheer inertia?',
      '  3. Should we refactor architecture vs. continue fixing symptoms?',
      '',
      'Discuss with human before attempting more fixes.',
    ].join('\n');

    return {
      halted: true,
      question,
      failures: state.failureDescriptions,
      totalAttempts: state.totalAttempts,
    };
  }

  /**
   * Reset tracking for an issue.
   * @param {string} issueId
   */
  reset(issueId) {
    this._issues.delete(issueId);
  }

  /**
   * Get current state for an issue.
   * @param {string} issueId
   * @returns {IssueState|null}
   */
  getState(issueId) {
    return this._issues.get(issueId) || null;
  }

  /** Number of tracked issues */
  get size() {
    return this._issues.size;
  }
}
