/**
 * Cross-file relationship building for the knowledge graph.
 */

import type { KnowledgeGraph } from "../graph/knowledge-graph";
import { EdgeType, NodeType, type GraphEdge } from "../types";

export function resolveRelationships(graph: KnowledgeGraph): number {
  let resolved = 0;

  // Build lookup: class simple name -> node ID
  const classLookup = new Map<string, string>();
  const methodLookup = new Map<string, string[]>();
  for (const nodeType of [NodeType.CLASS, NodeType.INTERFACE, NodeType.ENUM]) {
    for (const nodeId of graph.getNodesByType(nodeType)) {
      const attrs = graph.getNodeAttrs(nodeId);
      const name = (attrs.name as string) || "";
      if (name) classLookup.set(name, nodeId);
    }
  }
  for (const nodeId of graph.getNodesByType(NodeType.METHOD)) {
    const attrs = graph.getNodeAttrs(nodeId);
    const name = (attrs.name as string) || "";
    if (!name) continue;
    const existing = methodLookup.get(name) || [];
    existing.push(nodeId);
    methodLookup.set(name, existing);
  }
  for (const nodeId of graph.getNodesByType(NodeType.CONSTRUCTOR)) {
    const attrs = graph.getNodeAttrs(nodeId);
    const name = (attrs.name as string) || "";
    if (!name) continue;
    const existing = methodLookup.get(name) || [];
    existing.push(nodeId);
    methodLookup.set(name, existing);
  }

  // Resolve extends and implements edges for classes
  for (const nodeId of graph.getNodesByType(NodeType.CLASS)) {
    const attrs = graph.getNodeAttrs(nodeId);

    const superclass = (attrs.extends as string) || "";
    if (superclass) {
      const simpleName = superclass.split(".").pop() || superclass;
      const targetId = classLookup.get(simpleName);
      if (targetId) {
        graph.addEdge({
          source: nodeId,
          target: targetId,
          type: EdgeType.EXTENDS,
          attributes: {},
        });
        resolved++;
      }
    }

    const implements_ = (attrs.implements as string[]) || [];
    for (const ifaceName of implements_) {
      const simpleName = ifaceName.split(".").pop() || ifaceName;
      const targetId = classLookup.get(simpleName);
      if (targetId) {
        graph.addEdge({
          source: nodeId,
          target: targetId,
          type: EdgeType.IMPLEMENTS,
          attributes: {},
        });
        resolved++;
      }
    }
  }

  // Resolve import edges to actual class nodes
  for (const fileId of graph.getNodesByType(NodeType.FILE)) {
    const importTargets = graph.getNeighbors(fileId, EdgeType.IMPORTS, "out");
    for (const importTarget of importTargets) {
      if (typeof importTarget === "string" && importTarget.startsWith("import:")) {
        const fqn = importTarget.slice("import:".length);
        const simpleName = fqn.split(".").pop() || fqn;
        const targetId = classLookup.get(simpleName);
        if (targetId) {
          graph.addEdge({
            source: fileId,
            target: targetId,
            type: EdgeType.IMPORTS,
            attributes: {},
          });
          resolved++;
        }
      }
    }
  }

  // Resolve method call placeholder edges to actual method/constructor nodes.
  for (const methodId of [
    ...graph.getNodesByType(NodeType.METHOD),
    ...graph.getNodesByType(NodeType.CONSTRUCTOR),
  ]) {
    const callTargets = graph.getNeighbors(methodId, EdgeType.CALLS, "out");
    for (const callTarget of callTargets) {
      if (typeof callTarget !== "string" || !callTarget.startsWith("method_ref:")) {
        continue;
      }
      const methodName = callTarget.slice("method_ref:".length);
      const candidateIds = methodLookup.get(methodName) || [];
      for (const targetId of candidateIds.slice(0, 5)) {
        if (targetId === methodId) continue;
        graph.addEdge({
          source: methodId,
          target: targetId,
          type: EdgeType.CALLS,
          attributes: {},
        });
        resolved++;
      }
    }
  }

  // Build package dependency edges
  buildPackageDependencies(graph);

  console.log(`[scan.relationships.resolved] count=${resolved}`);
  return resolved;
}

function buildPackageDependencies(graph: KnowledgeGraph): void {
  const fileNodes = graph.getNodesByType(NodeType.FILE);

  // Map files to their packages
  const fileToPackage = new Map<string, string>();
  for (const fileId of fileNodes) {
    const attrs = graph.getNodeAttrs(fileId);
    const pkg = (attrs.package as string) || "";
    if (pkg) fileToPackage.set(fileId, `package:${pkg}`);
  }

  // Track package dependencies
  const pkgDeps = new Set<string>();

  for (const fileId of fileNodes) {
    const srcPkg = fileToPackage.get(fileId);
    if (!srcPkg) continue;

    const importTargets = graph.getNeighbors(fileId, EdgeType.IMPORTS, "out");
    for (const targetId of importTargets) {
      if (!graph.hasNode(targetId)) continue;
      const targetAttrs = graph.getNodeAttrs(targetId);
      const targetFile = (targetAttrs.file_path as string) || "";
      const targetFileId = `file:${targetFile}:${targetFile}`;
      const tgtPkg = fileToPackage.get(targetFileId);
      if (tgtPkg && tgtPkg !== srcPkg) {
        pkgDeps.add(`${srcPkg}|${tgtPkg}`);
      }
    }
  }

  for (const dep of pkgDeps) {
    const [srcPkg, tgtPkg] = dep.split("|");
    if (graph.hasNode(srcPkg) && graph.hasNode(tgtPkg)) {
      graph.addEdge({
        source: srcPkg,
        target: tgtPkg,
        type: EdgeType.DEPENDS_ON,
        attributes: {},
      });
    }
  }
}
