/**
 * Planner Agent
 *
 * Creates research plans for exploring and understanding code.
 */

import { v4 as uuidv4 } from 'uuid';
import type { KuzuConnection } from '../graph/connection.js';
import type { EmbeddingService } from '../embeddings/service.js';
import { AgentAPI } from './agent-api.js';
import type {
  ResearchPlan,
  ResearchQuestion,
  QuestionPriority,
  PlanningContext,
} from './types.js';
import { createLogger, type Logger } from '../utils/logger.js';

/**
 * Planner options
 */
export interface PlannerOptions {
  /** Maximum questions in a plan */
  maxQuestions?: number;
  /** Default budget */
  defaultBudget?: {
    maxToolCalls?: number;
    maxTokens?: number;
    maxTimeMinutes?: number;
  };
}

/**
 * Default options
 */
const DEFAULT_OPTIONS: Required<PlannerOptions> = {
  maxQuestions: 10,
  defaultBudget: {
    maxToolCalls: 50,
    maxTokens: 100000,
    maxTimeMinutes: 30,
  },
};

/**
 * Question template for common research patterns
 */
interface QuestionTemplate {
  pattern: RegExp;
  questions: Array<{
    question: string;
    priority: QuestionPriority;
    rationale: string;
    successCriteria: string[];
    suggestedTools: string[];
  }>;
}

/**
 * Planner Agent
 *
 * Creates structured research plans for:
 * - Understanding features
 * - Debugging issues
 * - Planning changes
 * - Onboarding to codebases
 */
export class PlannerAgent {
  private agentAPI: AgentAPI;
  private logger: Logger;
  private options: Required<PlannerOptions>;

  // Question templates for common patterns
  private templates: QuestionTemplate[] = [
    {
      pattern: /how|what|where|why/i,
      questions: [
        {
          question: 'What are the main components involved?',
          priority: 'P0',
          rationale: 'Understanding components is foundational',
          successCriteria: ['List of components identified', 'Relationships mapped'],
          suggestedTools: ['findSimilar', 'getEntitySummary'],
        },
        {
          question: 'How do they interact?',
          priority: 'P1',
          rationale: 'Interactions reveal architecture',
          successCriteria: ['Call flow documented', 'Data flow understood'],
          suggestedTools: ['getCallees', 'getCallers'],
        },
      ],
    },
    {
      pattern: /implement|add|create|build/i,
      questions: [
        {
          question: 'What existing code is similar?',
          priority: 'P0',
          rationale: 'Similar code provides patterns to follow',
          successCriteria: ['Similar implementations found', 'Patterns identified'],
          suggestedTools: ['findSimilar', 'getPlanningContext'],
        },
        {
          question: 'Where should the new code go?',
          priority: 'P0',
          rationale: 'Location affects architecture',
          successCriteria: ['Directory chosen', 'Fits existing structure'],
          suggestedTools: ['getFileDependencies', 'getFileOverview'],
        },
        {
          question: 'What interfaces need to be implemented?',
          priority: 'P1',
          rationale: 'Interfaces define contracts',
          successCriteria: ['Required interfaces listed', 'Method signatures defined'],
          suggestedTools: ['getEntitySummary', 'getClassHierarchy'],
        },
        {
          question: 'What tests are needed?',
          priority: 'P1',
          rationale: 'Tests ensure correctness',
          successCriteria: ['Test cases outlined', 'Edge cases identified'],
          suggestedTools: ['findSimilar'],
        },
      ],
    },
    {
      pattern: /fix|bug|error|issue|problem/i,
      questions: [
        {
          question: 'Where does the error occur?',
          priority: 'P0',
          rationale: 'Locating the error is step one',
          successCriteria: ['Error location identified', 'Stack trace understood'],
          suggestedTools: ['findSimilar', 'getEntitySummary'],
        },
        {
          question: 'What is the expected behavior?',
          priority: 'P0',
          rationale: 'Need to know the goal',
          successCriteria: ['Expected behavior documented', 'Test cases available'],
          suggestedTools: ['findSimilar'],
        },
        {
          question: 'What calls the buggy code?',
          priority: 'P1',
          rationale: 'Callers may reveal trigger conditions',
          successCriteria: ['Call sites listed', 'Trigger conditions identified'],
          suggestedTools: ['getCallers'],
        },
        {
          question: 'What does the buggy code call?',
          priority: 'P1',
          rationale: 'Dependencies may be the real issue',
          successCriteria: ['Dependencies listed', 'Potential issues flagged'],
          suggestedTools: ['getCallees'],
        },
      ],
    },
    {
      pattern: /refactor|improve|optimize|clean/i,
      questions: [
        {
          question: 'What is the current state of the code?',
          priority: 'P0',
          rationale: 'Understand before changing',
          successCriteria: ['Current behavior documented', 'Pain points identified'],
          suggestedTools: ['getEntitySummary', 'getFileOverview'],
        },
        {
          question: 'What depends on this code?',
          priority: 'P0',
          rationale: 'Changes may break dependents',
          successCriteria: ['All dependents listed', 'Risk assessed'],
          suggestedTools: ['getImpactAnalysis', 'getCallers'],
        },
        {
          question: 'Can changes be made incrementally?',
          priority: 'P1',
          rationale: 'Incremental changes are safer',
          successCriteria: ['Incremental plan created', 'Checkpoints identified'],
          suggestedTools: ['getFileDependencies'],
        },
      ],
    },
  ];

  constructor(
    conn: KuzuConnection,
    embeddings?: EmbeddingService,
    logger?: Logger,
    options?: PlannerOptions
  ) {
    this.agentAPI = new AgentAPI(conn, embeddings, logger);
    this.logger = logger ?? createLogger({ name: 'planner-agent' });
    this.options = { ...DEFAULT_OPTIONS, ...options };
  }

  /**
   * Create a research plan for a goal
   */
  async createResearchPlan(
    goal: string,
    budget?: {
      maxToolCalls?: number;
      maxTokens?: number;
      maxTimeMinutes?: number;
    }
  ): Promise<ResearchPlan> {
    this.logger.info({ goal }, 'Creating research plan');

    // Get planning context for the goal
    const context = await this.agentAPI.getPlanningContext(goal);

    // Generate questions based on goal and context
    const questions = this.generateQuestions(goal, context);

    // Sort by priority
    questions.sort((a, b) => {
      const priorityOrder = { P0: 0, P1: 1, P2: 2 };
      return priorityOrder[a.priority] - priorityOrder[b.priority];
    });

    // Limit questions
    const limitedQuestions = questions.slice(0, this.options.maxQuestions);

    // Generate deliverables
    const deliverables = this.generateDeliverables(goal, limitedQuestions);

    const plan: ResearchPlan = {
      id: uuidv4(),
      goal,
      questions: limitedQuestions,
      deliverables,
      budget: budget ?? this.options.defaultBudget,
      createdAt: new Date(),
    };

    this.logger.info(
      { planId: plan.id, questionCount: plan.questions.length },
      'Created research plan'
    );

    return plan;
  }

  /**
   * Generate questions for a goal
   */
  private generateQuestions(
    goal: string,
    context: PlanningContext
  ): ResearchQuestion[] {
    const questions: ResearchQuestion[] = [];
    const questionIds = new Set<string>();

    // Find matching templates
    for (const template of this.templates) {
      if (template.pattern.test(goal)) {
        for (const q of template.questions) {
          const id = uuidv4().slice(0, 8);
          if (!questionIds.has(q.question)) {
            questionIds.add(q.question);
            questions.push({
              id,
              question: q.question,
              priority: q.priority,
              rationale: q.rationale,
              successCriteria: q.successCriteria,
              suggestedTools: q.suggestedTools,
              dependsOn: [],
            });
          }
        }
      }
    }

    // Add context-specific questions
    const firstSimilar = context.similarCode[0];
    if (firstSimilar) {
      const id = uuidv4().slice(0, 8);
      questions.push({
        id,
        question: `How is ${firstSimilar.qualifiedName} implemented?`,
        priority: 'P1',
        rationale: 'Similar code provides implementation patterns',
        successCriteria: ['Implementation understood', 'Patterns extracted'],
        suggestedTools: ['getEntitySummary', 'getCallees'],
        dependsOn: [],
      });
    }

    const firstEntry = context.entryPoints[0];
    if (firstEntry) {
      const id = uuidv4().slice(0, 8);
      questions.push({
        id,
        question: `What does ${firstEntry} export?`,
        priority: 'P1',
        rationale: 'Entry points define the public API',
        successCriteria: ['Exports listed', 'API surface understood'],
        suggestedTools: ['getFileOverview', 'getFunctionsInFile'],
        dependsOn: [],
      });
    }

    // Add generic questions if we don't have many
    if (questions.length < 3) {
      const genericQuestions: ResearchQuestion[] = [
        {
          id: uuidv4().slice(0, 8),
          question: 'What are the key data structures?',
          priority: 'P1',
          rationale: 'Data structures shape the solution',
          successCriteria: ['Data structures documented'],
          suggestedTools: ['findSimilar'],
          dependsOn: [],
        },
        {
          id: uuidv4().slice(0, 8),
          question: 'What edge cases need handling?',
          priority: 'P2',
          rationale: 'Edge cases ensure robustness',
          successCriteria: ['Edge cases listed'],
          suggestedTools: ['findSimilar'],
          dependsOn: [],
        },
      ];
      questions.push(...genericQuestions);
    }

    // Set up dependencies (P1 depends on P0, P2 depends on P1)
    const p0Ids = questions.filter((q) => q.priority === 'P0').map((q) => q.id);
    const p1Ids = questions.filter((q) => q.priority === 'P1').map((q) => q.id);
    const firstP0 = p0Ids[0];
    const firstP1 = p1Ids[0];

    for (const q of questions) {
      if (q.priority === 'P1' && firstP0) {
        q.dependsOn = [firstP0];
      } else if (q.priority === 'P2' && firstP1) {
        q.dependsOn = [firstP1];
      }
    }

    return questions;
  }

  /**
   * Generate deliverables for a plan
   */
  private generateDeliverables(
    goal: string,
    questions: ResearchQuestion[]
  ): string[] {
    const deliverables: string[] = [];
    const goalLower = goal.toLowerCase();

    // Common deliverables based on goal type
    if (goalLower.includes('implement') || goalLower.includes('add') || goalLower.includes('create')) {
      deliverables.push('Implementation plan with file locations');
      deliverables.push('Interface/type definitions');
      deliverables.push('Test plan');
    } else if (goalLower.includes('fix') || goalLower.includes('bug')) {
      deliverables.push('Root cause analysis');
      deliverables.push('Fix recommendation');
      deliverables.push('Regression test plan');
    } else if (goalLower.includes('refactor') || goalLower.includes('improve')) {
      deliverables.push('Current state documentation');
      deliverables.push('Refactoring plan');
      deliverables.push('Risk assessment');
    } else {
      deliverables.push('Summary document');
      deliverables.push('Key findings');
    }

    // Add deliverable for answering questions
    deliverables.push(`Answers to ${questions.length} research questions`);

    return deliverables;
  }

  /**
   * Estimate effort for a plan
   */
  estimatePlanEffort(plan: ResearchPlan): {
    estimatedToolCalls: number;
    estimatedTokens: number;
    complexity: 'low' | 'medium' | 'high';
  } {
    const questionCount = plan.questions.length;
    const p0Count = plan.questions.filter((q) => q.priority === 'P0').length;
    const p1Count = plan.questions.filter((q) => q.priority === 'P1').length;

    // Estimate tool calls (each question typically needs 2-5 tool calls)
    const estimatedToolCalls = questionCount * 3 + p0Count * 2;

    // Estimate tokens (each tool call ~1000 tokens average)
    const estimatedTokens = estimatedToolCalls * 1000;

    // Complexity based on question count and priorities
    let complexity: 'low' | 'medium' | 'high' = 'low';
    if (questionCount > 7 || p0Count > 3 || p1Count > 5) {
      complexity = 'high';
    } else if (questionCount > 4 || p0Count > 2 || p1Count > 3) {
      complexity = 'medium';
    }

    return {
      estimatedToolCalls,
      estimatedTokens,
      complexity,
    };
  }

  /**
   * Validate a research plan
   */
  validatePlan(plan: ResearchPlan): {
    valid: boolean;
    issues: string[];
  } {
    const issues: string[] = [];

    // Check for P0 questions
    const p0Questions = plan.questions.filter((q) => q.priority === 'P0');
    if (p0Questions.length === 0) {
      issues.push('No P0 (critical) questions defined');
    }

    // Check for circular dependencies
    const visited = new Set<string>();
    const visiting = new Set<string>();

    const hasCycle = (questionId: string): boolean => {
      if (visiting.has(questionId)) return true;
      if (visited.has(questionId)) return false;

      visiting.add(questionId);
      const question = plan.questions.find((q) => q.id === questionId);
      if (question) {
        for (const depId of question.dependsOn) {
          if (hasCycle(depId)) return true;
        }
      }
      visiting.delete(questionId);
      visited.add(questionId);
      return false;
    };

    for (const q of plan.questions) {
      if (hasCycle(q.id)) {
        issues.push(`Circular dependency detected involving question ${q.id}`);
        break;
      }
    }

    // Check budget
    const effort = this.estimatePlanEffort(plan);
    if (plan.budget.maxToolCalls && effort.estimatedToolCalls > plan.budget.maxToolCalls) {
      issues.push(`Estimated tool calls (${effort.estimatedToolCalls}) exceeds budget (${plan.budget.maxToolCalls})`);
    }

    // Check for deliverables
    if (plan.deliverables.length === 0) {
      issues.push('No deliverables defined');
    }

    return {
      valid: issues.length === 0,
      issues,
    };
  }
}

/**
 * Create a planner agent instance
 */
export function createPlannerAgent(
  conn: KuzuConnection,
  embeddings?: EmbeddingService,
  logger?: Logger,
  options?: PlannerOptions
): PlannerAgent {
  return new PlannerAgent(conn, embeddings, logger, options);
}
