/**
 * Reference Resolution Types
 *
 * Types for the reference resolution system.
 */

import { Language, Node, ReferenceKind } from '../types';

/**
 * An unresolved reference from extraction
 */
export interface UnresolvedRef {
  /** ID of the source node containing the reference */
  fromNodeId: string;
  /** The name being referenced */
  referenceName: string;
  /** Type of reference */
  referenceKind: ReferenceKind;
  /** Line where reference occurs */
  line: number;
  /** Column where reference occurs */
  column: number;
  /** File path where reference occurs */
  filePath: string;
  /** Language of the source file */
  language: Language;
  /** Possible qualified names it might resolve to */
  candidates?: string[];
  /** `unresolved_refs.id` when loaded from the database — post-pass cleanup
   * targets exactly this row instead of every same-key sibling (#1269). */
  rowId?: number;
}

/**
 * A resolved reference
 */
export interface ResolvedRef {
  /** Original unresolved reference */
  original: UnresolvedRef;
  /** ID of the target node */
  targetNodeId: string;
  /** Confidence score (0-1) */
  confidence: number;
  /** How it was resolved */
  resolvedBy: 'exact-match' | 'import' | 'qualified-name' | 'framework' | 'fuzzy' | 'instance-method' | 'file-path' | 'function-ref';
}

/**
 * Result of resolution attempt
 */
export interface ResolutionResult {
  /** Successfully resolved references */
  resolved: ResolvedRef[];
  /** References that couldn't be resolved */
  unresolved: UnresolvedRef[];
  /** Statistics */
  stats: {
    total: number;
    resolved: number;
    unresolved: number;
    byMethod: Record<string, number>;
  };
}

/**
 * Context for resolution - provides access to the graph
 */
export interface ResolutionContext {
  /** Get all nodes in a file */
  getNodesInFile(filePath: string): Node[];
  /** Get all nodes by name */
  getNodesByName(name: string): Node[];
  /** Get all nodes by qualified name */
  getNodesByQualifiedName(qualifiedName: string): Node[];
  /** Get all nodes of a kind */
  getNodesByKind(kind: Node['kind']): Node[];
  /**
   * Stream nodes of a kind one at a time instead of materializing (and, unlike
   * `getNodesByKind`, without populating the resolver's per-kind array cache).
   * For unbounded kinds (`function`, `method`, `struct`) on a symbol-dense
   * project the full array is gigabytes — the dynamic-edge synthesizers must
   * use this so their memory stays O(1) in node count (#610, #1212). Optional
   * so minimal test contexts compile; callers fall back to getNodesByKind.
   */
  iterateNodesByKind?(kind: Node['kind']): IterableIterator<Node>;
  /** Check if a file exists */
  fileExists(filePath: string): boolean;
  /** Read file content */
  readFile(filePath: string): string | null;
  /**
   * `readFile(filePath)` split into lines, LRU-cached per file. Receiver-type
   * inference scans source lines for EVERY `receiver.method()` ref; splitting
   * the whole file per ref made that O(refs-in-file × file-size) — ~20% of
   * total index CPU on a Java-heavy repo and a driver of the #1122 watchdog
   * kill on large ones. Optional so external/test contexts compile without it;
   * callers fall back to splitting `readFile` themselves.
   */
  getFileLines?(filePath: string): string[] | null;
  /**
   * The method-definition nodes matching `typeName::methodName` in `language` —
   * exactly `resolveMethodOnType`'s kind/language/qualifiedName-suffix filter,
   * LRU-cached per (language, type, method). The uncached path re-fetches every
   * node sharing the METHOD name (unbounded — tens of thousands on a collision-
   * heavy Java repo) and re-scans it per ref, the dominant term in the #1122
   * watchdog kill. Cached entries hold only the small filtered result; per-ref
   * disambiguation (import FQN, call-site file) stays in the caller so a cached
   * entry is valid from any call site. Optional for external/test contexts.
   */
  getMethodMatches?(typeName: string, methodName: string, language: Language): Node[];
  /** Get project root */
  getProjectRoot(): string;
  /** Get all files */
  getAllFiles(): string[];
  /** Get nodes by lowercase name (O(1) lookup for fuzzy matching) */
  getNodesByLowerName(lowerName: string): Node[];
  /**
   * Direct supertypes of the type named `typeName` (same language): the classes
   * it extends and the interfaces / protocols / traits it implements/conforms to,
   * by simple name. Backed by the resolved `implements`/`extends` edges, so it is
   * EMPTY during the first resolution pass (edges aren't built yet) and populated
   * afterward — the conformance pass uses it to resolve a chained method defined
   * on a supertype the receiver type conforms to (e.g. a protocol-extension
   * method). Optional so external/test contexts compile without it.
   */
  getSupertypes?(typeName: string, language: Language): string[];
  /**
   * Look up a node by its id. Lets matchers derive the FROM-symbol's
   * enclosing-class scope (Swift implicit-self method scoping, `this.X`
   * member resolution). Optional so external/test contexts compile
   * without it.
   */
  getNodeById?(id: string): Node | null;
  /** Get cached import mappings for a file */
  getImportMappings(filePath: string, language: Language): ImportMapping[];
  /**
   * Project import-path aliases (tsconfig/jsconfig `paths`). Returns
   * `null` when the project doesn't define any. Cached per resolver
   * instance — safe to call from any resolver code path. Optional so
   * existing test fixtures and external context implementations
   * compile without modification; production resolver implements it.
   */
  getProjectAliases?(): import('./path-aliases').AliasMap | null;
  /**
   * Go module info from `go.mod` at the project root. Returns `null`
   * when the project has no `go.mod` (non-Go projects, pre-modules
   * Go code, or projects whose modules live in subdirectories). Used
   * by the Go branch of import resolution to distinguish in-module
   * cross-package imports from third-party packages.
   */
  getGoModule?(): import('./go-module').GoModule | null;
  /**
   * Monorepo workspace member packages, keyed by declared package name.
   * Returns `null` for single-package repos (no `workspaces` field).
   * Lets the resolver treat `@scope/ui/sub` as a local import into the
   * member's directory instead of an external npm package (#629).
   */
  getWorkspacePackages?(): import('./workspace-packages').WorkspacePackages | null;
  /**
   * Re-exports declared by a file (`export { x } from './other'`,
   * `export * from './other'`). Empty array when the file has none.
   * Optional so older callers compile; the import resolver follows
   * re-export chains when this is provided.
   */
  getReExports?(filePath: string, language: Language): ReExport[];
  /**
   * List immediate subdirectories of `relativePath` (relative to the
   * project root). Returns an empty array when the path doesn't exist
   * or isn't a directory. Used by framework resolvers that need to
   * walk build-system metadata (e.g. Cargo workspace globs). Optional
   * so external context implementations and test fixtures compile
   * without modification.
   */
  listDirectories?(relativePath: string): string[];
  /**
   * C/C++ include search directories (relative to project root),
   * extracted from compile_commands.json or discovered by heuristic.
   * Used by resolveCppIncludePath to search -I directories when
   * relative resolution fails. Optional so existing callers compile.
   */
  getCppIncludeDirs?(): string[];
}

/**
 * Result of framework-specific file extraction.
 */
export interface FrameworkExtractionResult {
  /** Framework-specific nodes (e.g. routes) */
  nodes: Node[];
  /** Framework-specific unresolved references (e.g. route -> handler) */
  references: UnresolvedRef[];
}

/**
 * Framework-specific resolver
 */
export interface FrameworkResolver {
  /** Framework name */
  name: string;
  /** Languages this framework applies to. If omitted, applies to all languages. */
  languages?: Language[];
  /** Detect if project uses this framework (project-level, called once at startup) */
  detect(context: ResolutionContext): boolean;
  /** Resolve a reference using framework-specific patterns */
  resolve(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null;
  /**
   * Opt a reference NAME through the resolver's name-exists pre-filter, even when
   * no node is named that. Needed for dynamic dispatch where the call target is
   * an attribute/descriptor, not a declared symbol (e.g. Django's
   * `self._iterable_class(...)`, React effect callbacks). Returning true lets the
   * ref reach `resolve()` instead of being dropped for having no name match.
   */
  claimsReference?(name: string): boolean;
  /**
   * Extract framework-specific nodes and references from a file.
   *
   * Returns route nodes, middleware nodes, etc., plus unresolved references
   * that link those nodes to handlers (view classes, controller methods,
   * included modules). Unresolved references flow into the normal resolution
   * pipeline; the framework's own `resolve()` is one of the strategies tried.
   */
  extract?(filePath: string, content: string): FrameworkExtractionResult;
  /**
   * Cross-file finalization pass, called once after all per-file extraction
   * completes (and again on every incremental sync). Used by frameworks where
   * a symbol's final representation depends on a sibling file the per-file
   * `extract()` never saw — e.g. NestJS's `RouterModule.register([...])`
   * sets route prefixes for controllers declared elsewhere.
   *
   * Implementations return route/etc. nodes with mutated fields (typically
   * `name`); the orchestrator persists each via `updateNode`. The node `id`
   * MUST be preserved so existing edges (route → handler, etc.) stay intact;
   * `qualifiedName` SHOULD be preserved so the pass stays idempotent — a
   * second run can recover the original in-file form from `qualifiedName`.
   */
  postExtract?(context: ResolutionContext): Node[];
}

/**
 * Import mapping from a file
 */
export interface ImportMapping {
  /** Local name used in the file */
  localName: string;
  /** Original exported name (may differ due to aliasing) */
  exportedName: string;
  /** Source module/path */
  source: string;
  /** Whether it's a default import */
  isDefault: boolean;
  /** Whether it's a namespace import (import * as X) */
  isNamespace: boolean;
  /** Resolved file path (if local) */
  resolvedPath?: string;
}

/**
 * Re-export from a file: `export { x } from './other'` or
 * `export * from './other'`. Used by the resolver to chase
 * symbols through barrel files.
 */
export type ReExport =
  | {
      kind: 'named';
      /** Name as exported by THIS file. */
      exportedName: string;
      /** Name in the upstream module (differs when renamed: `as`). */
      originalName: string;
      /** Module specifier of the upstream module. */
      source: string;
    }
  | {
      kind: 'wildcard';
      /** Module specifier of the upstream module. */
      source: string;
    };
