#!/usr/bin/env -S node --no-warnings --no-deprecation

import { Command } from 'commander';
import { readFileSync, writeFileSync } from 'fs';
import { Oneshot } from '../src/oneshot';
import { AnthropicProvider } from '../src/providers/anthropic';
import { OpenAIProvider } from '../src/providers/openai';
import { loadConfig, checkAndPromptConfig, setupConfig } from '../src/config';
import { parse } from 'yaml';
import { createInterface } from 'readline';
import { existsSync } from 'fs';

const program = new Command();

program
  .name('oneshot')
  .description('Send AIs prompts with optional variations')
  .argument('[file]', 'Path to prompt file or quoted text')
  .option('-m, --model <model>', 'AI model(s) to use (e.g. claude-3 or "gpt-4 claude-3")')
  .option('-o, --outfile <path>', 'Save output to file')
  .option('--system <prompt>', 'System prompt')
  .option('--system-file <path>', 'System prompt from file')
  .option('--variations <json>', 'Variation prompts as JSON array')
  .option('--variations-file <path>', 'Variation prompts from file (JSON or YAML)')
  .option('--iterations <number>', 'Number of responses per variation', '1')
  .option('--default [model]', 'Set default model for future oneshot commands')
  .addHelpText('after', `
Examples:
  $ oneshot "Hello, how are you?"              Send text prompt using default model
  $ oneshot 'Tell me a story'                  Send text prompt using default model
  $ oneshot prompt.md                          Send file prompt using default model
  $ oneshot -m claude-3 'Tell me a story'     Send text prompt to Claude
  $ oneshot -m "gpt-4 claude-3" prompt.md     Send to multiple models
  $ oneshot prompt.md --system "..."          Add system prompt
  $ oneshot prompt.md -o response.md          Save to file
  $ oneshot prompt.md --variations-file roles.yaml
  $ oneshot --default                         Choose default model interactively
  $ oneshot --default 4o                      Set 4o as default model
  
For more examples and documentation:
  https://github.com/meldscience/oneshot#readme`)
  .showHelpAfterError('(add --help for additional information)')
  .action((promptFile: string | undefined, options: any) => {
    // Commander will handle missing required args
  });

program.parse();

async function main() {
  // Load config first to check for aliases and default model
  const config = loadConfig();

  const [inputFile] = program.args;
  const options = program.opts();

  // Handle setting default model
  if (options.default !== undefined) {
    if (options.default === true) {
      // Interactive model selection
      async function selectModel() {
        const rl = createInterface({
          input: process.stdin,
          output: process.stdout
        });

        console.log('\nAvailable models:');
        console.log('  Claude models:');
        console.log('    1. claude-3-5-sonnet-latest');
        console.log('    2. claude-3-opus-latest');
        console.log('    3. claude-3-5-haiku-latest');
        console.log('\n  GPT models:');
        console.log('    4. chatgpt-4o-latest');
        console.log('    5. o1-preview');
        console.log('    6. o1-mini');
        
        if (config.modelAliases) {
          console.log('\n  Aliases:');
          Object.entries(config.modelAliases).forEach(([alias, target]) => {
            console.log(`    ${alias} → ${target}`);
          });
        }

        const currentDefault = config.defaultOneshotModel;
        console.log(`\nCurrent default: ${currentDefault || '(none)'}`);
        
        const answer = await new Promise<string>(resolve => {
          rl.question('\nEnter model name or number: ', resolve);
        });
        rl.close();

        let selectedModel = answer.trim();
        
        // Map numbers to models
        const modelMap: {[key: string]: string} = {
          '1': 'claude-3-5-sonnet-latest',
          '2': 'claude-3-opus-latest', 
          '3': 'claude-3-5-haiku-latest',
          '4': 'chatgpt-4o-latest',
          '5': 'o1-preview',
          '6': 'o1-mini'
        };

        if (modelMap[selectedModel]) {
          selectedModel = modelMap[selectedModel];
        }

        setupConfig({
          defaultOneshotModel: selectedModel
        });
        console.log(`✅ Set default model to: ${selectedModel}`);
        process.exit(0);
      }

      await selectModel().catch(error => {
        console.error('❌ Error:', error.message);
        process.exit(1);
      });
      return;
    } else {
      setupConfig({
        defaultOneshotModel: options.default
      });
      console.log(`✅ Set default model to: ${options.default}`);
      process.exit(0);
    }
  }

  // Require file argument if not setting default
  if (!inputFile) {
    console.error('❌ Error: Missing required argument: file');
    console.error('(add --help for additional information)');
    process.exit(1);
  }

  // Get model from option or default
  let model = options.model;
  if (!model) {
    const defaultModel = config.defaultOneshotModel;
    if (!defaultModel) {
      console.error('❌ Error: No model specified and no default model configured');
      console.error('Either provide a model with -m/--model or set a default with:');
      console.error('  oneshot --default <model>');
      process.exit(1);
    }
    model = defaultModel;
  }

  const models = model.split(/\s+/);

  try {
    // Validate and resolve models
    for (const model of models) {
      // Check if it's an alias first
      const resolvedModel = config.modelAliases?.[model] || model;
      const isAlias = model in (config.modelAliases || {});
      
      if (!resolvedModel.startsWith('claude') && !resolvedModel.startsWith('gpt') && 
          !resolvedModel.startsWith('chatgpt') && !resolvedModel.startsWith('o1')) {
        if (!isAlias) {
          // This is not an alias
          console.error(`❌ Error: Unsupported model: ${model}`);
          console.error('Supported models start with: claude-, gpt-, chatgpt-, o1');
          console.error('\nOr use these aliases:');
          if (config.modelAliases) {
            Object.entries(config.modelAliases).forEach(([alias, target]) => {
              console.error(`  ${alias} → ${target}`);
            });
          }
        } else {
          // This is an alias that resolved to an invalid model
          console.error(`❌ Error: Unsupported model: ${resolvedModel} (from alias '${model}')`);
          console.error('Supported models start with: claude-, gpt-, chatgpt-, o1');
        }
        process.exit(1);
      }
    }

    // Check for required API keys
    checkAndPromptConfig(models.map((m: string) => config.modelAliases?.[m] || m));

    // Get prompt content - either from file or direct input
    let promptContent: string;
    try {
      if (existsSync(inputFile)) {
        promptContent = readFileSync(inputFile, 'utf-8');
      } else {
        promptContent = inputFile;
      }
    } catch (error: any) {
      console.error('❌ Error: Failed to read prompt:', error.message);
      process.exit(1);
    }

    // Read system prompt if specified
    let systemPrompt = options.system;
    if (options.systemFile) {
      try {
        systemPrompt = readFileSync(options.systemFile, 'utf-8');
      } catch (error: any) {
        if (error.code === 'ENOENT') {
          console.error('❌ Error: System prompt file not found:', options.systemFile);
          process.exit(1);
        }
        throw error;
      }
    }

    // Parse variations if specified
    let variations: string[] | undefined;
    if (options.variations) {
      try {
        variations = JSON.parse(options.variations);
        if (!Array.isArray(variations)) {
          console.error('❌ Error: Variations must be a JSON array');
          process.exit(1);
        }
      } catch (error) {
        console.error('❌ Error: Invalid JSON in variations argument');
        console.error('Example: --variations \'["Perspective 1", "Perspective 2"]\'');
        process.exit(1);
      }
    } else if (options.variationsFile) {
      try {
        const content = readFileSync(options.variationsFile, 'utf-8');
        if (options.variationsFile.endsWith('.json')) {
          variations = JSON.parse(content);
          if (!Array.isArray(variations)) {
            console.error('❌ Error: JSON variations file must contain an array');
            process.exit(1);
          }
        } else if (options.variationsFile.endsWith('.yaml') || options.variationsFile.endsWith('.yml')) {
          const yamlContent = parse(content);
          variations = Array.isArray(yamlContent) ? yamlContent : Object.values(yamlContent);
        } else {
          console.error('❌ Error: Variations file must be .json, .yaml, or .yml');
          process.exit(1);
        }
      } catch (error: any) {
        if (error.code === 'ENOENT') {
          console.error('❌ Error: Variations file not found:', options.variationsFile);
        } else {
          console.error('❌ Error: Failed to parse variations file:', error.message || 'Invalid format');
        }
        process.exit(1);
      }
    }

    // Process each model
    const allResponses = [];
    for (const model of models) {
      // Get resolved model name
      const resolvedModel = config.modelAliases?.[model] || model;

      // Select provider based on resolved model
      let provider;
      if (resolvedModel.startsWith('claude')) {
        if (!config.anthropicApiKey) {
          console.error('❌ Error: Anthropic API key is required for Claude models');
          console.error('Run: meld-config --anthropic-key <your-key>');
          process.exit(1);
        }
        provider = new AnthropicProvider(config.anthropicApiKey);
      } else if (resolvedModel.startsWith('gpt') || resolvedModel.startsWith('o1') || resolvedModel.startsWith('chatgpt')) {
        if (!config.openaiApiKey) {
          console.error('❌ Error: OpenAI API key is required for GPT models');
          console.error('Run: meld-config --openai-key <your-key>');
          process.exit(1);
        }
        provider = new OpenAIProvider(config.openaiApiKey);
      } else {
        console.error(`❌ Error: Unsupported model: ${model}`);
        console.error('Supported models start with: claude-, gpt-, chatgpt-, o1');
        process.exit(1);
      }

      // Create and run oneshot
      console.log(`🤖 Sending prompt to ${model} (${resolvedModel})`);
      const oneshot = new Oneshot({
        model,
        promptFile: inputFile,
        system: systemPrompt,
        variations,
        iterations: parseInt(options.iterations, 10)
      }, provider);

      const responses = await oneshot.process();
      allResponses.push(...responses.map(r => ({
        ...r,
        model
      })));
    }

    // Format output
    const output = allResponses.map((r) => {
      const modelHeader = `# Model: ${r.model}`;
      let variationHeader = '';
      if (r.variation) {
        if (typeof r.variation === 'string') {
          variationHeader = `## Variation: ${r.variation}`;
        } else {
          const parts = [];
          if (r.variation.prompt) parts.push(`Prompt: ${r.variation.prompt}`);
          if (r.variation.system) parts.push(`System: ${r.variation.system}`);
          if (r.variation.model) parts.push(`Model: ${r.variation.model}`);
          if (parts.length > 0) {
            variationHeader = `## Variation:\n${parts.map(p => `- ${p}`).join('\n')}`;
          }
        }
      }
      const headers = [modelHeader, variationHeader].filter(Boolean).join('\n');
      return `${headers}\n\n${r.response}`;
    }).join('\n\n---\n\n');

    // Write output
    if (options.outfile) {
      try {
        writeFileSync(options.outfile, output);
        console.log(`📝 Response written to ${options.outfile}`);
      } catch (error: any) {
        console.error('❌ Error: Failed to write output file:', error.message);
        process.exit(1);
      }
    } else {
      console.log('\n' + output);
    }

  } catch (error: any) {
    console.error('❌ Error:', error.message);
    process.exit(1);
  }
}

// Start the program
main().catch(error => {
  console.error('❌ Error:', error.message);
  process.exit(1);
}); 