/**
 * Explored Areas Provider
 *
 * Extracts context from agent exploration steps.
 * Scores entities based on which tools discovered them.
 */

import { ContextProvider } from './base.js';
import type { ContextItem, ContextProviderSpec, ExplorationStep } from '../types.js';
import { calculateToolScore, isSearchTool } from '../tool-relevance.js';

/**
 * Explored Areas Provider
 *
 * Tracks what entities the agent discovered during exploration.
 * Tool relevance hierarchy determines scoring.
 */
export class ExploredAreasProvider extends ContextProvider {
  get spec(): ContextProviderSpec {
    return {
      name: 'explored_areas',
      description: 'Extracts context from agent exploration steps with tool-based scoring',
      requiresGraph: false,
    };
  }

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

    // Track best score per entity (same entity may be discovered by multiple tools)
    const entityScores = new Map<string, number>();
    const entityInfo = new Map<string, { toolName: string; position?: number }>();

    for (const step of steps) {
      const { toolName, entitiesDiscovered, resultPositions } = step;

      for (let i = 0; i < entitiesDiscovered.length; i++) {
        const entity = entitiesDiscovered[i];

        // Get position for search tools
        let position: number | undefined;
        if (isSearchTool(toolName)) {
          position = resultPositions?.get(entity) ?? i;
        }

        const score = calculateToolScore(toolName, position);

        // Keep best score
        const existingScore = entityScores.get(entity) ?? 0;
        if (score > existingScore) {
          entityScores.set(entity, score);
          entityInfo.set(entity, { toolName, position });
        }
      }
    }

    // Convert to context items
    const now = new Date();
    const items: ContextItem[] = [];

    for (const [qualifiedName, score] of entityScores) {
      // Infer entity type from qualified name
      const entityType = this.inferEntityType(qualifiedName);

      items.push({
        qualifiedName,
        entityType,
        score,
        touchCount: 1,
        lastTouched: now,
        neighbors: [],
      });
    }

    // If we have a graph connection, fetch neighbors
    if (this.conn) {
      await this.enrichWithNeighbors(items);
    }

    return items;
  }

  /**
   * Infer entity type from qualified name
   */
  private inferEntityType(qualifiedName: string): 'Function' | 'Class' | 'File' {
    // If it has a file extension, it's a file
    if (/\.[a-z]+$/i.test(qualifiedName)) {
      return 'File';
    }

    // If it starts with uppercase, likely a class
    const lastPart = qualifiedName.split('.').pop() ?? '';
    if (lastPart[0] && lastPart[0] === lastPart[0].toUpperCase() && lastPart[0] !== lastPart[0].toLowerCase()) {
      // Could be class or constructor/method - if it's PascalCase with no camelCase suffix
      if (!/[a-z][A-Z]/.test(lastPart.slice(1))) {
        return 'Class';
      }
    }

    // Default to function
    return 'Function';
  }

  /**
   * Enrich items with graph neighbors
   */
  private async enrichWithNeighbors(items: ContextItem[]): Promise<void> {
    if (!this.conn) return;

    for (const item of items) {
      try {
        let neighbors: string[] = [];

        if (item.entityType === 'Function') {
          // Get callers and callees
          const result = await this.conn.query(`
            MATCH (f:Function {qualified_name: $name})-[:CALLS]->(callee:Function)
            RETURN callee.qualified_name AS name
            UNION
            MATCH (caller:Function)-[:CALLS]->(f:Function {qualified_name: $name})
            RETURN caller.qualified_name AS name
            LIMIT 10
          `, { name: item.qualifiedName });

          neighbors = result.all().map((r) => r.name as string);
        } else if (item.entityType === 'Class') {
          // Get methods and parent/child classes
          const result = await this.conn.query(`
            MATCH (c:Class {qualified_name: $name})-[:HAS_METHOD]->(m:Function)
            RETURN m.qualified_name AS name
            UNION
            MATCH (c:Class {qualified_name: $name})-[:INHERITS_FROM]->(parent:Class)
            RETURN parent.qualified_name AS name
            UNION
            MATCH (child:Class)-[:INHERITS_FROM]->(c:Class {qualified_name: $name})
            RETURN child.qualified_name AS name
            LIMIT 10
          `, { name: item.qualifiedName });

          neighbors = result.all().map((r) => r.name as string);
        }

        item.neighbors = neighbors;
      } catch {
        // Ignore query errors
      }
    }
  }
}
