const GraphStore = require('./graph.js');
const { buildSubsystems, relPath, subsystemOf } = require('./subsystem.js');

/**
 * Phase 7C: Flow Tracer
 * Walks the call graph across subsystem boundaries to produce sequenced data flow narratives.
 */

/**
 * Build reusable indexes from a graph + subsystem map.
 * Call once, then pass to traceFlow for each entry point.
 */
function buildFlowIndex(graph, subsystemMap, opts = {}) {
  const godThreshold = opts.godThreshold || 50;

  // File → subsystem lookup
  const fileSub = new Map();
  for (const sub of subsystemMap.subsystems) {
    for (const f of sub.files) fileSub.set(f, sub.name);
  }

  // Outgoing CALLS index: source → [targets]
  const callsOut = new Map();
  for (const e of graph.edges) {
    if (e.type !== 'CALLS') continue;
    if (!callsOut.has(e.source)) callsOut.set(e.source, []);
    callsOut.get(e.source).push(e.target);
  }

  // Function name → qualified IDs
  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);
    }
  }

  // In-degree per qualified ID
  const inDegree = new Map();
  for (const e of graph.edges) {
    if (e.type !== 'CALLS') continue;
    if (e.target.includes('/')) {
      inDegree.set(e.target, (inDegree.get(e.target) || 0) + 1);
    } else {
      // For bare names, increment all candidates
      const candidates = funcLookup.get(e.target);
      if (candidates) {
        for (const c of candidates) {
          inDegree.set(c, (inDegree.get(c) || 0) + 1);
        }
      }
    }
  }

  // God objects: qualified IDs with in-degree > threshold
  const godObjects = new Set();
  for (const [id, deg] of inDegree) {
    if (deg > godThreshold) godObjects.add(id);
  }

  // Subsystem cache
  const subCache = new Map();
  function getSubsystem(entityId) {
    if (!entityId) return null;
    if (subCache.has(entityId)) return subCache.get(entityId);
    const file = relPath(entityId.split(':')[0]);
    const result = fileSub.get(file) || null;
    subCache.set(entityId, result);
    return result;
  }

  // Resolve bare function name → qualified ID, preferring caller's subsystem
  function resolveTarget(bareName, callerSub) {
    const candidates = funcLookup.get(bareName);
    if (!candidates || candidates.length === 0) return null;
    if (candidates.length === 1) return candidates[0];
    for (const c of candidates) {
      if (getSubsystem(c) === callerSub) return c;
    }
    return candidates[0];
  }

  return { fileSub, callsOut, funcLookup, godObjects, getSubsystem, resolveTarget };
}

/**
 * Trace a flow from an entry point through the call graph.
 * @param {string} entryPoint - Entity ID (e.g. "channels/telegram.ts:onTelegramMessage")
 * @param {object} index - Precomputed index from buildFlowIndex
 * @param {object} opts - Options: { maxDepth, timeoutMs }
 */
function traceFlow(entryPoint, index, opts = {}) {
  const maxDepth = opts.maxDepth || 8;
  const timeout = opts.timeoutMs || 5000;
  const startTime = Date.now();

  const { callsOut, godObjects, getSubsystem, resolveTarget } = index;

  const visited = new Set();
  const flow = [];
  const cyclesDetected = [];
  const excludedNodes = new Set();

  const entrySub = getSubsystem(entryPoint);
  if (!entrySub) {
    return { entryPoint, error: `Entry point "${entryPoint}" not found in any subsystem`, flow: [], subsystemSequence: [], cyclesDetected: [], excludedNodes: [] };
  }

  // BFS with index pointer (no shift)
  const queue = [[entryPoint, 0]];
  let head = 0;
  visited.add(entryPoint);
  flow.push({ subsystem: entrySub, entity: entryPoint, depth: 0 });

  const subsystemSequence = [entrySub];
  const seqSet = new Set([entrySub]);

  while (head < queue.length) {
    if (Date.now() - startTime > timeout) break;

    const [current, depth] = queue[head++];
    const currentSub = getSubsystem(current);

    const targets = callsOut.get(current) || [];
    for (const rawTarget of targets) {
      // Skip test files
      if (rawTarget.includes('.test.') || rawTarget.includes('.spec.') || rawTarget.includes('__tests__/')) continue;

      let resolvedTarget = rawTarget;
      let targetSub = null;

      if (rawTarget.includes('/')) {
        // Qualified target — check god object by qualified ID
        if (godObjects.has(rawTarget)) {
          excludedNodes.add(rawTarget);
          continue;
        }
        targetSub = getSubsystem(rawTarget);
      } else {
        // Bare name — resolve to qualified ID first, then check god status
        resolvedTarget = resolveTarget(rawTarget, currentSub);
        if (!resolvedTarget) continue;
        if (godObjects.has(resolvedTarget)) {
          excludedNodes.add(resolvedTarget);
          continue;
        }
        targetSub = getSubsystem(resolvedTarget);
      }

      if (!targetSub) continue;

      // Cycle detection
      if (visited.has(resolvedTarget)) {
        cyclesDetected.push({ at: current, backEdgeTo: resolvedTarget });
        continue;
      }

      // Compute new depth
      const isCrossSubsystem = targetSub !== currentSub;
      const newDepth = depth + (isCrossSubsystem ? 1 : 0.5);

      if (newDepth > maxDepth) continue;

      visited.add(resolvedTarget);
      flow.push({
        subsystem: targetSub,
        entity: resolvedTarget,
        depth: newDepth,
        ...(isCrossSubsystem ? { crossedVia: 'CALLS' } : {})
      });

      if (isCrossSubsystem && !seqSet.has(targetSub)) {
        subsystemSequence.push(targetSub);
        seqSet.add(targetSub);
      }

      queue.push([resolvedTarget, newDepth]);
    }
  }

  return {
    entryPoint,
    depth: maxDepth,
    godThreshold: index.godObjects.size > 0 ? 'applied' : 'none',
    excludedNodes: Array.from(excludedNodes),
    cyclesDetected,
    flow,
    subsystemSequence
  };
}

/**
 * Auto-detect entry points from the graph.
 * Heuristics:
 * 1. Helm Deployments/StatefulSets with incoming Service/Ingress edges
 * 2. Python files with __main__ guard
 * 3. Shell scripts with main() function
 * 4. CI pipeline files (.github/workflows/, .gitlab-ci.yml)
 * 5. Go files with main() function in package main
 */
function detectEntryPoints(graph) {
  const entryPoints = [];
  const seen = new Set();

  for (const [id, node] of graph.nodes) {
    // 1. Helm: Deployments/StatefulSets that have a Service pointing to them
    if (node.kind === 'helm-resource' || node.kind === 'HelmWorkloads') {
      const resourceType = node.resourceType || node.name || '';
      if (resourceType.includes('Deployment') || resourceType.includes('StatefulSet') ||
          node.type === 'Deployment' || node.type === 'StatefulSet') {
        // Check if any Service/Ingress references this
        const hasService = graph.edges.some(e =>
          (e.target === id || e.source === id) &&
          (e.type === 'EXPOSES' || e.type === 'ROUTES_TO' || e.type === 'DEPENDS_ON')
        );
        if (hasService && !seen.has(id)) {
          seen.add(id);
          entryPoints.push({ id, name: node.name, kind: 'helm-workload', reason: 'Deployment/StatefulSet with service' });
        }
      }
    }

    // 2. Python __main__
    if (node._file && node._file.endsWith('.py') && node.type === 'Function' && node.name === '__main__') {
      if (!seen.has(id)) {
        seen.add(id);
        entryPoints.push({ id, name: node.name, kind: 'python-main', reason: 'Python __main__ guard' });
      }
    }

    // 3. Shell main()
    if (node._file && (node._file.endsWith('.sh') || node._file.endsWith('.bash'))) {
      if (node.type === 'Function' && node.name === 'main') {
        if (!seen.has(id)) {
          seen.add(id);
          entryPoints.push({ id, name: node.name, kind: 'shell-main', reason: 'Shell main() function' });
        }
      }
    }

    // 4. Go main()
    if (node._file && node._file.endsWith('.go') && node.type === 'Function' && node.name === 'main') {
      if (!seen.has(id)) {
        seen.add(id);
        entryPoints.push({ id, name: node.name, kind: 'go-main', reason: 'Go main() function' });
      }
    }
  }

  // 5. CI pipeline files
  for (const [filePath] of graph.fileIndex) {
    const rel = filePath;
    if (rel.includes('.github/workflows/') || rel.includes('.gitlab-ci') ||
        rel.includes('Jenkinsfile') || rel.includes('.circleci/')) {
      const fileEntities = graph.fileIndex.get(filePath);
      if (fileEntities && !seen.has(filePath)) {
        seen.add(filePath);
        entryPoints.push({ id: filePath, name: filePath, kind: 'ci-pipeline', reason: 'CI/CD pipeline file' });
      }
    }
  }

  return entryPoints;
}

if (require.main === module) {
  const snapshotPath = process.argv[2];
  const entryPoint = process.argv[3];
  const godThreshold = parseInt(process.argv[4]) || 50;
  if (!snapshotPath || !entryPoint) {
    console.error('Usage: node flow.js <snapshot.json> <entryPoint> [godThreshold]');
    process.exit(1);
  }
  const graph = GraphStore.loadSnapshot(snapshotPath);
  const subsystemMap = buildSubsystems(graph);
  const index = buildFlowIndex(graph, subsystemMap, { godThreshold });
  const result = traceFlow(entryPoint, index);
  console.log(JSON.stringify(result, null, 2));
}

module.exports = { buildFlowIndex, traceFlow, detectEntryPoints };
