/**
 * Plan Mode System
 *
 * Manages the agent's planning mode state and provides tools
 * for entering/exiting plan mode and writing plans.
 */

import { promises as fs } from 'fs';
import * as path from 'path';
import { AgentMode, type PlanModeState } from './types.js';
import { createLogger, type Logger } from '../utils/logger.js';

/**
 * Default plan file name
 */
const DEFAULT_PLAN_FILE = 'PLAN.md';

/**
 * Plan template
 */
const PLAN_TEMPLATE = `# Implementation Plan

## Goal
<!-- Describe the goal here -->

## Context
<!-- Key findings from research -->

## Approach
<!-- High-level approach -->

## Tasks
<!-- Ordered list of tasks -->
1.
2.
3.

## Files to Modify
<!-- List of files that will be changed -->

## Testing Strategy
<!-- How to test the changes -->

## Risks
<!-- Potential issues and mitigations -->

## Questions
<!-- Outstanding questions -->
`;

/**
 * Mode State Manager
 *
 * Singleton that manages the agent's current mode (code/plan)
 * and persists plan content to files.
 */
export class ModeStateManager {
  private static instance: ModeStateManager;
  private state: PlanModeState;
  private logger: Logger;

  private constructor(logger?: Logger) {
    this.state = {
      mode: AgentMode.CODE,
      planSubmitted: false,
    };
    this.logger = logger ?? createLogger({ name: 'mode-state' });
  }

  /**
   * Get the singleton instance
   */
  static getInstance(logger?: Logger): ModeStateManager {
    if (!ModeStateManager.instance) {
      ModeStateManager.instance = new ModeStateManager(logger);
    }
    return ModeStateManager.instance;
  }

  /**
   * Reset the singleton (for testing)
   */
  static reset(): void {
    ModeStateManager.instance = undefined as any;
  }

  /**
   * Get current state
   */
  getState(): PlanModeState {
    return { ...this.state };
  }

  /**
   * Get current mode
   */
  getMode(): AgentMode {
    return this.state.mode;
  }

  /**
   * Check if in plan mode
   */
  isInPlanMode(): boolean {
    return this.state.mode === AgentMode.PLAN;
  }

  /**
   * Enter plan mode
   */
  async enterPlanMode(workDir: string): Promise<PlanModeState> {
    if (this.state.mode === AgentMode.PLAN) {
      this.logger.warn('Already in plan mode');
      return this.getState();
    }

    const planPath = path.join(workDir, DEFAULT_PLAN_FILE);

    // Check if plan file exists
    let planContent = PLAN_TEMPLATE;
    try {
      const existing = await fs.readFile(planPath, 'utf-8');
      planContent = existing;
      this.logger.info({ planPath }, 'Loaded existing plan');
    } catch {
      // File doesn't exist, use template
      this.logger.info({ planPath }, 'Creating new plan');
    }

    this.state = {
      mode: AgentMode.PLAN,
      planContent,
      planPath,
      planSubmitted: false,
    };

    this.logger.info({ planPath }, 'Entered plan mode');
    return this.getState();
  }

  /**
   * Exit plan mode
   */
  exitPlanMode(): PlanModeState {
    if (this.state.mode === AgentMode.CODE) {
      this.logger.warn('Already in code mode');
      return this.getState();
    }

    this.state = {
      mode: AgentMode.CODE,
      planSubmitted: false,
    };

    this.logger.info('Exited plan mode');
    return this.getState();
  }

  /**
   * Write plan content
   */
  async writePlan(content: string): Promise<void> {
    if (this.state.mode !== AgentMode.PLAN) {
      throw new Error('Not in plan mode');
    }

    if (!this.state.planPath) {
      throw new Error('Plan path not set');
    }

    this.state.planContent = content;
    await fs.writeFile(this.state.planPath, content, 'utf-8');
    this.logger.info({ planPath: this.state.planPath }, 'Wrote plan');
  }

  /**
   * Submit plan for approval
   */
  submitPlan(): void {
    if (this.state.mode !== AgentMode.PLAN) {
      throw new Error('Not in plan mode');
    }

    if (!this.state.planContent) {
      throw new Error('No plan content to submit');
    }

    this.state.planSubmitted = true;
    this.logger.info('Plan submitted for approval');
  }

  /**
   * Reject plan with feedback
   */
  rejectPlan(feedback: string): void {
    if (this.state.mode !== AgentMode.PLAN) {
      throw new Error('Not in plan mode');
    }

    this.state.planSubmitted = false;
    this.state.rejectionFeedback = feedback;
    this.logger.info({ feedback }, 'Plan rejected');
  }

  /**
   * Approve plan and exit to code mode
   */
  approvePlan(): PlanModeState {
    if (this.state.mode !== AgentMode.PLAN) {
      throw new Error('Not in plan mode');
    }

    if (!this.state.planSubmitted) {
      throw new Error('Plan not submitted');
    }

    const finalState = this.getState();
    this.state = {
      mode: AgentMode.CODE,
      planSubmitted: false,
    };

    this.logger.info('Plan approved, switched to code mode');
    return finalState;
  }

  /**
   * Get plan content
   */
  getPlanContent(): string | undefined {
    return this.state.planContent;
  }

  /**
   * Get plan path
   */
  getPlanPath(): string | undefined {
    return this.state.planPath;
  }
}

/**
 * Tool definitions for plan mode
 */
export interface PlanModeTool {
  name: string;
  description: string;
  parameters: Record<string, unknown>;
  execute: (params: Record<string, unknown>) => Promise<unknown>;
}

/**
 * Create EnterPlanMode tool
 */
export function createEnterPlanModeTool(manager: ModeStateManager): PlanModeTool {
  return {
    name: 'enter_plan_mode',
    description: 'Enter planning mode to create an implementation plan before writing code',
    parameters: {
      type: 'object',
      properties: {
        work_dir: {
          type: 'string',
          description: 'Working directory for the plan file',
        },
      },
      required: ['work_dir'],
    },
    execute: async (params) => {
      const workDir = params.work_dir as string;
      const state = await manager.enterPlanMode(workDir);
      return {
        mode: state.mode,
        planPath: state.planPath,
        message: 'Entered plan mode. Use write_plan to write your plan.',
      };
    },
  };
}

/**
 * Create ExitPlanMode tool
 */
export function createExitPlanModeTool(manager: ModeStateManager): PlanModeTool {
  return {
    name: 'exit_plan_mode',
    description: 'Exit planning mode and return to code mode',
    parameters: {
      type: 'object',
      properties: {},
      required: [],
    },
    execute: async () => {
      const state = manager.exitPlanMode();
      return {
        mode: state.mode,
        message: 'Exited plan mode.',
      };
    },
  };
}

/**
 * Create WritePlan tool
 */
export function createWritePlanTool(manager: ModeStateManager): PlanModeTool {
  return {
    name: 'write_plan',
    description: 'Write or update the implementation plan',
    parameters: {
      type: 'object',
      properties: {
        content: {
          type: 'string',
          description: 'The plan content in markdown format',
        },
      },
      required: ['content'],
    },
    execute: async (params) => {
      const content = params.content as string;
      await manager.writePlan(content);
      return {
        planPath: manager.getPlanPath(),
        message: 'Plan written successfully.',
      };
    },
  };
}

/**
 * Create SubmitPlan tool
 */
export function createSubmitPlanTool(manager: ModeStateManager): PlanModeTool {
  return {
    name: 'submit_plan',
    description: 'Submit the plan for user approval',
    parameters: {
      type: 'object',
      properties: {},
      required: [],
    },
    execute: async () => {
      manager.submitPlan();
      return {
        planPath: manager.getPlanPath(),
        planContent: manager.getPlanContent(),
        message: 'Plan submitted for approval. Waiting for user review.',
      };
    },
  };
}

/**
 * Get all plan mode tools
 */
export function getPlanModeTools(manager: ModeStateManager): PlanModeTool[] {
  return [
    createEnterPlanModeTool(manager),
    createExitPlanModeTool(manager),
    createWritePlanTool(manager),
    createSubmitPlanTool(manager),
  ];
}

/**
 * Get the mode state manager instance
 */
export function getModeStateManager(logger?: Logger): ModeStateManager {
  return ModeStateManager.getInstance(logger);
}
