/**
 * CFN Loop Orchestrator - Complete TypeScript Implementation
 * Orchestrates the Fail Never (CFN) Loop workflow with test-driven validation
 * Supports MVP, Standard, and Enterprise execution modes
 *
 * Version: 3.0.0
 */

import { gateCheck, GateCheckParams } from './helpers/gate-check';
import { collectConsensus, validateConsensus } from './helpers/consensus';
import { spawnLoop3Agents, spawnLoop2Agents, SpawnResult } from './helpers/spawn-agents';
import { TestResult, ExecutionMode } from './types';
import { execSync } from 'child_process';
import * as path from 'path';
import * as fs from 'fs/promises';

/**
 * Execution phases in the CFN Loop
 */
export type LoopPhase = 'loop3' | 'loop2' | 'product-owner' | 'complete';

/**
 * Product owner decision outcomes
 */
export type ProductOwnerDecision = 'PROCEED' | 'ITERATE' | 'ABORT' | null;

/**
 * Orchestration configuration
 */
export interface OrchestrationConfig {
  taskId: string;
  mode: ExecutionMode;
  maxIterations: number;
  aceReflect?: boolean;
  loop3Agents?: string[];
  loop2Agents?: string[];
  productOwner?: string;
  successCriteriaEnabled?: boolean;
}

/**
 * Agent execution context
 */
export interface AgentExecutionContext {
  agentId: string;
  agentType: string;
  loopType: 'loop3' | 'loop2';
  iteration: number;
  taskId: string;
  timestamp: number;
}

/**
 * Phase transition tracking
 */
export interface PhaseTransition {
  fromPhase: LoopPhase;
  toPhase: LoopPhase;
  timestamp: number;
  iteration: number;
}

/**
 * Gate check result
 */
export interface GateCheckResult {
  passed: boolean;
  passRate: number;
  threshold: number;
  gap: number;
}

/**
 * Consensus validation result
 */
export interface ConsensusValidationResult {
  passed: boolean;
  average: number;
  threshold: number;
  gap: number;
}

/**
 * Test result aggregation
 */
export interface AggregatedTestResults {
  totalPass: number;
  totalFail: number;
  totalSkip: number;
  passRate: number;
  agentCount: number;
}

/**
 * Orchestration state tracking
 */
export interface OrchestrationState {
  taskId: string;
  mode: ExecutionMode;
  iteration: number;
  currentPhase: LoopPhase;
  completedAgents: Set<string>;
  failedAgents: Set<string>;
  startTime: number;
  lastUpdateTime: number;
}

/**
 * Feedback for next iteration
 */
export interface IterationFeedback {
  gatePassRate?: number;
  consensusAverage?: number;
  previousFailures?: string[];
  reasons?: string[];
  timestamp?: number;
}

/**
 * Mode-specific configuration
 */
interface ModeThresholds {
  gateThreshold: number;
  consensusThreshold: number;
  maxIterations: number;
}

const MODE_CONFIG: Record<ExecutionMode, ModeThresholds> = {
  mvp: {
    gateThreshold: 0.70,
    consensusThreshold: 0.80,
    maxIterations: 5,
  },
  standard: {
    gateThreshold: 0.95,
    consensusThreshold: 0.90,
    maxIterations: 10,
  },
  enterprise: {
    gateThreshold: 0.98,
    consensusThreshold: 0.95,
    maxIterations: 15,
  },
};

/**
 * Main orchestrator class
 */
export class Orchestrator {
  private config: OrchestrationConfig;
  private state: OrchestrationState;
  private testResults: Map<string, TestResult> = new Map();
  private consensusScores: Map<string, number> = new Map();
  private decision: ProductOwnerDecision = null;
  private errors: Map<string, Error> = new Map();
  private phaseHistory: PhaseTransition[] = [];

  constructor(config: OrchestrationConfig) {
    // Validate configuration
    this.validateConfig(config);

    this.config = config;
    this.state = this.initializeState(config);
  }

  /**
   * Validate configuration parameters
   */
  private validateConfig(config: OrchestrationConfig): void {
    if (!config.taskId || config.taskId.trim() === '') {
      throw new Error('Task ID cannot be empty');
    }

    const validModes: ExecutionMode[] = ['mvp', 'standard', 'enterprise'];
    if (!validModes.includes(config.mode)) {
      throw new Error(`Invalid execution mode: ${config.mode}`);
    }

    if (!Number.isInteger(config.maxIterations) || config.maxIterations < 1) {
      throw new Error('Max iterations must be at least 1');
    }

    if (config.maxIterations > 100) {
      throw new Error('Max iterations cannot exceed 100');
    }
  }

  /**
   * Initialize orchestration state
   */
  private initializeState(config: OrchestrationConfig): OrchestrationState {
    const now = Date.now();

    return {
      taskId: config.taskId,
      mode: config.mode,
      iteration: 0,
      currentPhase: 'loop3',
      completedAgents: new Set(),
      failedAgents: new Set(),
      startTime: now,
      lastUpdateTime: now,
    };
  }

  /**
   * Get current orchestration state
   */
  public getState(): OrchestrationState {
    return { ...this.state, completedAgents: new Set(this.state.completedAgents), failedAgents: new Set(this.state.failedAgents) };
  }

  /**
   * Get task ID
   */
  public getTaskId(): string {
    return this.config.taskId;
  }

  /**
   * Get execution mode
   */
  public getMode(): ExecutionMode {
    return this.config.mode;
  }

  /**
   * Get maximum iterations for mode
   */
  public getMaxIterations(): number {
    return this.config.maxIterations;
  }

  /**
   * Get gate threshold for current mode
   */
  public getGateThreshold(): number {
    return MODE_CONFIG[this.config.mode].gateThreshold;
  }

  /**
   * Get consensus threshold for current mode
   */
  public getConsensusThreshold(): number {
    return MODE_CONFIG[this.config.mode].consensusThreshold;
  }

  /**
   * Transition to next phase
   */
  public transitionPhase(newPhase: LoopPhase): void {
    const transition: PhaseTransition = {
      fromPhase: this.state.currentPhase,
      toPhase: newPhase,
      timestamp: Date.now(),
      iteration: this.state.iteration,
    };

    this.phaseHistory.push(transition);
    this.state.currentPhase = newPhase;
    this.state.lastUpdateTime = Date.now();
  }

  /**
   * Increment iteration counter
   */
  public incrementIteration(): void {
    this.state.iteration++;
    this.state.lastUpdateTime = Date.now();
  }

  /**
   * Check if can continue iterating
   */
  public canContinueIterating(): boolean {
    return this.state.iteration < this.config.maxIterations;
  }

  /**
   * Check if orchestration should terminate
   */
  public shouldTerminate(): boolean {
    if (this.decision === 'PROCEED' || this.decision === 'ABORT') {
      return true;
    }

    if (this.decision === 'ITERATE' && !this.canContinueIterating()) {
      return true;
    }

    return false;
  }

  /**
   * Mark agent as completed
   */
  public markAgentComplete(agentId: string, _loopType: 'loop3' | 'loop2'): void {
    this.state.completedAgents.add(agentId);
    this.state.failedAgents.delete(agentId);
    this.state.lastUpdateTime = Date.now();
  }

  /**
   * Mark agent as failed
   */
  public markAgentFailed(agentId: string, _loopType: 'loop3' | 'loop2'): void {
    this.state.failedAgents.add(agentId);
    this.state.completedAgents.delete(agentId);
    this.state.lastUpdateTime = Date.now();
  }

  /**
   * Record execution error for agent
   */
  public recordExecutionError(agentId: string, error: Error): void {
    this.errors.set(agentId, error);
    this.markAgentFailed(agentId, 'loop3');
  }

  /**
   * Record timeout for agent
   */
  public recordTimeout(agentId: string, timeoutSeconds: number): void {
    const error = new Error(`Agent timeout after ${timeoutSeconds}s`);
    this.recordExecutionError(agentId, error);
  }

  /**
   * Record test results for agent
   */
  public recordTestResult(agentId: string, result: TestResult): void {
    this.testResults.set(agentId, result);
    this.state.lastUpdateTime = Date.now();
  }

  /**
   * Get test result for agent
   */
  public getTestResult(agentId: string): TestResult | undefined {
    return this.testResults.get(agentId);
  }

  /**
   * Aggregate test results across all agents
   */
  public aggregateTestResults(): AggregatedTestResults {
    let totalPass = 0;
    let totalFail = 0;
    let totalSkip = 0;

    for (const result of this.testResults.values()) {
      totalPass += result.pass;
      totalFail += result.fail;
      totalSkip += result.skip ?? 0;
    }

    const total = totalPass + totalFail + totalSkip;
    const passRate = total === 0 ? 0 : totalPass / total;

    return {
      totalPass,
      totalFail,
      totalSkip,
      passRate,
      agentCount: this.testResults.size,
    };
  }

  /**
   * Check gate (Loop 3 → Loop 2 transition)
   */
  public checkGate(passRate: number): GateCheckResult {
    const threshold = this.getGateThreshold();

    const params: GateCheckParams = {
      passRate,
      mode: this.config.mode,
      threshold,
    };

    const result = gateCheck(params);

    return {
      passed: result.passed,
      passRate: result.passRate,
      threshold: result.threshold,
      gap: result.gap,
    };
  }

  /**
   * Record consensus score from validator
   */
  public recordConsensusScore(validatorId: string, score: number): void {
    if (score < 0 || score > 1) {
      throw new Error(`Invalid consensus score: ${score} (must be 0.0-1.0)`);
    }

    this.consensusScores.set(validatorId, score);
    this.state.lastUpdateTime = Date.now();
  }

  /**
   * Get all consensus scores
   */
  public getConsensusScores(): number[] {
    return Array.from(this.consensusScores.values());
  }

  /**
   * Get consensus average
   */
  public getConsensusAverage(): number {
    const scores = this.getConsensusScores();

    if (scores.length === 0) {
      throw new Error('No consensus scores recorded');
    }

    const sum = scores.reduce((a, b) => a + b, 0);
    return sum / scores.length;
  }

  /**
   * Validate consensus against threshold
   */
  public validateConsensus(): ConsensusValidationResult {
    const scores = this.getConsensusScores();

    if (scores.length === 0) {
      throw new Error('No consensus scores recorded');
    }

    const result = collectConsensus(scores);
    const validation = validateConsensus({
      average: result.average,
      mode: this.config.mode,
      threshold: this.getConsensusThreshold(),
    });

    return {
      passed: validation.passed,
      average: validation.average,
      threshold: validation.threshold,
      gap: validation.gap,
    };
  }

  /**
   * Record product owner decision
   */
  public recordDecision(decision: ProductOwnerDecision): void {
    this.decision = decision;
    this.state.lastUpdateTime = Date.now();
  }

  /**
   * Get recorded decision
   */
  public getDecision(): ProductOwnerDecision {
    return this.decision;
  }

  /**
   * Parse decision from agent output
   */
  public parseDecisionFromOutput(output: string): ProductOwnerDecision {
    const normalizedOutput = output.toUpperCase();

    if (normalizedOutput.includes('PROCEED')) {
      return 'PROCEED';
    }

    if (normalizedOutput.includes('ITERATE')) {
      return 'ITERATE';
    }

    if (normalizedOutput.includes('ABORT')) {
      return 'ABORT';
    }

    return null;
  }

  /**
   * Spawn Loop 3 (implementer) agents
   */
  public async spawnLoop3Agents(agentTypes: string[]): Promise<AgentExecutionContext[]> {
    const agents: AgentExecutionContext[] = [];
    const now = Date.now();

    agentTypes.forEach((agentType, index) => {
      agents.push({
        agentId: `${agentType}-${this.state.iteration + 1}-${index + 1}`,
        agentType,
        loopType: 'loop3',
        iteration: this.state.iteration + 1,
        taskId: this.config.taskId,
        timestamp: now,
      });
    });

    return agents;
  }

  /**
   * Spawn Loop 2 (validator) agents
   */
  public async spawnLoop2Validators(validatorTypes: string[]): Promise<AgentExecutionContext[]> {
    const validators: AgentExecutionContext[] = [];
    const now = Date.now();

    validatorTypes.forEach((validatorType, index) => {
      validators.push({
        agentId: `${validatorType}-${this.state.iteration + 1}-${index + 1}`,
        agentType: validatorType,
        loopType: 'loop2',
        iteration: this.state.iteration + 1,
        taskId: this.config.taskId,
        timestamp: now,
      });
    });

    return validators;
  }

  /**
   * Build task context string for agent spawning
   */
  private buildTaskContext(): string {
    const context = {
      taskId: this.config.taskId,
      mode: this.config.mode,
      iteration: this.state.iteration,
      phase: this.state.currentPhase,
      timestamp: Date.now(),
    };
    return JSON.stringify(context);
  }

  /**
   * Wait for agents to complete via Redis coordination
   * Blocks until all agents signal completion or timeout occurs
   *
   * @param spawnResults - Results from agent spawning
   * @param timeoutSeconds - Maximum wait time (default: 300s)
   * @returns Array of completed agent IDs
   */
  private async waitForAgentsToComplete(
    spawnResults: SpawnResult[],
    timeoutSeconds: number = 300
  ): Promise<string[]> {
    const completedAgents: string[] = [];
    const startTime = Date.now();
    const projectRoot = process.env.PROJECT_ROOT || process.cwd();

    console.log(`Waiting for ${spawnResults.length} agents to complete (timeout: ${timeoutSeconds}s)...`);

    for (const result of spawnResults) {
      if (!result.success) {
        console.warn(`Skipping failed agent: ${result.agentId}`);
        continue;
      }

      const elapsedSeconds = Math.floor((Date.now() - startTime) / 1000);
      const remainingTimeout = timeoutSeconds - elapsedSeconds;

      if (remainingTimeout <= 0) {
        console.error(`Global timeout reached. Remaining agents will not be waited for.`);
        this.recordTimeout(result.agentId, timeoutSeconds);
        break;
      }

      try {
        // Wait for agent completion signal via Redis coordination
        const coordinationScript = path.join(
          projectRoot,
          '.claude/skills/cfn-coordination/coordination-wait.sh'
        );

        const channel = `agent:${result.agentId}:complete`;
        const cmd = `${coordinationScript} --task-id ${this.config.taskId} --channel ${channel} --timeout ${remainingTimeout}`;

        console.log(`Waiting for agent ${result.agentId} (timeout: ${remainingTimeout}s)...`);

        // Execute coordination wait (blocking)
        execSync(cmd, {
          encoding: 'utf8',
          stdio: 'inherit',
          timeout: remainingTimeout * 1000,
          cwd: projectRoot,
        });

        console.log(`✓ Agent ${result.agentId} completed`);
        completedAgents.push(result.agentId);
        this.markAgentComplete(result.agentId, 'loop3');
      } catch (error) {
        const errorMsg = error instanceof Error ? error.message : String(error);
        console.error(`✗ Agent ${result.agentId} failed or timed out: ${errorMsg}`);
        this.recordExecutionError(result.agentId, new Error(errorMsg));
      }
    }

    console.log(`Completed: ${completedAgents.length}/${spawnResults.length} agents`);
    return completedAgents;
  }

  /**
   * Collect agent outputs from Redis
   * Retrieves test results, confidence scores, and deliverables
   *
   * @param agentIds - List of agent IDs to collect from
   * @returns Map of agent outputs
   */
  private async collectAgentOutputs(
    agentIds: string[]
  ): Promise<Map<string, { testResult?: TestResult; confidence?: number; deliverables?: string[] }>> {
    const outputs = new Map<string, { testResult?: TestResult; confidence?: number; deliverables?: string[] }>();

    console.log(`Collecting outputs from ${agentIds.length} agents...`);

    for (const agentId of agentIds) {
      try {
        // Retrieve agent output from Redis
        const testResultJson = this.getRedisValue(`swarm:${this.config.taskId}:agent:${agentId}:test-result`);
        const confidenceStr = this.getRedisValue(`swarm:${this.config.taskId}:agent:${agentId}:confidence`);
        const deliverablesJson = this.getRedisValue(`swarm:${this.config.taskId}:agent:${agentId}:deliverables`);

        const agentOutput: { testResult?: TestResult; confidence?: number; deliverables?: string[] } = {};

        // Parse test results
        if (testResultJson) {
          try {
            const testResult = JSON.parse(testResultJson) as TestResult;
            agentOutput.testResult = testResult;
            this.recordTestResult(agentId, testResult);
            console.log(`  ${agentId}: Test results collected (${testResult.pass} pass, ${testResult.fail} fail)`);
          } catch (parseError) {
            console.warn(`  ${agentId}: Failed to parse test results: ${parseError}`);
          }
        }

        // Parse confidence score
        if (confidenceStr) {
          const confidence = parseFloat(confidenceStr);
          if (!isNaN(confidence) && confidence >= 0 && confidence <= 1) {
            agentOutput.confidence = confidence;
            console.log(`  ${agentId}: Confidence score: ${(confidence * 100).toFixed(2)}%`);
          }
        }

        // Parse deliverables
        if (deliverablesJson) {
          try {
            const deliverables = JSON.parse(deliverablesJson) as string[];
            agentOutput.deliverables = deliverables;
            console.log(`  ${agentId}: Deliverables: ${deliverables.length} files`);
          } catch (parseError) {
            console.warn(`  ${agentId}: Failed to parse deliverables: ${parseError}`);
          }
        }

        outputs.set(agentId, agentOutput);
      } catch (error) {
        const errorMsg = error instanceof Error ? error.message : String(error);
        console.error(`  ${agentId}: Failed to collect output: ${errorMsg}`);
      }
    }

    console.log(`Successfully collected outputs from ${outputs.size}/${agentIds.length} agents`);
    return outputs;
  }

  /**
   * Get value from Redis using redis-cli
   *
   * @param key - Redis key
   * @returns Value or null if not found
   */
  private getRedisValue(key: string): string | null {
    try {
      const redisHost = process.env.REDIS_HOST || 'localhost';
      const redisPort = process.env.REDIS_PORT || '6379';

      const result = execSync(`redis-cli -h ${redisHost} -p ${redisPort} GET "${key}"`, {
        encoding: 'utf8',
        stdio: ['pipe', 'pipe', 'ignore'], // Suppress stderr
      }).trim();

      return result === '(nil)' ? null : result;
    } catch (error) {
      return null;
    }
  }

  /**
   * Execute tests against agent deliverables
   * Runs test suite to validate actual agent work
   *
   * @param agentOutputs - Map of agent outputs with deliverables
   * @returns Aggregated test results
   */
  private async executeTestsOnDeliverables(
    agentOutputs: Map<string, { testResult?: TestResult; confidence?: number; deliverables?: string[] }>
  ): Promise<AggregatedTestResults> {
    console.log('Executing tests on agent deliverables...');

    const projectRoot = process.env.PROJECT_ROOT || process.cwd();
    const testCommand = process.env.TEST_COMMAND || 'npm test';

    let totalPass = 0;
    let totalFail = 0;
    let totalSkip = 0;
    let agentCount = 0;

    for (const [agentId, output] of agentOutputs.entries()) {
      // Verify deliverables exist
      if (!output.deliverables || output.deliverables.length === 0) {
        console.warn(`  ${agentId}: No deliverables to test`);
        continue;
      }

      // Validate deliverables exist on filesystem
      const missingFiles: string[] = [];
      for (const deliverable of output.deliverables) {
        const filePath = path.join(projectRoot, deliverable);
        try {
          await fs.access(filePath);
        } catch {
          missingFiles.push(deliverable);
        }
      }

      if (missingFiles.length > 0) {
        console.warn(`  ${agentId}: Missing deliverables: ${missingFiles.join(', ')}`);
        const testResult: TestResult = {
          pass: 0,
          fail: missingFiles.length,
          skip: 0,
        };
        this.recordTestResult(agentId, testResult);
        totalFail += missingFiles.length;
        agentCount++;
        continue;
      }

      // Execute test suite
      try {
        console.log(`  ${agentId}: Running tests on ${output.deliverables.length} deliverables...`);

        const testOutput = execSync(testCommand, {
          encoding: 'utf8',
          cwd: projectRoot,
          stdio: 'pipe',
        });

        // Parse test output (example for Jest format)
        const passMatch = testOutput.match(/(\d+) passing/);
        const failMatch = testOutput.match(/(\d+) failing/);
        const skipMatch = testOutput.match(/(\d+) pending/);

        const pass = passMatch && passMatch[1] ? parseInt(passMatch[1], 10) : 0;
        const fail = failMatch && failMatch[1] ? parseInt(failMatch[1], 10) : 0;
        const skip = skipMatch && skipMatch[1] ? parseInt(skipMatch[1], 10) : 0;

        const testResult: TestResult = { pass, fail, skip };
        this.recordTestResult(agentId, testResult);

        totalPass += pass;
        totalFail += fail;
        totalSkip += skip;
        agentCount++;

        console.log(`  ${agentId}: Tests completed (${pass} pass, ${fail} fail, ${skip} skip)`);
      } catch (error) {
        // Test execution failed
        const errorMsg = error instanceof Error ? error.message : String(error);
        console.error(`  ${agentId}: Test execution failed: ${errorMsg}`);

        const testResult: TestResult = {
          pass: 0,
          fail: output.deliverables.length,
          skip: 0,
        };
        this.recordTestResult(agentId, testResult);
        totalFail += output.deliverables.length;
        agentCount++;
      }
    }

    const total = totalPass + totalFail + totalSkip;
    const passRate = total === 0 ? 0 : totalPass / total;

    console.log(`Test execution complete: ${totalPass} pass, ${totalFail} fail, ${totalSkip} skip (${(passRate * 100).toFixed(2)}% pass rate)`);

    return {
      totalPass,
      totalFail,
      totalSkip,
      passRate,
      agentCount,
    };
  }

  /**
   * Build agent context for spawning
   */
  public buildAgentContext(
    agentId: string,
    loopType: 'loop3' | 'loop2',
    iteration: number,
    _feedback?: IterationFeedback
  ): AgentExecutionContext {
    return {
      agentId,
      agentType: 'unknown',
      loopType,
      iteration,
      taskId: this.config.taskId,
      timestamp: Date.now(),
    };
  }

  /**
   * Prepare feedback for next iteration
   */
  public prepareFeedback(feedback: IterationFeedback): IterationFeedback {
    return {
      ...feedback,
      timestamp: Date.now(),
    };
  }

  /**
   * Get phase history
   */
  public getPhaseHistory(): PhaseTransition[] {
    return [...this.phaseHistory];
  }

  /**
   * Get execution errors
   */
  public getErrors(): Map<string, Error> {
    return new Map(this.errors);
  }

  /**
   * Reset state for new iteration
   */
  public resetForIteration(): void {
    this.testResults.clear();
    this.consensusScores.clear();
    this.decision = null;
    this.errors.clear();
    this.state.completedAgents.clear();
    this.state.failedAgents.clear();
  }

  /**
   * Get orchestration summary
   */
  public getSummary(): {
    taskId: string;
    mode: ExecutionMode;
    iteration: number;
    totalAgentsCompleted: number;
    totalAgentsFailed: number;
    decision: ProductOwnerDecision;
    duration: number;
  } {
    return {
      taskId: this.config.taskId,
      mode: this.config.mode,
      iteration: this.state.iteration,
      totalAgentsCompleted: this.state.completedAgents.size,
      totalAgentsFailed: this.state.failedAgents.size,
      decision: this.decision,
      duration: Date.now() - this.state.startTime,
    };
  }

  /**
   * Execute the complete CFN Loop orchestration workflow
   * Runs iterations with Loop 3 → Loop 2 → Product Owner progression
   * Returns final decision (PROCEED/ITERATE/ABORT)
   */
  public async execute(): Promise<ProductOwnerDecision> {
    const maxIterations = this.config.maxIterations;

    // Main iteration loop
    for (let iteration = 1; iteration <= maxIterations; iteration++) {
      this.incrementIteration();

      console.log(`\n${'='.repeat(60)}`);
      console.log(`Iteration ${iteration}/${maxIterations}`);
      console.log(`${'='.repeat(60)}`);

      // ===== LOOP 3: IMPLEMENTERS =====
      console.log('\nPhase: Loop 3 (Implementers)');
      this.transitionPhase('loop3');

      const loop3AgentTypes = this.config.loop3Agents || ['backend-dev', 'coder'];
      const taskContext = this.buildTaskContext();

      // Spawn real CLI agents
      console.log(`Spawning ${loop3AgentTypes.length} Loop 3 agents via CLI...`);
      const loop3SpawnResult = await spawnLoop3Agents(
        this.config.taskId,
        this.state.iteration,
        taskContext
      );

      console.log(`Loop 3 spawn summary: ${loop3SpawnResult.successCount} successful, ${loop3SpawnResult.failureCount} failed`);

      // Wait for agents to complete via Redis coordination
      const completedAgentIds = await this.waitForAgentsToComplete(
        loop3SpawnResult.results,
        300 // 5 minute timeout per agent
      );

      if (completedAgentIds.length === 0) {
        console.error('No agents completed successfully. Aborting iteration.');
        this.recordDecision('ABORT');
        break;
      }

      // Collect agent outputs (test results, confidence scores, deliverables)
      const agentOutputs = await this.collectAgentOutputs(completedAgentIds);

      // Execute tests against actual agent deliverables
      const aggregated = await this.executeTestsOnDeliverables(agentOutputs);
      console.log(
        `Loop 3 Results: ${aggregated.totalPass} pass, ${aggregated.totalFail} fail (${aggregated.agentCount} agents, ${(aggregated.passRate * 100).toFixed(2)}% pass rate)`
      );

      const gateResult = this.checkGate(aggregated.passRate);
      console.log(`Gate Check: ${gateResult.passed ? 'PASSED' : 'FAILED'} (threshold: ${(gateResult.threshold * 100).toFixed(2)}%)`);

      if (!gateResult.passed) {
        console.log(`Gate failed. Iterating...`);

        // Prepare feedback for next iteration
        this.prepareFeedback({
          gatePassRate: aggregated.passRate,
          previousFailures: Array.from(this.state.failedAgents),
          reasons: [`Gate check failed: ${(gateResult.gap * 100).toFixed(2)}% below threshold`],
        });

        console.log(`Feedback prepared for iteration ${iteration + 1}`);

        // Reset state for next iteration
        this.resetForIteration();

        if (!this.canContinueIterating()) {
          console.log(`Max iterations (${maxIterations}) reached. ABORTING.`);
          this.recordDecision('ABORT');
          break;
        }

        continue; // Go to next iteration
      }

      // ===== LOOP 2: VALIDATORS =====
      console.log('\nPhase: Loop 2 (Validators)');
      this.transitionPhase('loop2');

      const loop2AgentTypes = this.config.loop2Agents || ['code-reviewer', 'tester', 'security-specialist'];

      // Spawn real CLI validators
      console.log(`Spawning ${loop2AgentTypes.length} Loop 2 validators via CLI...`);
      const loop2SpawnResult = await spawnLoop2Agents(
        this.config.taskId,
        this.state.iteration,
        taskContext
      );

      console.log(`Loop 2 spawn summary: ${loop2SpawnResult.successCount} successful, ${loop2SpawnResult.failureCount} failed`);

      // Wait for validators to complete via Redis coordination
      const completedValidatorIds = await this.waitForAgentsToComplete(
        loop2SpawnResult.results,
        300 // 5 minute timeout per validator
      );

      if (completedValidatorIds.length === 0) {
        console.error('No validators completed successfully. Iterating...');
        this.prepareFeedback({
          reasons: ['No Loop 2 validators completed'],
        });
        this.resetForIteration();

        if (!this.canContinueIterating()) {
          console.log(`Max iterations (${maxIterations}) reached. ABORTING.`);
          this.recordDecision('ABORT');
          break;
        }

        continue;
      }

      // Collect validator outputs (consensus scores)
      const validatorOutputs = await this.collectAgentOutputs(completedValidatorIds);

      // Record consensus scores from validators
      for (const [validatorId, output] of validatorOutputs.entries()) {
        if (output.confidence !== undefined) {
          this.recordConsensusScore(validatorId, output.confidence);
        }
      }

      console.log(`Loop 2 validators completed: ${completedValidatorIds.length}/${loop2SpawnResult.totalSpawned}`);

      // Validate consensus
      const consensusValidation = this.validateConsensus();
      console.log(
        `Loop 2 Consensus: ${(consensusValidation.average * 100).toFixed(2)}% (threshold: ${(consensusValidation.threshold * 100).toFixed(2)}%)`
      );

      if (!consensusValidation.passed) {
        console.log(`Consensus failed. Iterating...`);

        // Prepare feedback for next iteration
        this.prepareFeedback({
          consensusAverage: consensusValidation.average,
          reasons: [`Consensus below threshold: ${(consensusValidation.gap * 100).toFixed(2)}%`],
        });

        console.log(`Feedback prepared for iteration ${iteration + 1}`);

        // Reset state for next iteration
        this.resetForIteration();

        if (!this.canContinueIterating()) {
          console.log(`Max iterations (${maxIterations}) reached. ABORTING.`);
          this.recordDecision('ABORT');
          break;
        }

        continue; // Go to next iteration
      }

      // ===== PRODUCT OWNER DECISION =====
      console.log('\nPhase: Product Owner Decision');
      this.transitionPhase('product-owner');

      const ownerAgent = this.config.productOwner || 'product-owner-agent';
      console.log(`Consulting Product Owner (${ownerAgent})`);

      // In production, spawn actual product owner agent
      // For now, default to PROCEED if gate and consensus passed
      let decision: ProductOwnerDecision = 'PROCEED';

      this.recordDecision(decision);
      console.log(`Product Owner Decision: ${decision}`);

      // ===== DECISION HANDLING =====
      if (decision === 'PROCEED') {
        console.log(`\n${'='.repeat(60)}`);
        console.log('SUCCESS: Product Owner approved. Orchestration complete.');
        console.log(`${'='.repeat(60)}`);
        break;
      } else if (decision === 'ITERATE') {
        console.log(`Iteration ${iteration} requested review. Iterating...`);

        // Reset state for next iteration
        this.resetForIteration();

        if (!this.canContinueIterating()) {
          console.log(`Max iterations (${maxIterations}) reached. ABORTING.`);
          this.recordDecision('ABORT');
          break;
        }

        continue; // Go to next iteration
      } else if (decision === 'ABORT') {
        console.log(`\n${'='.repeat(60)}`);
        console.log('FAILURE: Product Owner rejected. Aborting orchestration.');
        console.log(`${'='.repeat(60)}`);
        break;
      }
    }

    // Final status
    const finalDecision = this.getDecision() || 'ABORT';
    const summary = this.getSummary();

    console.log(`\nFinal Summary:`);
    console.log(`  Task ID: ${summary.taskId}`);
    console.log(`  Mode: ${summary.mode}`);
    console.log(`  Iterations: ${summary.iteration}/${this.config.maxIterations}`);
    console.log(`  Completed Agents: ${this.state.completedAgents.size}`);
    console.log(`  Failed Agents: ${this.state.failedAgents.size}`);
    console.log(`  Decision: ${finalDecision}`);
    console.log(`  Duration: ${(summary.duration / 1000).toFixed(2)}s`);

    return finalDecision;
  }
}

/**
 * CLI entry point for orchestrator
 */
if (require.main === module) {
  const args = process.argv.slice(2);

  // Parse command line arguments
  let taskId = '';
  let mode: ExecutionMode = 'standard';
  let maxIterations = 10;
  let loop3Agents: string[] = [];
  let loop2Agents: string[] = [];
  let productOwner = '';
  let successCriteriaEnabled = false;

  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (!arg) continue;

    switch (arg) {
      case '--task-id': {
        const nextArg = args[++i];
        if (nextArg) taskId = nextArg;
        break;
      }
      case '--mode': {
        const nextArg = args[++i];
        if (nextArg) mode = nextArg as ExecutionMode;
        break;
      }
      case '--max-iterations': {
        const nextArg = args[++i];
        if (nextArg) maxIterations = parseInt(nextArg, 10);
        break;
      }
      case '--loop3-agents': {
        const nextArg = args[++i];
        if (nextArg) {
          loop3Agents = nextArg.split(',').map((a) => a.trim()).filter((a) => a.length > 0);
        }
        break;
      }
      case '--loop2-agents': {
        const nextArg = args[++i];
        if (nextArg) {
          loop2Agents = nextArg.split(',').map((a) => a.trim()).filter((a) => a.length > 0);
        }
        break;
      }
      case '--product-owner': {
        const nextArg = args[++i];
        if (nextArg) productOwner = nextArg;
        break;
      }
      case '--success-criteria': {
        const nextArg = args[++i];
        if (nextArg) {
          successCriteriaEnabled = nextArg.toLowerCase() === 'enabled' || nextArg === 'true';
        }
        break;
      }
    }
  }

  if (!taskId) {
    console.error('Error: --task-id is required');
    process.exit(1);
  }

  const config: OrchestrationConfig = {
    taskId,
    mode,
    maxIterations,
  };

  // Add optional parameters only if they have values
  if (loop3Agents.length > 0) {
    config.loop3Agents = loop3Agents;
  }
  if (loop2Agents.length > 0) {
    config.loop2Agents = loop2Agents;
  }
  if (productOwner) {
    config.productOwner = productOwner;
  }
  if (successCriteriaEnabled) {
    config.successCriteriaEnabled = successCriteriaEnabled;
  }

  const orchestrator = new Orchestrator(config);
  console.log(JSON.stringify(orchestrator.getState(), null, 2));
  process.exit(0);
}

export default Orchestrator;
