/**
 * Parallel Executor — P-Thread concurrent agent spawning
 *
 * Manages concurrent thread execution for parallel feature development.
 * Configurable max-concurrent (1-5, default 3).
 *
 * Thread types supported:
 *   P-Thread — Parallel execution (multiple agents simultaneously)
 *   Managed via thread-manager.js + analytics-engine.js
 */

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

const DEFAULT_MAX_CONCURRENT = 3;
const MAX_CONCURRENT_LIMIT = 5;

// ============================================================================
// Parallel Execution
// ============================================================================

/**
 * Spawn N threads in parallel for the same feature
 * @param {Object} opts
 * @param {string} opts.feature - Feature name
 * @param {Array} opts.threadConfigs - Array of { agent, mission, meta } configs
 * @param {number} [opts.maxConcurrent=3] - Maximum concurrent threads
 * @returns {Promise<Array>} Array of created thread objects
 */
export async function spawnParallel({ feature, threadConfigs, maxConcurrent = DEFAULT_MAX_CONCURRENT }) {
  const limit = Math.min(maxConcurrent, MAX_CONCURRENT_LIMIT);

  if (!threadConfigs || threadConfigs.length === 0) {
    throw new Error('threadConfigs is required and must be non-empty');
  }

  const threads = [];
  const batches = chunkArray(threadConfigs, limit);

  recordEvent({
    type: 'parallel_spawn_started',
    feature,
    data: {
      totalThreads: threadConfigs.length,
      maxConcurrent: limit,
      batches: batches.length
    }
  });

  for (const batch of batches) {
    const batchThreads = batch.map(config =>
      createThread({
        feature,
        type: THREAD_TYPES.PARALLEL,
        agent: config.agent,
        mission: config.mission,
        meta: { ...config.meta, maxConcurrent: limit }
      })
    );

    threads.push(...batchThreads);

    // Mark all batch threads as running simultaneously
    for (const t of batchThreads) {
      startThread(t.id);
    }

    recordEvent({
      type: 'parallel_batch_started',
      feature,
      data: {
        batchSize: batch.length,
        threadIds: batchThreads.map(t => t.id),
        concurrency: batch.length
      }
    });
  }

  return threads;
}

/**
 * Coordinate parallel thread lifecycle — wait for all to complete
 * @param {string} feature - Feature name
 * @param {string[]} threadIds - Thread IDs to track
 * @param {Object} [opts]
 * @param {number} [opts.pollIntervalMs=5000] - Poll interval
 * @param {number} [opts.timeoutMs=3600000] - Max wait (1 hour default)
 * @returns {Promise<Object>} { completed, failed, duration }
 */
export async function coordinateParallel(feature, threadIds, {
  pollIntervalMs = 5000,
  timeoutMs = 3600000
} = {}) {
  const start = Date.now();
  const completed = [];
  const failed = [];
  const remaining = new Set(threadIds);

  while (remaining.size > 0 && Date.now() - start < timeoutMs) {
    for (const id of [...remaining]) {
      const threads = listThreads({ feature });
      const thread = threads.find(t => t.id === id);

      if (!thread) {
        remaining.delete(id);
        continue;
      }

      if (thread.status === THREAD_STATUS.COMPLETED) {
        completed.push(id);
        remaining.delete(id);
      } else if (thread.status === THREAD_STATUS.FAILED || thread.status === THREAD_STATUS.KILLED) {
        failed.push(id);
        remaining.delete(id);
      }
    }

    if (remaining.size > 0) {
      await sleep(pollIntervalMs);
    }
  }

  const duration = Math.round((Date.now() - start) / 1000);

  if (remaining.size > 0) {
    recordEvent({
      type: 'parallel_timeout',
      feature,
      data: { timedOut: [...remaining], duration }
    });
  }

  recordEvent({
    type: 'parallel_coordination_complete',
    feature,
    data: { completed: completed.length, failed: failed.length, duration }
  });

  return { completed, failed, timedOut: [...remaining], duration };
}

/**
 * Wait until all specified threads complete
 * @param {string} feature - Feature name
 * @param {string[]} threadIds - Thread IDs to wait for
 * @param {number} [timeoutMs=3600000] - Max wait time
 * @returns {Promise<Object>} Coordination result
 */
export async function waitAll(feature, threadIds, timeoutMs = 3600000) {
  return coordinateParallel(feature, threadIds, { timeoutMs });
}

/**
 * Wait until any thread completes, then return it
 * @param {string} feature - Feature name
 * @param {string[]} threadIds - Thread IDs to watch
 * @param {number} [timeoutMs=3600000] - Max wait time
 * @returns {Promise<Object|null>} First completed/failed thread or null on timeout
 */
export async function waitAny(feature, threadIds, timeoutMs = 3600000) {
  const start = Date.now();
  const idSet = new Set(threadIds);

  while (Date.now() - start < timeoutMs) {
    const threads = listThreads({ feature });
    for (const thread of threads) {
      if (idSet.has(thread.id)) {
        if (thread.status === THREAD_STATUS.COMPLETED || thread.status === THREAD_STATUS.FAILED) {
          return thread;
        }
      }
    }
    await sleep(3000);
  }

  return null;
}

// ============================================================================
// Efficiency Metrics
// ============================================================================

/**
 * Plan parallel execution batches without spawning threads
 * @param {Object} opts
 * @param {Array} opts.threadConfigs - Array of { agent, mission } configs
 * @param {number} [opts.maxConcurrent=3] - Max concurrent threads
 * @returns {Object} { batches, totalThreads, maxConcurrent, batchCount }
 */
export function planParallelBatches({ threadConfigs, maxConcurrent = DEFAULT_MAX_CONCURRENT }) {
  const limit = Math.min(maxConcurrent, MAX_CONCURRENT_LIMIT);
  const batches = chunkArray(threadConfigs, limit);
  return {
    batches,
    totalThreads: threadConfigs.length,
    maxConcurrent: limit,
    batchCount: batches.length
  };
}

/**
 * Calculate parallel execution efficiency
 * @param {string|Object} featureOrOpts - Feature name, or { totalTasks, waves, maxConcurrent }
 * @returns {Object} Efficiency metrics (efficiency is 0-1 when given opts, 0-100 for real threads)
 */
export function getParallelEfficiency(featureOrOpts) {
  // Accept stats object for testing/preview
  if (featureOrOpts && typeof featureOrOpts === 'object' && 'totalTasks' in featureOrOpts) {
    const { totalTasks, waves, maxConcurrent = DEFAULT_MAX_CONCURRENT } = featureOrOpts;
    const serialSteps = Math.ceil(totalTasks / Math.max(maxConcurrent, 1));
    const efficiency = waves > 0 ? Math.min(1, serialSteps / waves) : 0;
    return { totalTasks, waves, maxConcurrent, efficiency: Math.round(efficiency * 100) / 100 };
  }

  const feature = featureOrOpts;
  const threads = listThreads({ feature, type: THREAD_TYPES.PARALLEL });

  if (threads.length === 0) {
    return {
      feature,
      parallelThreads: 0,
      avgParallel: 0,
      maxParallel: 0,
      efficiency: 0
    };
  }

  // Group by time windows to estimate concurrent execution
  const completed = threads.filter(t => t.completedAt && t.startedAt);
  let maxParallel = 0;

  if (completed.length > 1) {
    // Find the time when most threads were running simultaneously
    for (const t1 of completed) {
      const concurrent = completed.filter(t2 =>
        t2.startedAt <= t1.completedAt && t2.completedAt >= t1.startedAt
      ).length;
      if (concurrent > maxParallel) maxParallel = concurrent;
    }
  } else {
    maxParallel = threads.length;
  }

  const avgParallel = threads.length / Math.max(1, Math.ceil(threads.length / maxParallel));

  return {
    feature,
    parallelThreads: threads.length,
    avgParallel: Math.round(avgParallel * 10) / 10,
    maxParallel,
    efficiency: Math.round(avgParallel / Math.max(maxParallel, 1) * 100)
  };
}

// ============================================================================
// Helpers
// ============================================================================

function chunkArray(arr, size) {
  const chunks = [];
  for (let i = 0; i < arr.length; i += size) {
    chunks.push(arr.slice(i, i + size));
  }
  return chunks;
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
