/**
 * CLIService - A thin wrapper around the API
 * 
 * This service provides backward compatibility with code that depends on CLIService
 * but delegates the actual processing to the API.
 */

import { main as apiMain } from '@api/index.js';
import { cliLogger as logger } from '@core/utils/logger.js';
import { MeldError, ErrorSeverity } from '@core/errors/MeldError.js';
import { version } from '@core/version.js';
import { createInterface } from 'readline';
import { dirname, basename, extname } from 'path';
import { join } from 'path';
import { IParserService } from '@services/parser/ParserService/IParserService.js';
import { IInterpreterService } from '@services/pipeline/InterpreterService/IInterpreterService.js';
import { IOutputService } from '@services/output/OutputService/IOutputService.js';
import { IFileSystemService } from '@services/fs/FileSystemService/IFileSystemService.js';
import { IPathService } from '@services/fs/PathService/IPathService.js';
import { IStateService } from '@services/state/StateService/IStateService.js';
import { ProcessOptions } from '@api/types.js';
import readline from 'readline';

/**
 * Interface for a service that handles user prompts
 */
export interface IPromptService {
  /**
   * Gets text input from the user
   * @param prompt The prompt to display to the user
   * @param defaultValue Optional default value to use if the user presses Enter without input
   * @returns The user's input
   */
  getText(prompt: string, defaultValue?: string): Promise<string>;
}

export interface CLIOptions {
  input?: string;
  output?: string;
  format?: 'xml' | 'llmxml' | 'markdown' | 'md';
  strict?: boolean;
  stdout?: boolean;
  version?: boolean;
  verbose?: boolean;
  homePath?: string;
  debug?: boolean;
  help?: boolean;
}

export interface ICLIService {
  run(args: string[]): Promise<void>;
}

export class CLIService implements ICLIService {
  private parserService: IParserService;
  private interpreterService: IInterpreterService;
  private outputService: IOutputService;
  private fileSystemService: IFileSystemService;
  private pathService: IPathService;
  private stateService: IStateService;
  private promptService: IPromptService;
  private flags: Record<string, string | boolean | undefined> = {};
  private cmdOptions: ProcessOptions = {
    output: ''
  };

  constructor(
    private parserService: IParserService,
    private interpreterService: IInterpreterService,
    private outputService: IOutputService,
    private fileSystemService: IFileSystemService,
    private pathService: IPathService,
    private stateService: IStateService,
    promptService?: IPromptService
  ) {
    this.parserService = parserService;
    this.interpreterService = interpreterService;
    this.outputService = outputService;
    this.fileSystemService = fileSystemService;
    this.pathService = pathService;
    this.stateService = stateService;
    
    // Use the provided prompt service or create a default one
    this.promptService = promptService || {
      getText: async (prompt: string, defaultValue?: string): Promise<string> => {
        return new Promise((resolve) => {
          const rl = readline.createInterface({
            input: process.stdin,
            output: process.stdout
          });
          
          rl.question(prompt, (answer) => {
            rl.close();
            // If the user just pressed Enter and we have a default value, use that
            resolve(answer.trim() || defaultValue || '');
          });
        });
      }
    };
  }

  private normalizeFormat(format: string): 'markdown' | 'xml' {
    format = format.toLowerCase();
    switch (format) {
      case 'markdown':
      case 'md':
        return 'markdown';
      case 'xml':
      default:
        return 'markdown';
    }
  }

  private getOutputExtension(format: string): string {
    switch (format.toLowerCase()) {
      case 'markdown':
      case 'md':
        return '.md';
      case 'xml':
        return '.xml';
      default:
        return '.xml'; // Default to XML
    }
  }

  /**
   * Process CLI arguments
   */
  parseArguments(args: string[]): CLIOptions {
    const options: CLIOptions = {
      format: 'xml',
      strict: false
    };

    // Skip 'node' and 'meld' executable names if present at the beginning
    let startIndex = 0;
    if (args.length > 0 && (args[0] === 'node' || args[0] === 'meld')) {
      startIndex = 1;
      // If second arg is 'meld', skip that too (handles both 'node meld' and just 'meld')
      if (args.length > 1 && args[1] === 'meld') {
        startIndex = 2;
      }
    }

    // Process all arguments starting from the appropriate index
    for (let i = startIndex; i < args.length; i++) {
      const arg = args[i];
      
      switch (arg) {
        case '--version':
        case '-V':
          options.version = true;
          break;
        case '--output':
        case '-o':
          options.output = args[++i];
          break;
        case '--format':
        case '-f':
          options.format = this.normalizeFormat(args[++i]);
          break;
        case '--stdout':
          options.stdout = true;
          break;
        case '--verbose':
        case '-v':
          options.verbose = true;
          break;
        case '--strict':
          options.strict = true;
          break;
        case '--permissive':
          options.strict = false;
          break;
        case '--home-path':
          options.homePath = args[++i];
          break;
        case '--debug':
        case '-d':
          options.debug = true;
          break;
        case '--help':
        case '-h':
          options.help = true;
          break;
        default:
          if (!arg.startsWith('-') && !options.input) {
            options.input = arg;
          } else {
            throw new Error(`Unknown option: ${arg}`);
          }
      }
    }

    if (!options.input && !options.version) {
      throw new Error('No input file specified');
    }

    return options;
  }

  /**
   * Convert CLI options to API options
   */
  private cliToApiOptions(cliOptions: CLIOptions): ProcessOptions {
    return {
      format: cliOptions.format,
      debug: cliOptions.debug,
      strict: cliOptions.strict,
      transformation: true, // Enable transformation by default for CLI usage
      fs: this.fileSystemService.getFileSystem()
    };
  }

  /**
   * Confirms whether a file should be overwritten
   */
  async confirmOverwrite(outputPath: string): Promise<{ outputPath: string; shouldOverwrite: boolean }> {
    this.debug(`confirmOverwrite: ${outputPath}`);
    
    // Check if file exists
    const exists = await this.fileSystemService.exists(outputPath);
    if (!exists) {
      this.debug(`confirmOverwrite: file does not exist, no need to overwrite`);
      return { outputPath, shouldOverwrite: true };
    }
    
    // Prompt for overwrite
    const response = await this.promptService.getText(
      `File ${outputPath} already exists. Overwrite? [Y/n] `, 
      'y'
    );
    
    this.debug(`confirmOverwrite: user response: ${response}`);
    
    if (response.toLowerCase() === 'n') {
      this.debug('confirmOverwrite: user declined overwrite');
      return this.findAvailableIncrementalFilename(outputPath);
    }
    
    return { outputPath, shouldOverwrite: true };
  }

  /**
   * Finds an available incremental filename (file-1.ext, file-2.ext, etc.)
   * @param outputPath The original output path
   * @returns An object with the available output path and shouldOverwrite=true
   */
  private async findAvailableIncrementalFilename(outputPath: string): Promise<{ outputPath: string; shouldOverwrite: boolean }> {
    console.log('Finding incremental filename for:', outputPath);
    const ext = extname(outputPath); // Use Node.js path module
    console.log('Extension:', ext);
    const basePath = outputPath.slice(0, -ext.length);
    console.log('Base path:', basePath);
    let counter = 1;
    let newPath = `${basePath}-${counter}${ext}`;
    console.log('Trying path:', newPath);
    
    while (await this.fileSystemService.exists(newPath)) {
      console.log(`Path ${newPath} exists, incrementing counter`);
      counter++;
      newPath = `${basePath}-${counter}${ext}`;
      console.log('Trying next path:', newPath);
    }
    
    console.log('Found available path:', newPath);
    logger.info(`Using incremental filename: ${newPath}`);
    return { outputPath: newPath, shouldOverwrite: true };
  }

  /**
   * Run the CLI with the given arguments
   */
  public async run(args: string[]): Promise<void> {
    try {
      // Parse command line arguments
      const options = this.parseArguments(args);

      // Handle special commands
      if (options.version) {
        console.log(`meld version ${version}`);
        return;
      }

      if (options.help) {
        this.showHelp();
        return;
      }

      // Set up environment paths
      const state = this.stateService.createChildState();
      
      // Set up project path
      const projectPath = await this.pathService.resolveProjectPath();
      state.setPathVar('PROJECTPATH', projectPath);
      state.setPathVar('.', projectPath);
      
      // Set up home path if specified
      if (options.homePath) {
        state.setPathVar('HOMEPATH', options.homePath);
        state.setPathVar('~', options.homePath);
      }

      // Configure logging based on options
      if (options.verbose) {
        logger.level = 'debug';
      } else if (options.debug) {
        logger.level = 'trace';
      } else {
        logger.level = 'info';
      }

      logger.info('Starting Meld CLI', {
        version,
        options
      });

      // Remove watch check and directly process the file
      await this.processFile(options);
    } catch (error) {
      // For CLI errors, always log and exit with error code
      logger.error('Error running Meld CLI', {
        error: error instanceof Error ? error.message : String(error),
        stack: error instanceof Error ? error.stack : undefined
      });
      throw error;
    }
  }

  /**
   * Process a file using the API
   */
  private async processFile(options: CLIOptions): Promise<void> {
    // Configure logging based on options
    if (options.verbose) {
      logger.level = 'debug';
    }

    logger.info('Starting Meld CLI', {
      version,
      options
    });
    
    // Store the options for later use
    this.cmdOptions = {
      ...this.cmdOptions,
      output: options.output
    };

    try {
      // Check if input file exists
      const inputPath = await this.pathService.resolvePath(options.input);
      if (!(await this.fileSystemService.exists(inputPath))) {
        throw new MeldError(`File not found: ${options.input}`, {
          severity: ErrorSeverity.Fatal,
          code: 'FILE_NOT_FOUND'
        });
      }

      // Read input file
      const content = await this.fileSystemService.readFile(inputPath);
      
      // Parse content into AST
      const ast = await this.parserService.parse(content);
      
      // Interpret AST
      const interpretResult = await this.interpreterService.interpret(ast, { 
        strict: options.strict 
      });
      
      // Determine output path
      const outputPath = await this.determineOutputPath(options);
      
      // Convert to desired format
      const outputContent = await this.outputService.convert(
        ast, // Pass the AST as the first parameter
        this.stateService, // Pass the state service as the second parameter
        options.format || 'md', // Pass the format as the third parameter
        { // Pass the options as the fourth parameter
          preserveMarkdown: options.format === 'markdown'
        }
      );

      // Write output or log to stdout
      if (options.stdout) {
        console.log(outputContent);
        logger.info('Successfully wrote output to stdout');
      } else {
        // Check if output file exists and prompt for overwrite if needed
        if (await this.fileSystemService.exists(outputPath) && !options.force) {
          const { outputPath: confirmedOutputPath, shouldOverwrite } = await this.confirmOverwrite(outputPath);
          if (!shouldOverwrite) {
            // Instead of cancelling, use an incremental filename
            const alternateOutputPath = await this.findAvailableIncrementalFilename(confirmedOutputPath);
            logger.info('Using alternative filename instead of overwriting', { path: alternateOutputPath });
            
            // Write to the alternate file
            await this.fileSystemService.writeFile(alternateOutputPath, outputContent);
            console.log(`Output written to ${alternateOutputPath}`);
            return;
          }
        }

        // Write output file
        await this.fileSystemService.writeFile(outputPath, outputContent);
        logger.info('Successfully wrote output file', { path: outputPath });
      }
    } catch (error) {
      // Convert errors to MeldError for consistent handling
      const meldError = error instanceof MeldError
        ? error
        : new MeldError(error instanceof Error ? error.message : String(error), {
            severity: ErrorSeverity.Fatal,
            code: 'PROCESSING_ERROR'
          });

      logger.error('Error processing file', {
        error: meldError.message,
        code: meldError.code,
        severity: meldError.severity
      });

      throw meldError;
    }
  }

  /**
   * Determine the output path based on CLI options
   */
  private async determineOutputPath(options: CLIOptions): Promise<string> {
    // If output path is explicitly specified, use it
    if (options.output) {
      return this.pathService.resolvePath(options.output);
    }
    
    // If no output path specified, use input path with new extension
    if (!options.input || typeof options.input !== 'string') {
      throw new MeldError('Input file path is required', {
        severity: ErrorSeverity.Fatal,
        code: 'INVALID_INPUT'
      });
    }
    
    const inputPath = options.input;
    const inputExt = extname(inputPath); // Use Node.js path module
    const outputExt = this.getOutputExtension(options.format || 'md');
    
    // Extract the base filename without extension
    const basePath = inputPath.substring(0, inputPath.length - inputExt.length);
    
    // Always append .o.{format} for default behavior
    const outputPath = `${basePath}.o${outputExt}`;
    
    return this.pathService.resolvePath(outputPath);
  }

  private showHelp() {
    console.log(`
Usage: meld [options] <input-file>

Options:
  -f, --format <format>  Output format (md, markdown, llm) [default: llm]
  -o, --output <path>    Output file path [default: input filename with new extension]
  --stdout               Print to stdout instead of file
  --strict               Enable strict mode (fail on all errors)
  --permissive           Enable permissive mode (ignore recoverable errors) [default]
  --home-path <path>     Set custom home path for $~/ and $HOMEPATH
  -v, --verbose          Enable verbose output
  -d, --debug            Enable debug output
  -h, --help             Display this help message
  -V, --version          Display version information
    `);
  }

  /**
   * Log debug messages if verbose mode is enabled
   * @param message Message to log
   */
  private debug(message: string): void {
    if (this.cmdOptions.verbose) {
      console.log(`DEBUG: ${message}`);
    }
  }
} 