/**
 * Touched Areas Provider
 *
 * Extracts AST neighbors of modified files.
 * All entities in touched files get uniform high scores.
 */

import { ContextProvider } from './base.js';
import type { ContextItem, ContextProviderSpec } from '../types.js';
import type { KuzuConnection } from '../../graph/connection.js';

/**
 * Touched Areas Provider
 *
 * When files are modified, extract all entities in those files
 * plus their AST neighbors (callers, callees, inheritance).
 */
export class TouchedAreasProvider extends ContextProvider {
  private neighborDepth: number;

  constructor(conn?: KuzuConnection, config?: Record<string, unknown>) {
    super(conn, config);
    this.neighborDepth = (config?.neighborDepth as number) ?? 2;
  }

  get spec(): ContextProviderSpec {
    return {
      name: 'touched_areas',
      description: 'Extracts AST neighbors of modified files',
      requiresGraph: true,
    };
  }

  /**
   * Extract context from modified file paths
   */
  async extractContext(input: unknown): Promise<ContextItem[]> {
    const filePaths = input as string[];
    if (!Array.isArray(filePaths) || filePaths.length === 0) {
      return [];
    }

    if (!this.conn) {
      return [];
    }

    const items: ContextItem[] = [];
    const now = new Date();
    const seen = new Set<string>();

    for (const filePath of filePaths) {
      // Get all entities in this file
      const entities = await this.getEntitiesInFile(filePath);

      for (const entity of entities) {
        if (seen.has(entity.qualifiedName)) continue;
        seen.add(entity.qualifiedName);

        // All touched entities get score 1.0
        items.push({
          ...entity,
          score: 1.0,
          touchCount: 1,
          lastTouched: now,
        });
      }

      // Get neighbors of entities in this file
      const neighbors = await this.getNeighbors(entities, this.neighborDepth);

      for (const neighbor of neighbors) {
        if (seen.has(neighbor.qualifiedName)) continue;
        seen.add(neighbor.qualifiedName);

        // Neighbors get slightly lower score
        items.push({
          ...neighbor,
          score: 0.8,
          touchCount: 1,
          lastTouched: now,
        });
      }
    }

    return items;
  }

  /**
   * Get all entities in a file
   */
  private async getEntitiesInFile(filePath: string): Promise<Partial<ContextItem>[]> {
    if (!this.conn) return [];

    const entities: Partial<ContextItem>[] = [];

    try {
      // Get functions in file
      const funcResult = await this.conn.query(`
        MATCH (f:File {path: $path})-[:CONTAINS_FUNCTION]->(fn:Function)
        RETURN fn.qualified_name AS name, fn.docstring AS doc
      `, { path: filePath });

      for (const row of funcResult.all()) {
        entities.push({
          qualifiedName: row.name as string,
          entityType: 'Function',
          description: row.doc as string | undefined,
          filePath,
          neighbors: [],
        });
      }

      // Get classes in file
      const classResult = await this.conn.query(`
        MATCH (f:File {path: $path})-[:CONTAINS_CLASS]->(c:Class)
        RETURN c.qualified_name AS name, c.docstring AS doc
      `, { path: filePath });

      for (const row of classResult.all()) {
        entities.push({
          qualifiedName: row.name as string,
          entityType: 'Class',
          description: row.doc as string | undefined,
          filePath,
          neighbors: [],
        });
      }
    } catch {
      // Ignore query errors
    }

    return entities;
  }

  /**
   * Get N-hop neighbors of entities
   */
  private async getNeighbors(
    entities: Partial<ContextItem>[],
    depth: number
  ): Promise<Partial<ContextItem>[]> {
    if (!this.conn || depth <= 0) return [];

    const neighbors: Partial<ContextItem>[] = [];
    const seenNames = new Set(entities.map((e) => e.qualifiedName));

    for (const entity of entities) {
      if (!entity.qualifiedName) continue;

      try {
        if (entity.entityType === 'Function') {
          // Get callers and callees up to depth
          const result = await this.conn.query(`
            MATCH (f:Function {qualified_name: $name})-[:CALLS*1..${depth}]->(callee:Function)
            RETURN DISTINCT callee.qualified_name AS name,
                   callee.docstring AS doc,
                   callee.file_path AS path
            UNION
            MATCH (caller:Function)-[:CALLS*1..${depth}]->(f:Function {qualified_name: $name})
            RETURN DISTINCT caller.qualified_name AS name,
                   caller.docstring AS doc,
                   caller.file_path AS path
            LIMIT 20
          `, { name: entity.qualifiedName });

          for (const row of result.all()) {
            const name = row.name as string;
            if (seenNames.has(name)) continue;
            seenNames.add(name);

            neighbors.push({
              qualifiedName: name,
              entityType: 'Function',
              description: row.doc as string | undefined,
              filePath: row.path as string | undefined,
              neighbors: [],
            });
          }
        } else if (entity.entityType === 'Class') {
          // Get methods and inheritance chain
          const result = await this.conn.query(`
            MATCH (c:Class {qualified_name: $name})-[:HAS_METHOD]->(m:Function)
            RETURN m.qualified_name AS name,
                   m.docstring AS doc,
                   m.file_path AS path,
                   'Function' AS type
            UNION
            MATCH (c:Class {qualified_name: $name})-[:INHERITS_FROM*1..${depth}]->(parent:Class)
            RETURN parent.qualified_name AS name,
                   parent.docstring AS doc,
                   parent.file_path AS path,
                   'Class' AS type
            UNION
            MATCH (child:Class)-[:INHERITS_FROM*1..${depth}]->(c:Class {qualified_name: $name})
            RETURN child.qualified_name AS name,
                   child.docstring AS doc,
                   child.file_path AS path,
                   'Class' AS type
            LIMIT 20
          `, { name: entity.qualifiedName });

          for (const row of result.all()) {
            const name = row.name as string;
            if (seenNames.has(name)) continue;
            seenNames.add(name);

            neighbors.push({
              qualifiedName: name,
              entityType: (row.type as 'Function' | 'Class') ?? 'Function',
              description: row.doc as string | undefined,
              filePath: row.path as string | undefined,
              neighbors: [],
            });
          }
        }
      } catch {
        // Ignore query errors
      }
    }

    return neighbors;
  }
}
