/**
 * graph.ts — Build the BA order plan from user-validated module dependencies.
 *
 * 1. Build a directed dependency graph (module A → module B means A depends on B).
 * 2. Identify external modules (referenced but not in the selected apps).
 * 3. Detect cycles via Tarjan's SCC algorithm (shared lib).
 * 4. Topological sort into waves via Kahn's algorithm with level tracking.
 * 5. External deps form Wave 0; internal modules fill Wave 1+.
 */
import { tarjanSCC, collapseCycles } from '../../../../lib/graph.js';
import type { ModuleDep, ModuleKey, BaOrderPlan, BaWave, BaWaveModule } from './types.js';
import { moduleKey } from './types.js';

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

/**
 * Build the BA order plan from the validated module dependency list.
 */
export function buildBaOrderPlan(
  modules: ModuleDep[],
  selectedApps: string[],
): BaOrderPlan {
  const warnings: string[] = [];

  // Index all in-scope modules by key
  const moduleMap = new Map<ModuleKey, ModuleDep>();
  for (const mod of modules) {
    moduleMap.set(mod.key, mod);
  }

  // --- Build adjacency list and collect external refs ---
  const adjacency = new Map<ModuleKey, Set<ModuleKey>>();
  const externalRefs = new Map<ModuleKey, Set<ModuleKey>>(); // external → set of modules that need it
  let totalDeps = 0;

  for (const mod of modules) {
    if (!adjacency.has(mod.key)) adjacency.set(mod.key, new Set());

    for (const depKey of mod.dependsOn) {
      totalDeps++;

      if (!moduleMap.has(depKey)) {
        // External dependency — not in the selected apps
        if (!externalRefs.has(depKey)) externalRefs.set(depKey, new Set());
        externalRefs.get(depKey)!.add(mod.key);
      } else {
        // Internal dependency
        adjacency.get(mod.key)!.add(depKey);
        if (!adjacency.has(depKey)) adjacency.set(depKey, new Set());
      }
    }
  }

  // --- Detect cycles via Tarjan SCC ---
  const internalKeys = [...moduleMap.keys()];
  const cycles = tarjanSCC(internalKeys, adjacency as Map<string, Set<string>>) as ModuleKey[][];

  if (cycles.length > 0) {
    for (const cycle of cycles) {
      warnings.push(
        `Dépendance cyclique détectée : ${cycle.join(' ↔ ')}. Ces modules seront dans la même vague.`,
      );
    }
  }

  // --- Collapse cycles into single virtual nodes for Kahn's ---
  const { collapsedAdj, nodeToGroup, groupMembers } = collapseCycles(
    internalKeys,
    adjacency as Map<string, Set<string>>,
    cycles,
  );

  // --- Kahn's algorithm with level tracking ---
  const waves = kahnsWithLevels(
    collapsedAdj,
    nodeToGroup,
    groupMembers,
    moduleMap,
    externalRefs,
  );

  return {
    generatedAt: new Date().toISOString().split('T')[0],
    apps: selectedApps,
    totalModules: modules.length,
    totalDeps,
    waves,
    cycles,
    warnings,
    source: 'context',
  };
}

// ---------------------------------------------------------------------------
// Kahn's algorithm with level tracking → waves
// ---------------------------------------------------------------------------

function kahnsWithLevels(
  collapsedAdj: Map<string, Set<string>>,
  nodeToGroup: Map<string, string>,
  groupMembers: Map<string, string[]>,
  moduleMap: Map<ModuleKey, ModuleDep>,
  externalRefs: Map<ModuleKey, Set<ModuleKey>>,
): BaWave[] {
  const waves: BaWave[] = [];

  // --- Wave 0: external prerequisites ---
  if (externalRefs.size > 0) {
    const externalModules: BaWaveModule[] = [];
    for (const [extKey, referencedBy] of externalRefs) {
      const [appCode, moduleCode] = extKey.split('/');
      externalModules.push({
        appCode,
        moduleCode,
        key: extKey as ModuleKey,
        dependsOn: [],
        isExternal: true,
        referencedBy: [...referencedBy],
        warnings: [],
      });
    }
    // Sort for deterministic output
    externalModules.sort((a, b) => a.key.localeCompare(b.key));
    waves.push({ level: 0, modules: externalModules });
  }

  // --- Compute in-degrees for the collapsed graph ---
  const inDegree = new Map<string, number>();
  for (const [groupId, deps] of collapsedAdj) {
    inDegree.set(groupId, deps.size);
  }

  // Build reverse index: dependency → list of groups that depend on it
  const reverseDeps = new Map<string, string[]>();
  for (const [group, deps] of collapsedAdj) {
    for (const dep of deps) {
      if (!reverseDeps.has(dep)) reverseDeps.set(dep, []);
      reverseDeps.get(dep)!.push(group);
    }
  }

  // --- BFS level by level ---
  let queue: string[] = [];
  for (const [groupId, deg] of inDegree) {
    if (deg === 0) queue.push(groupId);
  }

  let level = 0;
  while (queue.length > 0) {
    level++;
    const nextQueue: string[] = [];
    const waveModules: BaWaveModule[] = [];

    for (const groupId of queue) {
      const members = groupMembers.get(groupId) ?? [];
      for (const memberKey of members) {
        const mod = moduleMap.get(memberKey as ModuleKey);
        if (!mod) continue;

        waveModules.push({
          appCode: mod.appCode,
          moduleCode: mod.moduleCode,
          key: mod.key,
          dependsOn: [...mod.dependsOn],
          isExternal: false,
          warnings: [],
        });
      }

      // Decrement in-degree of dependents
      const dependents = reverseDeps.get(groupId) ?? [];
      for (const dependent of dependents) {
        const newDeg = (inDegree.get(dependent) ?? 1) - 1;
        inDegree.set(dependent, newDeg);
        if (newDeg === 0) {
          nextQueue.push(dependent);
        }
      }
    }

    // Sort for deterministic output
    waveModules.sort((a, b) => a.key.localeCompare(b.key));
    if (waveModules.length > 0) {
      waves.push({ level, modules: waveModules });
    }

    queue = nextQueue;
  }

  return waves;
}
