const fs = require('fs');
const path = require('path');
const GraphStore = require('./graph.js');

/**
 * Phase 7A: Subsystem Aggregator
 * Groups files into subsystems and computes cross-subsystem dependency matrix.
 */

/** Normalize absolute paths to relative (strips up to /src/ or configurable srcDir) */
function relPath(file, srcMarker) {
  if (!file) return '';
  srcMarker = srcMarker || '/src/';
  if (file.startsWith('/')) {
    const idx = file.indexOf(srcMarker);
    if (idx !== -1) return file.substring(idx + srcMarker.length);
    return file.split('/').slice(-2).join('/');
  }
  return file;
}

/** Get subsystem name from a relative file path */
function subsystemOf(relFile) {
  const parts = relFile.split('/');
  return parts.length > 1 ? parts[0] : 'root';
}

function buildSubsystems(graph, opts = {}) {
  const srcMarker = opts.srcDir || '/src/';
  const crossCuttingMinTraffic = opts.minTraffic || 10;
  const crossCuttingThreshold = opts.crossCuttingThreshold || 0.75;

  const subsystems = new Map();

  // Build function name → qualified node ID lookup
  const funcLookup = new Map();
  for (const [id, node] of graph.nodes) {
    if (node.type === 'Function' || node.type === 'Class') {
      if (!funcLookup.has(node.name)) funcLookup.set(node.name, []);
      funcLookup.get(node.name).push(id);
    }
  }

  // 1. Directory-based clustering (using relPath for DRY)
  for (const file of graph.fileIndex.keys()) {
    const rel = relPath(file, srcMarker);
    if (rel.startsWith('dep:')) continue;
    const subName = subsystemOf(rel);

    if (!subsystems.has(subName)) {
      subsystems.set(subName, {
        name: subName,
        kind: 'domain',
        files: new Set(),
        entities: { functions: 0, classes: 0, modules: 0 },
        publicExports: new Set(),
      });
    }

    const sub = subsystems.get(subName);
    sub.files.add(rel);

    const ids = graph.fileIndex.get(file);
    if (ids) {
      for (const id of ids) {
        const node = graph.nodes.get(id);
        if (!node) continue;
        if (node.type === 'Module') sub.entities.modules++;
        else if (node.type === 'Function') {
          sub.entities.functions++;
          if (node.visibility === 'public') sub.publicExports.add(node.name);
        }
        else if (node.type === 'Class') {
          sub.entities.classes++;
          if (node.visibility === 'public') sub.publicExports.add(node.name);
        }
      }
    }
  }

  // 2. Compute inter-subsystem edges
  const matrix = {};
  const inboundCross = {};
  const outboundCross = {};

  for (const name of subsystems.keys()) {
    inboundCross[name] = 0;
    outboundCross[name] = 0;
  }

  // Cache: nodeId → subsystem name
  const nodeSubCache = new Map();

  function resolveSubsystem(nodeId) {
    if (!nodeId) return null;
    if (nodeSubCache.has(nodeId)) return nodeSubCache.get(nodeId);
    const rel = relPath(nodeId.split(':')[0], srcMarker);
    const sub = subsystemOf(rel);
    const result = subsystems.has(sub) ? sub : null;
    nodeSubCache.set(nodeId, result);
    return result;
  }

  function resolveCallTarget(callerSub, targetName) {
    const candidates = funcLookup.get(targetName);
    if (!candidates || candidates.length === 0) return null;
    if (candidates.length === 1) return resolveSubsystem(candidates[0]);
    let sameSub = null;
    let diffSub = null;
    for (const cid of candidates) {
      const s = resolveSubsystem(cid);
      if (s === callerSub) sameSub = s;
      else if (s) diffSub = s;
    }
    return sameSub || diffSub;
  }

  for (const e of graph.edges) {
    if (e.type === 'CONTAINS') continue;

    let srcSub = resolveSubsystem(e.source);
    if (!srcSub) continue;

    let tgtSub = null;

    if (e.type === 'IMPORTS') {
      const dep = e.target.replace('dep:', '');
      tgtSub = subsystemOf(dep);
    } else if (e.type === 'CALLS') {
      if (e.target.includes('/')) tgtSub = resolveSubsystem(e.target);
      else tgtSub = resolveCallTarget(srcSub, e.target);
    } else if (e.type === 'IMPLEMENTS') {
      if (e.target.includes('/')) tgtSub = resolveSubsystem(e.target);
    }

    if (!tgtSub || !subsystems.has(tgtSub)) continue;
    if (srcSub === tgtSub) continue;

    const key = `${srcSub}→${tgtSub}`;
    if (!matrix[key]) matrix[key] = { calls: 0, imports: 0, via: new Set() };

    if (e.type === 'CALLS') matrix[key].calls++;
    else if (e.type === 'IMPORTS') matrix[key].imports++;

    if (matrix[key].via.size < 5) {
      matrix[key].via.add(`${e.source}→${e.target}`);
    }

    inboundCross[tgtSub]++;
    outboundCross[srcSub]++;
  }

  // 3. Cross-cutting detection (high fan-in ratio + minimum traffic)
  const crossCutting = [];
  for (const name of subsystems.keys()) {
    const totalCross = inboundCross[name] + outboundCross[name];
    if (totalCross >= crossCuttingMinTraffic) {
      const inboundRatio = inboundCross[name] / totalCross;
      if (inboundRatio > crossCuttingThreshold) {
        subsystems.get(name).kind = 'cross-cutting';
        crossCutting.push(name);
      }
    }
  }

  // Format output
  const result = {
    subsystems: [],
    crossCutting,
    dependencyMatrix: {}
  };

  for (const sub of subsystems.values()) {
    result.subsystems.push({
      name: sub.name,
      kind: sub.kind,
      files: Array.from(sub.files).sort(),
      entities: sub.entities,
      publicExports: Array.from(sub.publicExports).sort()
    });
  }

  for (const [key, val] of Object.entries(matrix)) {
    result.dependencyMatrix[key] = {
      calls: val.calls,
      imports: val.imports,
      via: Array.from(val.via)
    };
  }

  return result;
}

if (require.main === module) {
  const snapshotPath = process.argv[2];
  if (!snapshotPath) {
    console.error('Usage: node subsystem.js <snapshot.json>');
    process.exit(1);
  }
  const graph = GraphStore.loadSnapshot(snapshotPath);
  const result = buildSubsystems(graph);
  console.log(JSON.stringify(result, null, 2));
}

module.exports = { buildSubsystems, relPath, subsystemOf };
