/**
 * Thread Coordinator — Dependency analysis and wait strategies
 *
 * Parses tasks.json dependency graphs and identifies:
 * - Tasks that can run in parallel (no dependencies on each other)
 * - Tasks that must run sequentially
 * - Circular dependencies
 *
 * Returns an execution plan: phases of parallel groups.
 *
 * Example output:
 *   Phase 1 (parallel): [T001, T002, T005]  ← no dependencies
 *   Phase 2 (parallel): [T003, T004]         ← depend only on phase 1
 *   Phase 3 (sequential): [T006]             ← depends on both T003, T004
 */

import { readFileSync, existsSync } from 'fs';
import { join } from 'path';

// ============================================================================
// Dependency Analysis
// ============================================================================

/**
 * Build a dependency graph from an in-memory task array
 * @param {Array} tasks - Array of { id, dependencies: [] } task objects
 * @returns {Object} { nodes: Map<id, task>, edges: Map<id, Set<depId>>, tasks }
 */
export function buildGraph(tasks) {
  const nodes = new Map();
  const edges = new Map();

  for (const task of tasks) {
    nodes.set(task.id, task);
    edges.set(task.id, new Set(task.dependencies || []));
  }

  return { nodes, edges, tasks };
}

/**
 * Parse tasks.json and build a dependency graph
 * @param {string} feature - Feature name
 * @returns {Object} { nodes: Map<id, task>, edges: Map<id, Set<depId>> }
 */
export function analyzeDependencies(feature) {
  const tasksPath = join(process.cwd(), `.morph/project/outputs/${feature}/tasks.json`);

  if (!existsSync(tasksPath)) {
    throw new Error(`tasks.json not found for feature: ${feature}`);
  }

  let tasksData;
  try {
    tasksData = JSON.parse(readFileSync(tasksPath, 'utf8'));
  } catch (err) {
    throw new Error(`Failed to parse tasks.json: ${err.message}`);
  }

  return buildGraph(tasksData.tasks || []);
}

/**
 * Identify tasks that can run in parallel (no dependencies between them)
 * @param {Object} graph - { nodes, edges } from buildGraph/analyzeDependencies
 * @returns {Array} Groups of task IDs that can run in parallel
 */
export function identifyParallelizable(graph) {
  const { nodes, edges } = graph;
  const plan = planExecution(nodes, edges);
  return plan.filter(group => group.length > 1);
}

/**
 * Identify tasks that must run sequentially
 * @param {Object} graph - { nodes, edges } from buildGraph/analyzeDependencies
 * @returns {Array} Task IDs that must run sequentially
 */
export function identifySequential(graph) {
  const { nodes, edges } = graph;
  const plan = planExecution(nodes, edges);
  return plan.filter(group => group.length === 1).flat();
}

/**
 * Detect circular dependencies in the task graph
 * @param {Map} nodes - Task nodes
 * @param {Map} edges - Dependency edges
 * @returns {Array} Array of circular dependency chains, or [] if none
 */
export function detectCircular(nodes, edges) {
  const cycles = [];
  const visited = new Set();
  const recursionStack = new Set();

  function dfs(nodeId, path) {
    visited.add(nodeId);
    recursionStack.add(nodeId);

    for (const dep of (edges.get(nodeId) || [])) {
      if (!nodes.has(dep)) continue;

      if (!visited.has(dep)) {
        dfs(dep, [...path, nodeId]);
      } else if (recursionStack.has(dep)) {
        // Found a cycle
        const cycleStart = path.indexOf(dep);
        const cycle = cycleStart >= 0
          ? [...path.slice(cycleStart), nodeId, dep]
          : [...path, nodeId, dep];
        cycles.push(cycle);
      }
    }

    recursionStack.delete(nodeId);
  }

  for (const nodeId of nodes.keys()) {
    if (!visited.has(nodeId)) {
      dfs(nodeId, []);
    }
  }

  return cycles;
}

/**
 * Plan execution: return phases of parallel task groups
 * @param {Map} nodes - Task nodes
 * @param {Map} edges - Dependency edges
 * @returns {Array} Array of phases, each phase is an array of task IDs
 *
 * Algorithm: Kahn's topological sort, grouping by "wave"
 */
export function planExecution(nodes, edges) {
  // Check for cycles first
  const cycles = detectCircular(nodes, edges);
  if (cycles.length > 0) {
    throw new Error(
      `Circular dependencies detected: ${cycles.map(c => c.join(' → ')).join('; ')}`
    );
  }

  // Build reverse edges (dependents)
  const inDegree = new Map();
  const dependents = new Map(); // id → Set of task IDs that depend on it

  for (const [id] of nodes) {
    inDegree.set(id, 0);
    dependents.set(id, new Set());
  }

  for (const [id, deps] of edges) {
    for (const dep of deps) {
      if (!inDegree.has(dep)) continue;
      inDegree.set(id, (inDegree.get(id) || 0) + 1);
      dependents.get(dep).add(id);
    }
  }

  // Correct in-degree: count only deps that exist in nodes
  for (const [id, deps] of edges) {
    let count = 0;
    for (const dep of deps) {
      if (nodes.has(dep)) count++;
    }
    inDegree.set(id, count);
  }

  const phases = [];
  const remaining = new Set(nodes.keys());

  while (remaining.size > 0) {
    // Find all nodes with no unsatisfied dependencies
    const ready = [...remaining].filter(id => {
      const deps = edges.get(id) || new Set();
      return [...deps].every(dep => !remaining.has(dep));
    });

    if (ready.length === 0 && remaining.size > 0) {
      // Shouldn't happen if no cycles, but safeguard
      throw new Error(`Execution plan stalled with ${remaining.size} remaining tasks: ${[...remaining].join(', ')}`);
    }

    phases.push(ready);
    for (const id of ready) {
      remaining.delete(id);
    }
  }

  return phases;
}

// ============================================================================
// Full Analysis
// ============================================================================

/**
 * Get full parallel execution plan for a feature
 * @param {string} feature - Feature name
 * @returns {Object} Execution plan with phases, parallelizable groups, and stats
 */
export function getExecutionPlan(feature) {
  const { nodes, edges, tasks } = analyzeDependencies(feature);

  const cycles = detectCircular(nodes, edges);
  if (cycles.length > 0) {
    return {
      feature,
      valid: false,
      cycles,
      error: `Circular dependencies found: ${cycles.map(c => c.join(' → ')).join('; ')}`
    };
  }

  const phases = planExecution(nodes, edges);
  const parallelPhases = phases.filter(p => p.length > 1);
  const totalParallelizable = parallelPhases.reduce((sum, p) => sum + p.length, 0);

  return {
    feature,
    valid: true,
    totalTasks: tasks.length,
    phases: phases.map((group, i) => ({
      phase: i + 1,
      tasks: group,
      canRunParallel: group.length > 1,
      count: group.length
    })),
    stats: {
      totalPhases: phases.length,
      parallelPhases: parallelPhases.length,
      parallelizableTasks: totalParallelizable,
      sequentialTasks: tasks.length - totalParallelizable,
      parallelizationRatio: Math.round(totalParallelizable / Math.max(tasks.length, 1) * 100)
    }
  };
}
