/**
 * Graph Query Helpers for Layer A - Code Structure
 *
 * Provides common queries for code navigation and analysis.
 */

import type { KuzuConnection, QueryResultWrapper } from './connection.js';
import type { FileEntity, ClassEntity, FunctionEntity, ModuleEntity } from './types.js';

/**
 * Search result with relevance score
 */
export interface SearchResult<T> {
  entity: T;
  score: number;
}

/**
 * Call graph node
 */
export interface CallGraphNode {
  qualifiedName: string;
  name: string;
  depth: number;
}

/**
 * Inheritance chain node
 */
export interface InheritanceNode {
  qualifiedName: string;
  name: string;
  depth: number;
  isAbstract: boolean;
}

/**
 * Graph Queries for Layer A
 *
 * Common queries for code structure navigation:
 * - Finding entities by name/path
 * - Navigating relationships
 * - Computing call graphs
 * - Analyzing dependencies
 */
export class GraphQueries {
  private conn: KuzuConnection;

  constructor(conn: KuzuConnection) {
    this.conn = conn;
  }

  // ============================================================
  // File Queries
  // ============================================================

  /**
   * Get file by path
   */
  async getFile(path: string): Promise<FileEntity | null> {
    const result = await this.conn.query(
      `MATCH (f:File {path: $path}) RETURN f`,
      { path }
    );
    const record = result.single();
    if (!record) return null;
    return this.mapFileNode(record.f as Record<string, unknown>);
  }

  /**
   * Find files by name pattern
   */
  async findFilesByName(pattern: string): Promise<FileEntity[]> {
    const result = await this.conn.query(
      `MATCH (f:File) WHERE f.name CONTAINS $pattern RETURN f ORDER BY f.name`,
      { pattern }
    );
    return result.all().map((r) => this.mapFileNode(r.f as Record<string, unknown>));
  }

  /**
   * Find files by extension
   */
  async findFilesByExtension(extension: string): Promise<FileEntity[]> {
    const result = await this.conn.query(
      `MATCH (f:File {extension: $extension}) RETURN f ORDER BY f.path`,
      { extension }
    );
    return result.all().map((r) => this.mapFileNode(r.f as Record<string, unknown>));
  }

  /**
   * Get all files
   */
  async getAllFiles(): Promise<FileEntity[]> {
    const result = await this.conn.query('MATCH (f:File) RETURN f ORDER BY f.path');
    return result.all().map((r) => this.mapFileNode(r.f as Record<string, unknown>));
  }

  // ============================================================
  // Class Queries
  // ============================================================

  /**
   * Get class by qualified name
   */
  async getClass(qualifiedName: string): Promise<ClassEntity | null> {
    const result = await this.conn.query(
      `MATCH (c:Class {qualified_name: $name}) RETURN c`,
      { name: qualifiedName }
    );
    const record = result.single();
    if (!record) return null;
    return this.mapClassNode(record.c as Record<string, unknown>);
  }

  /**
   * Find classes by name pattern
   */
  async findClassesByName(pattern: string): Promise<ClassEntity[]> {
    const result = await this.conn.query(
      `MATCH (c:Class) WHERE c.name CONTAINS $pattern RETURN c ORDER BY c.name`,
      { pattern }
    );
    return result.all().map((r) => this.mapClassNode(r.c as Record<string, unknown>));
  }

  /**
   * Get classes in a file
   */
  async getClassesInFile(filePath: string): Promise<ClassEntity[]> {
    const result = await this.conn.query(
      `MATCH (f:File {path: $path})-[:CONTAINS_CLASS]->(c:Class) RETURN c ORDER BY c.line_start`,
      { path: filePath }
    );
    return result.all().map((r) => this.mapClassNode(r.c as Record<string, unknown>));
  }

  /**
   * Get class hierarchy (parents and children)
   */
  async getClassHierarchy(qualifiedName: string): Promise<{
    parents: InheritanceNode[];
    children: InheritanceNode[];
  }> {
    // Get parent classes
    const parentsResult = await this.conn.query(
      `
      MATCH path = (c:Class {qualified_name: $name})-[:INHERITS_FROM*1..10]->(parent:Class)
      RETURN parent.qualified_name AS qualified_name,
             parent.name AS name,
             parent.is_abstract AS is_abstract,
             length(path) AS depth
      ORDER BY depth
      `,
      { name: qualifiedName }
    );

    const parents: InheritanceNode[] = parentsResult.all().map((r) => ({
      qualifiedName: r.qualified_name as string,
      name: r.name as string,
      isAbstract: r.is_abstract as boolean,
      depth: r.depth as number,
    }));

    // Get child classes
    const childrenResult = await this.conn.query(
      `
      MATCH path = (child:Class)-[:INHERITS_FROM*1..10]->(c:Class {qualified_name: $name})
      RETURN child.qualified_name AS qualified_name,
             child.name AS name,
             child.is_abstract AS is_abstract,
             length(path) AS depth
      ORDER BY depth
      `,
      { name: qualifiedName }
    );

    const children: InheritanceNode[] = childrenResult.all().map((r) => ({
      qualifiedName: r.qualified_name as string,
      name: r.name as string,
      isAbstract: r.is_abstract as boolean,
      depth: r.depth as number,
    }));

    return { parents, children };
  }

  // ============================================================
  // Function Queries
  // ============================================================

  /**
   * Get function by qualified name
   */
  async getFunction(qualifiedName: string): Promise<FunctionEntity | null> {
    const result = await this.conn.query(
      `MATCH (fn:Function {qualified_name: $name}) RETURN fn`,
      { name: qualifiedName }
    );
    const record = result.single();
    if (!record) return null;
    return this.mapFunctionNode(record.fn as Record<string, unknown>);
  }

  /**
   * Find functions by name pattern
   */
  async findFunctionsByName(pattern: string): Promise<FunctionEntity[]> {
    const result = await this.conn.query(
      `MATCH (fn:Function) WHERE fn.name CONTAINS $pattern RETURN fn ORDER BY fn.name`,
      { pattern }
    );
    return result.all().map((r) => this.mapFunctionNode(r.fn as Record<string, unknown>));
  }

  /**
   * Get functions in a file
   */
  async getFunctionsInFile(filePath: string): Promise<FunctionEntity[]> {
    const result = await this.conn.query(
      `MATCH (f:File {path: $path})-[:CONTAINS_FUNCTION]->(fn:Function) RETURN fn ORDER BY fn.line_start`,
      { path: filePath }
    );
    return result.all().map((r) => this.mapFunctionNode(r.fn as Record<string, unknown>));
  }

  /**
   * Get methods of a class
   */
  async getMethodsOfClass(classQualifiedName: string): Promise<FunctionEntity[]> {
    const result = await this.conn.query(
      `MATCH (c:Class {qualified_name: $name})-[:HAS_METHOD]->(fn:Function) RETURN fn ORDER BY fn.line_start`,
      { name: classQualifiedName }
    );
    return result.all().map((r) => this.mapFunctionNode(r.fn as Record<string, unknown>));
  }

  /**
   * Get call graph for a function (outgoing calls)
   */
  async getCallGraph(qualifiedName: string, maxDepth: number = 3): Promise<CallGraphNode[]> {
    const result = await this.conn.query(
      `
      MATCH path = (fn:Function {qualified_name: $name})-[:CALLS*1..${maxDepth}]->(called:Function)
      RETURN called.qualified_name AS qualified_name,
             called.name AS name,
             length(path) AS depth
      ORDER BY depth, name
      `,
      { name: qualifiedName }
    );

    return result.all().map((r) => ({
      qualifiedName: r.qualified_name as string,
      name: r.name as string,
      depth: r.depth as number,
    }));
  }

  /**
   * Get callers of a function (incoming calls)
   */
  async getCallers(qualifiedName: string, maxDepth: number = 3): Promise<CallGraphNode[]> {
    const result = await this.conn.query(
      `
      MATCH path = (caller:Function)-[:CALLS*1..${maxDepth}]->(fn:Function {qualified_name: $name})
      RETURN caller.qualified_name AS qualified_name,
             caller.name AS name,
             length(path) AS depth
      ORDER BY depth, name
      `,
      { name: qualifiedName }
    );

    return result.all().map((r) => ({
      qualifiedName: r.qualified_name as string,
      name: r.name as string,
      depth: r.depth as number,
    }));
  }

  /**
   * Find complex functions by cyclomatic complexity
   */
  async findComplexFunctions(minComplexity: number): Promise<FunctionEntity[]> {
    const result = await this.conn.query(
      `
      MATCH (fn:Function)
      WHERE fn.cyclomatic_complexity >= $min
      RETURN fn
      ORDER BY fn.cyclomatic_complexity DESC
      `,
      { min: minComplexity }
    );
    return result.all().map((r) => this.mapFunctionNode(r.fn as Record<string, unknown>));
  }

  // ============================================================
  // Module/Import Queries
  // ============================================================

  /**
   * Get module by name
   */
  async getModule(name: string): Promise<ModuleEntity | null> {
    const result = await this.conn.query(
      `MATCH (m:Module {name: $name}) RETURN m`,
      { name }
    );
    const record = result.single();
    if (!record) return null;
    return this.mapModuleNode(record.m as Record<string, unknown>);
  }

  /**
   * Get files that import a module
   */
  async getImporters(moduleName: string): Promise<FileEntity[]> {
    const result = await this.conn.query(
      `MATCH (f:File)-[:IMPORTS]->(m:Module {name: $name}) RETURN f ORDER BY f.path`,
      { name: moduleName }
    );
    return result.all().map((r) => this.mapFileNode(r.f as Record<string, unknown>));
  }

  /**
   * Get modules imported by a file
   */
  async getImports(filePath: string): Promise<ModuleEntity[]> {
    const result = await this.conn.query(
      `MATCH (f:File {path: $path})-[:IMPORTS]->(m:Module) RETURN m ORDER BY m.name`,
      { path: filePath }
    );
    return result.all().map((r) => this.mapModuleNode(r.m as Record<string, unknown>));
  }

  /**
   * Get external dependencies
   */
  async getExternalDependencies(): Promise<ModuleEntity[]> {
    const result = await this.conn.query(
      `MATCH (m:Module {is_external: true}) RETURN m ORDER BY m.package, m.name`
    );
    return result.all().map((r) => this.mapModuleNode(r.m as Record<string, unknown>));
  }

  // ============================================================
  // Semantic Search (requires embeddings)
  // ============================================================

  /**
   * Search classes by embedding similarity
   */
  async searchClassesBySimilarity(
    embedding: number[],
    limit: number = 10
  ): Promise<SearchResult<ClassEntity>[]> {
    // Kuzu doesn't have native vector similarity yet, so we compute in application
    const result = await this.conn.query(
      `MATCH (c:Class) WHERE size(c.embedding) > 0 RETURN c`
    );

    const classes = result.all().map((r) => ({
      entity: this.mapClassNode(r.c as Record<string, unknown>),
      embedding: (r.c as Record<string, unknown>).embedding as number[],
    }));

    // Compute cosine similarity
    const scored = classes
      .map(({ entity, embedding: classEmb }) => ({
        entity,
        score: this.cosineSimilarity(embedding, classEmb),
      }))
      .sort((a, b) => b.score - a.score)
      .slice(0, limit);

    return scored;
  }

  /**
   * Search functions by embedding similarity
   */
  async searchFunctionsBySimilarity(
    embedding: number[],
    limit: number = 10
  ): Promise<SearchResult<FunctionEntity>[]> {
    const result = await this.conn.query(
      `MATCH (fn:Function) WHERE size(fn.embedding) > 0 RETURN fn`
    );

    const functions = result.all().map((r) => ({
      entity: this.mapFunctionNode(r.fn as Record<string, unknown>),
      embedding: (r.fn as Record<string, unknown>).embedding as number[],
    }));

    const scored = functions
      .map(({ entity, embedding: fnEmb }) => ({
        entity,
        score: this.cosineSimilarity(embedding, fnEmb),
      }))
      .sort((a, b) => b.score - a.score)
      .slice(0, limit);

    return scored;
  }

  // ============================================================
  // Analysis Queries
  // ============================================================

  /**
   * Get orphan classes (no inheritance relationships)
   */
  async getOrphanClasses(): Promise<ClassEntity[]> {
    const result = await this.conn.query(`
      MATCH (c:Class)
      WHERE NOT (c)-[:INHERITS_FROM]->() AND NOT ()-[:INHERITS_FROM]->(c)
      RETURN c
      ORDER BY c.name
    `);
    return result.all().map((r) => this.mapClassNode(r.c as Record<string, unknown>));
  }

  /**
   * Get dead code (functions with no callers)
   */
  async getDeadCode(): Promise<FunctionEntity[]> {
    const result = await this.conn.query(`
      MATCH (fn:Function)
      WHERE fn.is_method = false AND NOT ()-[:CALLS]->(fn)
      RETURN fn
      ORDER BY fn.name
    `);
    return result.all().map((r) => this.mapFunctionNode(r.fn as Record<string, unknown>));
  }

  /**
   * Get file statistics
   */
  async getFileStats(): Promise<{
    totalFiles: number;
    totalLines: number;
    averageLines: number;
    byExtension: Record<string, number>;
  }> {
    const countResult = await this.conn.query(`
      MATCH (f:File)
      RETURN count(f) AS total, sum(f.lines_of_code) AS lines
    `);
    const counts = countResult.single();

    const extResult = await this.conn.query(`
      MATCH (f:File)
      RETURN f.extension AS ext, count(f) AS count
      ORDER BY count DESC
    `);

    const byExtension: Record<string, number> = {};
    for (const row of extResult.all()) {
      byExtension[row.ext as string] = row.count as number;
    }

    const totalFiles = (counts?.total as number) ?? 0;
    const totalLines = (counts?.lines as number) ?? 0;

    return {
      totalFiles,
      totalLines,
      averageLines: totalFiles > 0 ? Math.round(totalLines / totalFiles) : 0,
      byExtension,
    };
  }

  // ============================================================
  // Helper Methods
  // ============================================================

  private mapFileNode(node: Record<string, unknown>): FileEntity {
    return {
      path: node.path as string,
      name: node.name as string,
      extension: node.extension as string,
      sizeBytes: node.size_bytes as number,
      linesOfCode: node.lines_of_code as number,
      hash: node.hash as string,
      lastModified: new Date(node.last_modified as string),
    };
  }

  private mapClassNode(node: Record<string, unknown>): ClassEntity {
    return {
      qualifiedName: node.qualified_name as string,
      name: node.name as string,
      filePath: node.file_path as string,
      lineStart: node.line_start as number,
      lineEnd: node.line_end as number,
      docstring: (node.docstring as string) || undefined,
      isAbstract: node.is_abstract as boolean,
      decorators: node.decorators as string[],
      baseClasses: node.base_classes as string[],
      embedding: (node.embedding as number[]) || undefined,
    };
  }

  private mapFunctionNode(node: Record<string, unknown>): FunctionEntity {
    return {
      qualifiedName: node.qualified_name as string,
      name: node.name as string,
      filePath: node.file_path as string,
      lineStart: node.line_start as number,
      lineEnd: node.line_end as number,
      docstring: (node.docstring as string) || undefined,
      parameters: node.parameters as string[],
      returnAnnotation: (node.return_annotation as string) || undefined,
      isAsync: node.is_async as boolean,
      isMethod: node.is_method as boolean,
      isStatic: node.is_static as boolean,
      decorators: node.decorators as string[],
      calls: node.calls as string[],
      parentClass: (node.parent_class as string) || undefined,
      cyclomaticComplexity: (node.cyclomatic_complexity as number) || undefined,
      embedding: (node.embedding as number[]) || undefined,
    };
  }

  private mapModuleNode(node: Record<string, unknown>): ModuleEntity {
    return {
      name: node.name as string,
      importPath: node.import_path as string,
      isExternal: node.is_external as boolean,
      package: (node.package as string) || undefined,
    };
  }

  private cosineSimilarity(a: number[], b: number[]): number {
    if (a.length !== b.length) return 0;

    let dotProduct = 0;
    let normA = 0;
    let normB = 0;

    for (let i = 0; i < a.length; i++) {
      dotProduct += a[i] * b[i];
      normA += a[i] * a[i];
      normB += b[i] * b[i];
    }

    const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
    return magnitude === 0 ? 0 : dotProduct / magnitude;
  }
}
