/**
 * Fusion Executor — F-Thread best-of-N aggregation
 *
 * Runs N parallel agents on the same prompt, collects results,
 * then aggregates using one of three strategies:
 *   - best-of-n: score each result, pick highest (automated)
 *   - consensus: merge common elements from all results
 *   - manual-select: present all N to user for selection
 *
 * Returns: { winner, allResults, scores }
 */

import { createThread, startThread, completeThread, failThread, THREAD_TYPES } from '../threads/thread-manager.js';
import { recordEvent, generateAsciiChart } from '../analytics/analytics-engine.js';

// ============================================================================
// Scoring
// ============================================================================

/**
 * Score a fusion result based on completeness, compliance, and quality
 * @param {Object} result - Agent result object
 * @param {Object} [rubric] - Scoring rubric
 * @returns {number} Score 0-100
 */
export function scoreResult(result, rubric = {}) {
  let score = 0;
  const weights = {
    completeness: rubric.completenessWeight || 40,
    checkpointCompliance: rubric.complianceWeight || 30,
    codeQuality: rubric.qualityWeight || 30
  };

  // Completeness: does it have all required deliverables?
  if (result.deliverables) {
    const required = rubric.requiredDeliverables?.length || 1;
    const provided = Object.keys(result.deliverables).length;
    score += weights.completeness * Math.min(provided / required, 1.0);
  } else if (result.content) {
    // Estimate completeness from content length
    const targetLength = rubric.targetLength || 1000;
    score += weights.completeness * Math.min(result.content.length / targetLength, 1.0);
  } else {
    score += weights.completeness * 0.5; // Partial credit
  }

  // Checkpoint compliance: did it follow patterns?
  if (result.checkpointsPassed !== undefined) {
    const total = (result.checkpointsPassed || 0) + (result.checkpointsFailed || 0);
    const passRate = total > 0 ? result.checkpointsPassed / total : 0.8; // Assume 80% if unknown
    score += weights.checkpointCompliance * passRate;
  } else {
    score += weights.checkpointCompliance * 0.8; // Default: assume compliant
  }

  // Code quality: based on error count and warnings
  const errors = result.errors || 0;
  const warnings = result.warnings || 0;
  const qualityScore = Math.max(0, 1.0 - (errors * 0.2) - (warnings * 0.05));
  score += weights.codeQuality * qualityScore;

  return Math.round(score);
}

// ============================================================================
// Aggregation Strategies
// ============================================================================

/**
 * Best-of-N aggregation: score each result, return highest scorer
 * @param {Array} results - Array of agent results
 * @param {Object} rubric - Scoring rubric
 * @returns {Object} { winner, scores }
 */
export function aggregateBestOfN(results, rubric = {}) {
  const scored = results.map((result, idx) => ({
    index: idx,
    agentId: result.agentId || `agent-${idx}`,
    result,
    score: scoreResult(result, rubric)
  }));

  scored.sort((a, b) => b.score - a.score);
  const winner = scored[0];

  return {
    winner: winner.result,
    winnerId: winner.agentId,
    winnerScore: winner.score,
    scores: scored.map(s => ({
      agentId: s.agentId,
      score: s.score,
      rank: scored.indexOf(s) + 1
    }))
  };
}

/**
 * Consensus aggregation: merge common elements from all results
 * @param {Array} results - Array of agent results
 * @returns {Object} { merged, commonElements }
 */
export function aggregateConsensus(results) {
  if (results.length === 0) return { merged: {}, commonElements: [] };
  if (results.length === 1) return { merged: results[0], commonElements: [] };

  // Find common keys across all results
  const allKeys = results.map(r => Object.keys(r));
  const commonKeys = allKeys[0].filter(key =>
    allKeys.every(keys => keys.includes(key))
  );

  const merged = {};
  const commonElements = [];

  for (const key of commonKeys) {
    const values = results.map(r => r[key]);

    // For strings: use longest (most complete) version
    if (values.every(v => typeof v === 'string')) {
      merged[key] = values.reduce((longest, v) =>
        v.length > longest.length ? v : longest, ''
      );
      commonElements.push(key);
    }
    // For arrays: union of all arrays
    else if (values.every(v => Array.isArray(v))) {
      merged[key] = [...new Set(values.flat())];
      commonElements.push(key);
    }
    // For objects: merge recursively (simple one level)
    else if (values.every(v => v && typeof v === 'object')) {
      merged[key] = Object.assign({}, ...values);
      commonElements.push(key);
    }
    // For primitives: use majority value
    else {
      const valueCounts = values.reduce((acc, v) => {
        const key2 = String(v);
        acc[key2] = (acc[key2] || 0) + 1;
        return acc;
      }, {});
      const [majority] = Object.entries(valueCounts).sort(([, a], [, b]) => b - a);
      merged[key] = majority ? JSON.parse(majority[0]) : values[0];
      commonElements.push(key);
    }
  }

  return { merged, commonElements };
}

// ============================================================================
// Main Fusion Runner
// ============================================================================

/**
 * Run fusion execution: spawn N agents in parallel, collect and aggregate results
 * @param {Object} opts
 * @param {string} opts.feature - Feature name
 * @param {string} opts.prompt - Prompt/mission for all agents
 * @param {number} [opts.count=3] - Number of agents to run
 * @param {string} [opts.strategy='best-of-n'] - Aggregation strategy
 * @param {string[]} [opts.agents] - Agent IDs to use (will use thread types if not specified)
 * @param {Object} [opts.rubric] - Scoring rubric for best-of-n
 * @returns {Promise<Object>} { winner, allResults, scores, strategy, threads }
 */
export async function runFusion(prompt, {
  feature,
  count = 3,
  strategy = 'best-of-n',
  agents = null,
  rubric = {}
} = {}) {
  const { randomUUID } = await import('crypto');
  const sessionId = randomUUID();
  const agentList = agents || Array.from({ length: count }, (_, i) => `fusion-agent-${i + 1}`);
  const threads = [];
  const results = [];

  recordEvent({
    type: 'fusion_started',
    feature,
    data: { count, strategy, agents: agentList }
  });

  // Create and start all fusion threads simultaneously
  for (let i = 0; i < agentList.length; i++) {
    const thread = createThread({
      feature,
      type: THREAD_TYPES.FUSION,
      agent: agentList[i],
      mission: prompt,
      meta: { fusionIndex: i, strategy }
    });
    startThread(thread.id);
    threads.push(thread);
  }

  console.log(`  [fusion] ${threads.length} agents spawned. Collecting results...`);

  // In a real system, results would come from actual sub-agent execution.
  // Here we return thread configs for the calling code to use with Task tool.
  // The aggregation is designed to be called after results are collected.

  return {
    id: sessionId,
    threads,
    strategy,
    feature,
    prompt,
    agentConfigs: agentList.map((agentId, i) => ({
      agentId,
      threadId: threads[i].id,
      mission: prompt
    })),
    aggregate: (collectedResults) => aggregateResults(collectedResults, strategy, rubric)
  };
}

/**
 * Aggregate collected results from N agents
 * @param {Array} results - Array of result objects from agents
 * @param {string} strategy - Aggregation strategy
 * @param {Object} [rubric] - Scoring rubric for best-of-n
 * @returns {Object} Aggregated result
 */
export function aggregateResults(results, strategy = 'best-of-n', rubric = {}) {
  if (!results || results.length === 0) {
    throw new Error('No results to aggregate');
  }

  let aggregated;

  switch (strategy) {
    case 'best-of-n':
      aggregated = aggregateBestOfN(results, rubric);
      break;

    case 'consensus':
      const { merged, commonElements } = aggregateConsensus(results);
      aggregated = {
        winner: merged,
        winnerId: 'consensus',
        scores: results.map((_, i) => ({
          agentId: `agent-${i}`,
          score: 0,
          rank: i + 1
        })),
        commonElements
      };
      break;

    case 'manual-select':
      aggregated = {
        winner: null,
        winnerId: null,
        allResults: results,
        requiresUserSelection: true,
        scores: results.map((r, i) => ({
          agentId: r.agentId || `agent-${i}`,
          score: scoreResult(r, rubric),
          rank: i + 1
        }))
      };
      break;

    default:
      throw new Error(`Unknown aggregation strategy: ${strategy}`);
  }

  return {
    ...aggregated,
    strategy,
    totalAgents: results.length,
    allResults: results
  };
}

/**
 * Display fusion results summary
 * @param {Object} fusionResult - Result from aggregateResults
 */
export function displayFusionSummary(fusionResult) {
  const { strategy, totalAgents, scores, winner, requiresUserSelection } = fusionResult;

  console.log(`\n  Fusion Results — ${strategy} (${totalAgents} agents)\n`);
  console.log('  ' + '─'.repeat(50));

  if (scores) {
    const scoreData = {};
    scores.forEach(s => { scoreData[s.agentId] = s.score; });
    console.log(generateAsciiChart(scoreData, { title: 'Agent Scores' }));
  }

  if (requiresUserSelection) {
    console.log('\n  Manual selection required — review results above');
  } else if (winner) {
    console.log(`\n  Winner: ${fusionResult.winnerId || 'consensus'}`);
    if (fusionResult.winnerScore !== undefined) {
      console.log(`  Score: ${fusionResult.winnerScore}/100`);
    }
  }
  console.log('');
}
