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

/**
 * Phase 9: Change Impact Query
 * Given a node ID, traverse graph edges to find all affected downstream entities.
 * Uses reverse BFS on DEPENDS_ON, IMPORTS, CALLS, and CONTAINS edges.
 */

/**
 * Query the blast radius of changing a given node.
 * @param {GraphStore} graph - The populated knowledge graph.
 * @param {string} targetNodeId - The node being modified.
 * @param {number} maxDepth - Max traversal depth (default 10).
 * @returns {Object} { target, impacted: [...], tree: {...}, depth, circular: [...] }
 */
function queryImpact(graph, targetNodeId, maxDepth = 10) {
  const impacted = [];
  const visited = new Set();
  const circular = [];
  const tree = { id: targetNodeId, depth: 0, children: [] };
  const treeMap = new Map(); // nodeId → tree node for parent linking

  visited.add(targetNodeId);
  treeMap.set(targetNodeId, tree);

  // Build reverse adjacency: target → [sources] for relevant edge types
  const reverseAdj = new Map();
  const IMPACT_EDGE_TYPES = new Set(['DEPENDS_ON', 'IMPORTS', 'CALLS', 'CONTAINS', 'USES']);

  for (const edge of graph.edges) {
    if (!IMPACT_EDGE_TYPES.has(edge.type)) continue;
    if (!reverseAdj.has(edge.target)) reverseAdj.set(edge.target, []);
    reverseAdj.get(edge.target).push({ source: edge.source, type: edge.type });
  }

  // BFS
  const queue = [{ id: targetNodeId, depth: 0 }];
  let head = 0;

  while (head < queue.length) {
    const { id: currentId, depth } = queue[head++];
    if (depth >= maxDepth) continue;

    const dependents = reverseAdj.get(currentId) || [];
    for (const { source, type } of dependents) {
      if (visited.has(source)) {
        circular.push({ from: source, to: currentId, type });
        continue;
      }

      visited.add(source);
      const node = graph.nodes.get(source);
      const entry = {
        id: source,
        name: node?.name || source,
        kind: node?.kind || node?.type || 'unknown',
        depth: depth + 1,
        via: type,
        from: currentId
      };
      impacted.push(entry);

      // Build tree
      const treeNode = { id: source, depth: depth + 1, via: type, children: [] };
      treeMap.set(source, treeNode);
      const parentTree = treeMap.get(currentId);
      if (parentTree) parentTree.children.push(treeNode);

      queue.push({ id: source, depth: depth + 1 });
    }
  }

  return {
    target: targetNodeId,
    targetNode: graph.nodes.get(targetNodeId) || null,
    impacted,
    impactedCount: impacted.length,
    maxDepthReached: impacted.some(i => i.depth >= maxDepth),
    circular,
    tree
  };
}

/**
 * Format impact result as markdown.
 */
function formatImpactMarkdown(result) {
  const lines = [];
  lines.push(`## Change Impact: \`${result.target}\``);
  lines.push('');
  if (result.targetNode) {
    lines.push(`**Kind:** ${result.targetNode.kind || result.targetNode.type}`);
    lines.push('');
  }
  lines.push(`**Total impacted:** ${result.impactedCount} entities`);
  lines.push('');

  if (result.impacted.length === 0) {
    lines.push('No downstream dependents found.');
    return lines.join('\n');
  }

  // Group by depth
  const byDepth = new Map();
  for (const item of result.impacted) {
    if (!byDepth.has(item.depth)) byDepth.set(item.depth, []);
    byDepth.get(item.depth).push(item);
  }

  for (const [depth, items] of [...byDepth.entries()].sort((a, b) => a[0] - b[0])) {
    lines.push(`### Depth ${depth}`);
    for (const item of items) {
      lines.push(`- \`${item.id}\` (${item.kind}) via ${item.via}`);
    }
    lines.push('');
  }

  if (result.circular.length > 0) {
    lines.push('### Circular References');
    for (const c of result.circular) {
      lines.push(`- ${c.from} ↔ ${c.to} (${c.type})`);
    }
  }

  return lines.join('\n');
}

// CLI
if (require.main === module) {
  const snapshotPath = process.argv[2];
  const targetId = process.argv[3];
  const maxDepth = parseInt(process.argv[4]) || 10;
  const format = process.argv[5] || 'json';

  if (!snapshotPath || !targetId) {
    console.error('Usage: node impact.js <snapshot.json> <targetNodeId> [maxDepth] [json|md]');
    process.exit(1);
  }

  const graph = GraphStore.loadSnapshot(snapshotPath);
  const result = queryImpact(graph, targetId, maxDepth);

  if (format === 'md') {
    console.log(formatImpactMarkdown(result));
  } else {
    console.log(JSON.stringify(result, null, 2));
  }
}

module.exports = { queryImpact, formatImpactMarkdown };
