/**
 * TypeScript CLI Generator
 * Generates CLI tools with TypeScript and Commander.js
 */

import { CliGenerator, GeneratorContext, GenerationResult, GeneratedFile } from '../types';
import * as path from 'path';

export class TypeScriptCliGenerator implements CliGenerator {
  id = 'typescript-cli';
  name = 'TypeScript CLI Generator';
  description = 'Generates CLI tools with TypeScript and Commander.js';
  framework = 'commander';
  language = 'typescript';

  generate(context: GeneratorContext): GenerationResult {
    const { workflow, outputDirectory } = context;
    const files: GeneratedFile[] = [];

    // Find CLI components in the stack
    const cliComponents = this.getCliComponents(workflow);

    if (cliComponents.length === 0) {
      return { files: [] };
    }

    // Generate each CLI component
    for (const [componentName, component] of cliComponents) {
      const binName = component.config?.bin_name || componentName;

      // Generate package.json
      files.push(this.generatePackageJson(componentName, binName, workflow, outputDirectory));

      // Generate bin entry point
      files.push(this.generateBinEntry(componentName, binName, outputDirectory));

      // Generate main CLI file
      files.push(this.generateCliMain(componentName, component, binName, outputDirectory));

      // Generate command files
      if (component.commands) {
        for (const command of component.commands) {
          files.push(this.generateCommand(componentName, command, outputDirectory));
        }
        files.push(this.generateCommandIndex(componentName, component.commands, outputDirectory));
      }

      // Generate TypeScript config
      files.push(this.generateTsConfig(componentName, outputDirectory));

      // Generate README
      files.push(this.generateReadme(componentName, component, binName, workflow, outputDirectory));

      // Generate environment files
      files.push(this.generateEnvExample(componentName, workflow, outputDirectory));
    }

    return { files };
  }

  private getCliComponents(workflow: any): Array<[string, any]> {
    const components: Array<[string, any]> = [];
    for (const [name, component] of Object.entries(workflow.stack)) {
      const comp = component as any;
      if (comp.type === 'cli') {
        components.push([name, comp]);
      }
    }
    return components;
  }

  private generatePackageJson(
    componentName: string,
    binName: string,
    workflow: any,
    outputDir: string
  ): GeneratedFile {
    const pkg = {
      name: binName,
      version: '1.0.0',
      description: workflow.workflow.description || `CLI tool generated from ${workflow.workflow.name}`,
      main: 'dist/cli.js',
      types: 'dist/cli.d.ts',
      bin: {
        [binName]: './bin/cli.js',
      },
      files: ['bin', 'dist'],
      scripts: {
        dev: 'ts-node src/cli.ts',
        build: 'tsc',
        test: 'jest',
        'link:local': 'npm link',
        prepublishOnly: 'npm run build && npm test',
      },
      keywords: ['cli', 'tool'],
      author: '',
      license: 'MIT',
      dependencies: {
        commander: '^11.1.0',
        chalk: '^4.1.2',
      },
      devDependencies: {
        '@types/node': '^20.0.0',
        typescript: '^5.0.0',
        'ts-node': '^10.9.0',
        jest: '^29.5.0',
        '@types/jest': '^29.5.0',
        'ts-jest': '^29.1.0',
      },
    };

    return {
      path: path.join(outputDir, componentName, 'package.json'),
      content: JSON.stringify(pkg, null, 2),
      overwrite: false,
    };
  }

  private generateBinEntry(componentName: string, binName: string, outputDir: string): GeneratedFile {
    const content = `#!/usr/bin/env node

require('../dist/cli.js');
`;

    return {
      path: path.join(outputDir, componentName, 'bin', 'cli.js'),
      content,
      overwrite: true,
    };
  }

  private generateCliMain(
    componentName: string,
    component: any,
    binName: string,
    outputDir: string
  ): GeneratedFile {
    const commands = component.commands || [];

    let content = `#!/usr/bin/env node

import { Command } from 'commander';
import chalk from 'chalk';
`;

    // Import commands
    if (commands.length > 0) {
      content += `import { registerCommands } from './commands';\n`;
    }

    content += `
const program = new Command();

program
  .name('${binName}')
  .description('${component.description || 'CLI tool'}')
  .version('1.0.0');
`;

    if (commands.length > 0) {
      content += `
// Register all commands
registerCommands(program);
`;
    }

    content += `
// Parse arguments
program.parse(process.argv);

// Show help if no command specified
if (!process.argv.slice(2).length) {
  program.outputHelp();
}
`;

    return {
      path: path.join(outputDir, componentName, 'src', 'cli.ts'),
      content,
      overwrite: true,
    };
  }

  private generateCommand(componentName: string, command: any, outputDir: string): GeneratedFile {
    const commandName = command.name || command.id;
    const args = command.arguments || [];
    const options = command.options || [];

    let content = `import { Command } from 'commander';
import chalk from 'chalk';

export function ${this.toCamelCase(commandName)}Command(program: Command) {
  program
    .command('${commandName}')
    .description('${command.description || ''}')
`;

    // Add arguments
    for (const arg of args) {
      const argSyntax = arg.required ? `<${arg.name}>` : `[${arg.name}]`;
      content += `    .argument('${argSyntax}', '${arg.description || ''}', '${arg.default || ''}')\n`;
    }

    // Add options
    for (const opt of options) {
      const optSyntax = opt.alias ? `-${opt.alias}, --${opt.name}` : `--${opt.name}`;
      const optType = opt.type === 'boolean' ? '' : ' <value>';
      content += `    .option('${optSyntax}${optType}', '${opt.description || ''}')\n`;
    }

    content += `    .action(async (`;

    // Action parameters
    const params = args.map((a: any) => a.name);
    if (options.length > 0) {
      params.push('options');
    }
    content += params.join(', ');

    content += `) => {
      try {
`;

    // Generate command logic based on implementation
    const steps = command.implementation?.steps || [];

    for (const step of steps) {
      if (step.action === 'format-string') {
        const template = step.template || 'Hello, {{name}}!';
        const message = template.replace(/\{\{(\w+)\}\}/g, (_: string, key: string) => {
          if (args.find((a: any) => a.name === key)) {
            return `\${${key}}`;
          }
          return key;
        });
        content += `        const message = \`${message}\`;\n`;
      }

      if (step.action === 'console-output') {
        const color = step.color || 'white';
        content += `        console.log(chalk.${color}(message));\n`;
      }
    }

    // Fallback if no implementation
    if (steps.length === 0) {
      content += `        console.log(chalk.green('Command executed!'));\n`;
    }

    content += `      } catch (error: any) {
        console.error(chalk.red('Error:'), error.message);
        process.exit(1);
      }
    });
}
`;

    return {
      path: path.join(outputDir, componentName, 'src', 'commands', `${commandName}.ts`),
      content,
      overwrite: true,
    };
  }

  private generateCommandIndex(
    componentName: string,
    commands: any[],
    outputDir: string
  ): GeneratedFile {
    let content = `import { Command } from 'commander';
`;

    // Import all commands
    for (const command of commands) {
      const commandName = command.name || command.id;
      content += `import { ${this.toCamelCase(commandName)}Command } from './${commandName}';\n`;
    }

    content += `
export function registerCommands(program: Command) {
`;

    // Register all commands
    for (const command of commands) {
      const commandName = command.name || command.id;
      content += `  ${this.toCamelCase(commandName)}Command(program);\n`;
    }

    content += `}
`;

    return {
      path: path.join(outputDir, componentName, 'src', 'commands', 'index.ts'),
      content,
      overwrite: true,
    };
  }

  private generateTsConfig(componentName: string, outputDir: string): GeneratedFile {
    const config = {
      compilerOptions: {
        target: 'ES2020',
        module: 'commonjs',
        lib: ['ES2020'],
        outDir: './dist',
        rootDir: './src',
        strict: true,
        esModuleInterop: true,
        skipLibCheck: true,
        forceConsistentCasingInFileNames: true,
        declaration: true,
        declarationMap: true,
        sourceMap: true,
      },
      include: ['src/**/*'],
      exclude: ['node_modules', 'dist', '**/*.test.ts'],
    };

    return {
      path: path.join(outputDir, componentName, 'tsconfig.json'),
      content: JSON.stringify(config, null, 2),
      overwrite: false,
    };
  }

  private generateReadme(
    componentName: string,
    component: any,
    binName: string,
    workflow: any,
    outputDir: string
  ): GeneratedFile {
    const commands = component.commands || [];

    let content = `# ${binName}

${workflow.workflow.description || 'A CLI tool'}

## Installation

\`\`\`bash
npm install -g ${binName}
\`\`\`

## Usage

`;

    for (const command of commands) {
      const commandName = command.name || command.id;
      content += `### ${commandName}

${command.description || ''}

\`\`\`bash
${binName} ${commandName}
\`\`\`

`;
    }

    content += `## Development

\`\`\`bash
# Install dependencies
npm install

# Run in development
npm run dev ${commands[0]?.name || 'help'}

# Build
npm run build

# Test
npm test

# Link locally
npm run link:local
\`\`\`

## Publishing to npm

### Step 1: Create an npm Account

If you don't have an npm account yet:

1. Go to [https://www.npmjs.com/signup](https://www.npmjs.com/signup)
2. Fill out the signup form with:
   - Username (will be part of your package name if using scoped packages)
   - Email address
   - Password
3. Verify your email address

### Step 2: Login to npm

Login to npm from your terminal:

\`\`\`bash
npm login
\`\`\`

You'll be prompted to enter:
- Username
- Password
- Email
- One-time password (if you have 2FA enabled)

Verify you're logged in:

\`\`\`bash
npm whoami
\`\`\`

### Step 3: Configure Your Package

Before publishing, update \`package.json\`:

\`\`\`json
{
  "name": "${binName}",  // Change if the name is taken
  "version": "1.0.0",
  "description": "Your description here",
  "author": "Your Name <your.email@example.com>",
  "repository": {
    "type": "git",
    "url": "https://github.com/yourusername/your-repo.git"
  },
  "keywords": ["cli", "tool", "your", "keywords"],
  "license": "MIT"
}
\`\`\`

**Important**: If the package name \`${binName}\` is already taken, you have two options:

1. **Use a scoped package** (recommended):
   \`\`\`json
   "name": "@yourusername/${binName}"
   \`\`\`

2. **Choose a unique name**:
   \`\`\`json
   "name": "my-unique-${binName}"
   \`\`\`

### Step 4: Publish Your Package

\`\`\`bash
# Build and test (done automatically by prepublishOnly script)
npm run build
npm test

# Publish to npm
npm publish

# If using a scoped package for the first time, use:
npm publish --access public
\`\`\`

### Step 5: Verify Publication

1. Check your package page: \`https://www.npmjs.com/package/${binName}\`
2. Try installing it globally:
   \`\`\`bash
   npm install -g ${binName}
   ${binName} --help
   \`\`\`

### Step 6: Update Versions

When you make changes and want to publish updates:

\`\`\`bash
# For bug fixes (1.0.0 -> 1.0.1)
npm version patch

# For new features (1.0.0 -> 1.1.0)
npm version minor

# For breaking changes (1.0.0 -> 2.0.0)
npm version major

# Publish the new version
npm publish
\`\`\`

## License

MIT
`;

    return {
      path: path.join(outputDir, componentName, 'README.md'),
      content,
      overwrite: false,
    };
  }

  private generateEnvExample(componentName: string, workflow: any, outputDir: string): GeneratedFile {
    const envVars = workflow.environment?.development?.[componentName] || {};

    let content = '# Environment Variables\n\n';
    for (const [key, value] of Object.entries(envVars)) {
      content += `${key}=${value}\n`;
    }

    return {
      path: path.join(outputDir, componentName, '.env.example'),
      content,
      overwrite: false,
    };
  }

  private toCamelCase(str: string): string {
    return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
  }
}
