/**
 * Feature Context Builder
 *
 * Builds expanded feature context with call graphs and dependencies.
 */

import type { KuzuConnection } from '../graph/connection.js';
import type { EmbeddingService } from '../embeddings/service.js';
import { GraphQueries } from '../graph/queries.js';
import { SimilaritySearch } from './similarity.js';
import type {
  FeatureContext,
  SimilarEntity,
  SimilarPR,
  DomainExpert,
} from './types.js';
import { createLogger, type Logger } from '../utils/logger.js';

/**
 * Feature context builder options
 */
export interface FeatureContextOptions {
  /** Max root entities to find */
  maxRootEntities?: number;
  /** Max hops for call graph expansion */
  maxCallGraphHops?: number;
  /** Max hops for dependency expansion */
  maxDependencyHops?: number;
  /** Include test files */
  includeTests?: boolean;
}

/**
 * Default options
 */
const DEFAULT_OPTIONS: Required<FeatureContextOptions> = {
  maxRootEntities: 10,
  maxCallGraphHops: 3,
  maxDependencyHops: 2,
  includeTests: false,
};

/**
 * Feature Context Builder
 *
 * Builds comprehensive feature context including:
 * - Root entities (similar code)
 * - Expanded call graphs
 * - Dependency graphs
 * - Affected files
 * - Related PRs
 * - Contributors
 */
export class FeatureContextBuilder {
  private queries: GraphQueries;
  private similaritySearch: SimilaritySearch;
  private logger: Logger;

  constructor(
    conn: KuzuConnection,
    embeddings?: EmbeddingService,
    logger?: Logger
  ) {
    this.queries = new GraphQueries(conn);
    this.similaritySearch = new SimilaritySearch(conn, embeddings, logger);
    this.logger = logger ?? createLogger({ name: 'feature-context' });
  }

  /**
   * Build feature context for a query
   */
  async buildFeatureContext(
    query: string,
    options?: FeatureContextOptions
  ): Promise<FeatureContext> {
    const opts = { ...DEFAULT_OPTIONS, ...options };
    this.logger.info({ query }, 'Building feature context');

    // Find root entities via similarity search
    const rootEntities = await this.similaritySearch.findSimilarCode(query, {
      limit: opts.maxRootEntities,
      entityTypes: ['Function', 'Class'],
    });

    // Build call graph from root entities
    const callGraph = await this.buildCallGraph(rootEntities, opts.maxCallGraphHops);

    // Build dependency graph from root entities
    const dependencyGraph = await this.buildDependencyGraph(
      rootEntities,
      opts.maxDependencyHops
    );

    // Collect all affected files
    const affectedFiles = this.collectAffectedFiles(
      rootEntities,
      callGraph,
      dependencyGraph,
      opts.includeTests
    );

    // Get related PRs (placeholder - needs PR data)
    const relatedPRs: SimilarPR[] = [];

    // Get contributors (placeholder - needs git history)
    const contributors: DomainExpert[] = [];

    const context: FeatureContext = {
      query,
      rootEntities,
      callGraph,
      dependencyGraph,
      affectedFiles,
      relatedPRs,
      contributors,
    };

    this.logger.info(
      {
        rootEntitiesCount: rootEntities.length,
        callGraphSize: callGraph.size,
        dependencyGraphSize: dependencyGraph.size,
        affectedFilesCount: affectedFiles.length,
      },
      'Built feature context'
    );

    return context;
  }

  /**
   * Build call graph from root entities
   */
  private async buildCallGraph(
    rootEntities: SimilarEntity[],
    maxHops: number
  ): Promise<Map<string, string[]>> {
    const callGraph = new Map<string, string[]>();
    const visited = new Set<string>();
    const queue: Array<{ name: string; depth: number }> = [];

    // Initialize queue with root functions
    for (const entity of rootEntities) {
      if (entity.entityType === 'Function') {
        queue.push({ name: entity.qualifiedName, depth: 0 });
      }
    }

    // BFS expansion
    while (queue.length > 0) {
      const { name, depth } = queue.shift()!;

      if (visited.has(name) || depth > maxHops) {
        continue;
      }
      visited.add(name);

      // Get callees
      const callees = await this.queries.getCallGraph(name, 1);
      const calleeNames = callees.map((c) => c.qualifiedName);
      callGraph.set(name, calleeNames);

      // Add callees to queue for next level
      if (depth < maxHops) {
        for (const callee of calleeNames) {
          if (!visited.has(callee)) {
            queue.push({ name: callee, depth: depth + 1 });
          }
        }
      }
    }

    return callGraph;
  }

  /**
   * Build dependency graph from root entities
   */
  private async buildDependencyGraph(
    rootEntities: SimilarEntity[],
    maxHops: number
  ): Promise<Map<string, string[]>> {
    const depGraph = new Map<string, string[]>();
    const visited = new Set<string>();
    const queue: Array<{ path: string; depth: number }> = [];

    // Initialize queue with root files
    const rootFiles = new Set(rootEntities.map((e) => e.filePath));
    for (const path of rootFiles) {
      queue.push({ path, depth: 0 });
    }

    // BFS expansion
    while (queue.length > 0) {
      const { path, depth } = queue.shift()!;

      if (visited.has(path) || depth > maxHops) {
        continue;
      }
      visited.add(path);

      // Get imports for this file
      const imports = await this.queries.getImports(path);
      const importPaths = imports.map((i) => i.importPath).filter(Boolean);
      depGraph.set(path, importPaths);

      // Add imports to queue for next level
      if (depth < maxHops) {
        for (const importPath of importPaths) {
          if (!visited.has(importPath)) {
            queue.push({ path: importPath, depth: depth + 1 });
          }
        }
      }
    }

    return depGraph;
  }

  /**
   * Collect all affected files from graphs
   */
  private collectAffectedFiles(
    rootEntities: SimilarEntity[],
    callGraph: Map<string, string[]>,
    dependencyGraph: Map<string, string[]>,
    includeTests: boolean
  ): string[] {
    const files = new Set<string>();

    // Add root entity files
    for (const entity of rootEntities) {
      files.add(entity.filePath);
    }

    // Add files from call graph
    for (const [caller, callees] of callGraph) {
      // Get file for caller
      // The qualified name format is typically "file:function"
      const callerFile = caller.split(':')[0];
      if (callerFile) {
        files.add(callerFile);
      }
      // Get files for callees
      for (const callee of callees) {
        const calleeFile = callee.split(':')[0];
        if (calleeFile) {
          files.add(calleeFile);
        }
      }
    }

    // Add files from dependency graph
    for (const [file, deps] of dependencyGraph) {
      files.add(file);
      for (const dep of deps) {
        files.add(dep);
      }
    }

    // Filter test files if not included
    let result = Array.from(files);
    if (!includeTests) {
      result = result.filter((f) => !/\.(test|spec)\.[tj]sx?$/.test(f));
    }

    return result.sort();
  }

  /**
   * Get impact analysis for a potential change
   */
  async getImpactAnalysis(
    filePath: string,
    options?: { maxHops?: number }
  ): Promise<{
    directDependents: string[];
    transitiveDependents: string[];
    affectedFunctions: string[];
    riskLevel: 'low' | 'medium' | 'high';
  }> {
    const maxHops = options?.maxHops ?? 3;

    // Get direct dependents (files that import this file)
    // Note: GraphQueries doesn't have getFileDependents, so we use getImporters with file path
    const file = await this.queries.getFile(filePath);
    const directDependents: string[] = [];

    if (file) {
      // Get files that import modules from this file path
      const importers = await this.queries.getImporters(filePath);
      for (const importer of importers) {
        directDependents.push(importer.path);
      }
    }

    // Get transitive dependents
    const transitiveDependents = new Set<string>();
    const queue = [...directDependents];
    const visited = new Set([filePath, ...directDependents]);
    let depth = 1;

    while (queue.length > 0 && depth < maxHops) {
      const batch = queue.splice(0, queue.length);
      for (const dep of batch) {
        const depImporters = await this.queries.getImporters(dep);
        for (const importer of depImporters) {
          if (!visited.has(importer.path)) {
            visited.add(importer.path);
            transitiveDependents.add(importer.path);
            queue.push(importer.path);
          }
        }
      }
      depth++;
    }

    // Get functions in the file
    const functions = await this.queries.getFunctionsInFile(filePath);
    const affectedFunctions = functions.map((f) => f.qualifiedName);

    // Calculate risk level
    const totalDependents = directDependents.length + transitiveDependents.size;
    let riskLevel: 'low' | 'medium' | 'high' = 'low';
    if (totalDependents > 20) {
      riskLevel = 'high';
    } else if (totalDependents > 5) {
      riskLevel = 'medium';
    }

    return {
      directDependents,
      transitiveDependents: Array.from(transitiveDependents),
      affectedFunctions,
      riskLevel,
    };
  }
}

/**
 * Create a feature context builder instance
 */
export function createFeatureContextBuilder(
  conn: KuzuConnection,
  embeddings?: EmbeddingService,
  logger?: Logger
): FeatureContextBuilder {
  return new FeatureContextBuilder(conn, embeddings, logger);
}
