/**
 * Graph extraction functions: tree/graph context builders,
 * class/method extractors, and module dependency resolution.
 */

import { KnowledgeGraph } from "../../repopilot/graph/knowledge-graph";
import type { ModuleNode } from "../../repopilot/module-tree";
import { EdgeType, NodeType } from "../../repopilot/types";
import type { ClassEntry, MethodEntry } from "../types";
import {
  collectModuleFiles,
  findModule,
  getNumber,
  getString,
  uniqueStrings,
} from "../utils";
import { buildCodeLink } from "../code-links";

/* ------------------------------------------------------------------ */
/*  Context builders for prompts                                       */
/* ------------------------------------------------------------------ */

export function buildTreeContext(
  module: ModuleNode,
  tree: ModuleNode,
): string {
  const lines: string[] = [];
  lines.push(`Module: ${module.name}`);
  lines.push(`Path: ${module.path}`);
  lines.push(`Level: ${module.level}`);

  if (module.parent) {
    const parent = findModule(tree, module.parent);
    if (parent) {
      lines.push(`Parent: ${parent.name} (${parent.path})`);
      const siblings = parent.children.filter((c) => c.id !== module.id);
      if (siblings.length > 0) {
        lines.push(`Siblings: ${siblings.map((s) => s.name).join(", ")}`);
      }
    }
  }

  if (module.children.length > 0) {
    lines.push(
      `Children: ${module.children.map((c) => c.name).join(", ")}`,
    );
  }

  lines.push(
    `Classes: ${module.classes.length > 0 ? module.classes.join(", ") : "(none — see children)"}`,
  );
  lines.push(
    `Files: ${module.files.length > 0 ? module.files.join(", ") : "(none — see children)"}`,
  );

  return `## Module Position in Codebase\n${lines.join("\n")}`;
}

export function buildGraphContext(
  module: ModuleNode,
  graph: KnowledgeGraph,
  sourcePath: string,
): string {
  const files = new Set(collectModuleFiles(module));
  const lines: string[] = [];

  for (const nodeType of [
    NodeType.CLASS,
    NodeType.INTERFACE,
    NodeType.ENUM,
  ]) {
    for (const nodeId of graph.getNodesByType(nodeType)) {
      const attrs = graph.getNodeAttrs(nodeId);
      const filePath = getString(attrs, "file_path");
      if (!filePath || !files.has(filePath)) continue;

      const link = buildCodeLink(
        `${String(attrs.name)}:${getNumber(attrs, "line_start") ?? 1}`,
        sourcePath,
        filePath,
        getNumber(attrs, "line_start"),
        getNumber(attrs, "line_end"),
        String(attrs.name),
      );
      lines.push(`- ${attrs.type}: ${link}`);
    }
  }

  return lines.length > 0
    ? `## Indexed Code Symbols\n${lines.join("\n")}`
    : "## Indexed Code Symbols\nNo graph data available for this module.";
}

/* ------------------------------------------------------------------ */
/*  Graph extraction — classes, methods, dependencies                  */
/* ------------------------------------------------------------------ */

export function getClassEntries(
  graph: KnowledgeGraph,
  moduleFiles: Set<string>,
): ClassEntry[] {
  const entries: ClassEntry[] = [];

  for (const nodeType of [
    NodeType.CLASS,
    NodeType.INTERFACE,
    NodeType.ENUM,
  ]) {
    for (const nodeId of graph.getNodesByType(nodeType)) {
      const attrs = graph.getNodeAttrs(nodeId);
      const filePath = getString(attrs, "file_path");
      if (!filePath || !moduleFiles.has(filePath)) continue;

      entries.push({
        nodeId,
        name: String(attrs.name),
        type: String(attrs.type),
        filePath,
        lineStart: getNumber(attrs, "line_start"),
        lineEnd: getNumber(attrs, "line_end"),
        extendsName: getString(attrs, "extends"),
        implementsNames: Array.isArray(attrs.implements)
          ? attrs.implements.filter(
              (v): v is string => typeof v === "string",
            )
          : [],
      });
    }
  }

  return entries.sort((a, b) => {
    if (a.filePath !== b.filePath)
      return a.filePath.localeCompare(b.filePath);
    return (a.lineStart ?? 0) - (b.lineStart ?? 0);
  });
}

export function getMethodEntries(
  graph: KnowledgeGraph,
  classes: ClassEntry[],
): MethodEntry[] {
  const entries: MethodEntry[] = [];

  for (const classEntry of classes) {
    const members = graph.getNeighbors(
      classEntry.nodeId,
      EdgeType.CONTAINS,
      "out",
    );
    for (const memberId of members) {
      let attrs: Record<string, unknown>;
      try {
        attrs = graph.getNodeAttrs(memberId);
      } catch {
        continue;
      }

      const type = String(attrs.type);
      if (type !== NodeType.METHOD && type !== NodeType.CONSTRUCTOR) continue;

      entries.push({
        nodeId: memberId,
        className: classEntry.name,
        name: String(attrs.name),
        type,
        filePath: classEntry.filePath,
        lineStart: getNumber(attrs, "line_start"),
        lineEnd: getNumber(attrs, "line_end"),
        returnType: getString(attrs, "return_type"),
        parameters: Array.isArray(attrs.parameters)
          ? attrs.parameters.filter(
              (v): v is string => typeof v === "string",
            )
          : [],
        javadoc: getString(attrs, "javadoc"),
      });
    }
  }

  return entries.sort((a, b) => {
    if (a.filePath !== b.filePath)
      return a.filePath.localeCompare(b.filePath);
    return (a.lineStart ?? 0) - (b.lineStart ?? 0);
  });
}

export function getModuleDependencyNames(
  module: ModuleNode,
  graph: KnowledgeGraph,
): string[] {
  if (!module.path) return [];

  const packageIds = graph
    .getNodesByType(NodeType.PACKAGE)
    .filter((pkgId) => {
      const attrs = graph.getNodeAttrs(pkgId);
      const name = getString(attrs, "name");
      if (!name) return false;
      return name === module.path || name.endsWith(`.${module.path}`);
    });

  const deps: string[] = [];
  for (const pkgId of packageIds) {
    for (const targetId of graph.getNeighbors(
      pkgId,
      EdgeType.DEPENDS_ON,
      "out",
    )) {
      if (!graph.hasNode(targetId)) continue;
      const attrs = graph.getNodeAttrs(targetId);
      const name = getString(attrs, "name");
      if (name) deps.push(name);
    }
  }
  return uniqueStrings(deps).sort();
}
