import type { Database, Statement } from "./database.js";

export interface SymbolRef {
  id: number;
  source_chunk_id: number;
  target_name: string;
  line: number | null;
}

export interface ImpactResult {
  chunk_id: number;
  chunk_name: string | null;
  chunk_type: string;
  file_path: string;
  start_line: number;
  end_line: number;
  ref_line: number | null;
}

export class SymbolRefStore {
  private insertStmt: Statement;
  private deleteByChunkStmt: Statement;
  private getImpactStmt: Statement;
  private getCallersStmt: Statement;
  private getAllStmt: Statement;
  private countStmt: Statement;

  constructor(private db: Database) {
    this.insertStmt = db.prepare(`
      INSERT OR IGNORE INTO symbol_refs (source_chunk_id, target_name, line)
      VALUES (?, ?, ?)
    `);
    this.deleteByChunkStmt = db.prepare(
      "DELETE FROM symbol_refs WHERE source_chunk_id = ?"
    );
    // Impact: find all chunks that reference a given symbol name,
    // joined with their file + chunk metadata for direct display
    this.getImpactStmt = db.prepare(`
      SELECT
        c.id as chunk_id,
        c.name as chunk_name,
        c.type as chunk_type,
        c.start_line,
        c.end_line,
        f.path as file_path,
        sr.line as ref_line
      FROM symbol_refs sr
      JOIN chunks c ON c.id = sr.source_chunk_id
      JOIN files f ON f.id = c.file_id
      WHERE sr.target_name = ?
      ORDER BY f.pagerank DESC
      LIMIT ?
    `);
    this.getCallersStmt = db.prepare(`
      SELECT COUNT(*) as c FROM symbol_refs WHERE target_name = ?
    `);
    this.getAllStmt = db.prepare("SELECT * FROM symbol_refs");
    this.countStmt = db.prepare("SELECT COUNT(*) as c FROM symbol_refs");
  }

  insert(sourceChunkId: number, targetName: string, line: number | null = null): void {
    this.insertStmt.run(sourceChunkId, targetName, line);
  }

  deleteByChunk(chunkId: number): void {
    this.deleteByChunkStmt.run(chunkId);
  }

  getImpact(targetName: string, limit: number = 50): ImpactResult[] {
    return this.getImpactStmt.all(targetName, limit) as unknown as ImpactResult[];
  }

  getCallerCount(targetName: string): number {
    return (this.getCallersStmt.get(targetName) as { c: number }).c;
  }

  /**
   * Aggregated god-node stats: per target_name, total refs and the
   * number of DISTINCT source files that contain those refs. Used by
   * sverklo_audit to rank "god nodes" — a name referenced 200 times
   * from one file is less interesting structurally than one referenced
   * 30 times across 30 different files. Dogfood T2 from the 2026-05-13
   * architectural review.
   *
   * `excludeFileIds` optionally excludes refs whose SOURCE file is in
   * the provided set — used by sverklo_audit to drop test-file and
   * vendored-cache refs from the count so identifier collisions like
   * `parse` (mostly JSON.parse + test helpers) don't dominate the
   * ranking. Dogfood review 2026-05-14 (Issue E).
   */
  getGodNodeStats(
    excludeFileIds?: Set<number>,
  ): { target_name: string; ref_count: number; distinct_source_files: number }[] {
    if (!excludeFileIds || excludeFileIds.size === 0) {
      return this.db
        .prepare(
          `SELECT sr.target_name,
                  COUNT(*)                       AS ref_count,
                  COUNT(DISTINCT c.file_id)      AS distinct_source_files
           FROM symbol_refs sr
           JOIN chunks c ON c.id = sr.source_chunk_id
           GROUP BY sr.target_name`,
        )
        .all() as { target_name: string; ref_count: number; distinct_source_files: number }[];
    }
    // SQLite has SQLITE_MAX_VARIABLE_NUMBER (~32k); our exclude sets
    // are typically <2k vendored/test files. Build IN-clause inline.
    const ids = Array.from(excludeFileIds);
    const placeholders = ids.map(() => "?").join(",");
    return this.db
      .prepare(
        `SELECT sr.target_name,
                COUNT(*)                       AS ref_count,
                COUNT(DISTINCT c.file_id)      AS distinct_source_files
         FROM symbol_refs sr
         JOIN chunks c ON c.id = sr.source_chunk_id
         WHERE c.file_id NOT IN (${placeholders})
         GROUP BY sr.target_name`,
      )
      .all(...ids) as { target_name: string; ref_count: number; distinct_source_files: number }[];
  }

  getAll(): SymbolRef[] {
    return this.getAllStmt.all() as unknown as SymbolRef[];
  }

  count(): number {
    return (this.countStmt.get() as { c: number }).c;
  }
}
