import fs from 'fs-extra';
import path from 'path';
import handlebars from 'handlebars';
import {
  Template,
  TemplateConfig,
  GenerateOptions,
  Transform,
  TemplateFile,
  Feature,
  FeatureContext
} from './types';

export class TemplateEngine {
  private templates: Map<string, Template> = new Map();
  private features: Map<string, Feature> = new Map();

  constructor() {
    // Register custom handlebars helpers
    this.registerHelpers();
  }

  private registerHelpers() {
    // Case transformations
    handlebars.registerHelper('camelCase', (str: string) => 
      str.replace(/-([a-z])/g, g => g[1].toUpperCase())
    );
    
    handlebars.registerHelper('pascalCase', (str: string) => {
      const camel = str.replace(/-([a-z])/g, g => g[1].toUpperCase());
      return camel.charAt(0).toUpperCase() + camel.slice(1);
    });
    
    handlebars.registerHelper('kebabCase', (str: string) => 
      str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
    );
    
    // Conditionals
    handlebars.registerHelper('if_eq', function(this: any, a: any, b: any, options: any) {
      return a === b ? options.fn(this) : options.inverse(this);
    });

    handlebars.registerHelper('if_includes', function(this: any, array: any[], value: any, options: any) {
      return array && array.includes(value) ? options.fn(this) : options.inverse(this);
    });
  }

  async loadTemplate(templatePath: string): Promise<TemplateConfig> {
    const configPath = path.join(templatePath, 'template.config.json');
    
    if (!await fs.pathExists(configPath)) {
      throw new Error(`Template configuration not found at ${configPath}`);
    }
    
    const config = await fs.readJson(configPath) as TemplateConfig;
    
    // Validate template config
    this.validateTemplateConfig(config);
    
    return config;
  }

  private validateTemplateConfig(config: TemplateConfig) {
    if (!config.name || !config.type || !config.files) {
      throw new Error('Invalid template configuration: missing required fields');
    }
  }

  async generateFromTemplate(
    templatePath: string,
    variables: Record<string, any>,
    options: GenerateOptions
  ): Promise<void> {
    const config = await this.loadTemplate(templatePath);
    
    // Validate variables against template requirements
    this.validateVariables(config, variables);
    
    // Process each file in the template
    for (const file of config.files) {
      if (file.condition && !file.condition(variables)) {
        continue; // Skip this file
      }
      
      await this.processTemplateFile(
        path.join(templatePath, file.source),
        path.join(options.outputPath, file.destination),
        variables,
        file.transforms,
        options
      );
    }
    
    // Install features if specified
    if (config.features && variables.features) {
      await this.installFeatures(
        options.outputPath,
        variables.features,
        variables.packageManager || 'pnpm'
      );
    }
  }

  private validateVariables(config: TemplateConfig, variables: Record<string, any>) {
    for (const varDef of config.variables) {
      if (varDef.required && !(varDef.name in variables)) {
        throw new Error(`Missing required variable: ${varDef.name}`);
      }
      
      // Set defaults for missing optional variables
      if (!(varDef.name in variables) && varDef.default !== undefined) {
        variables[varDef.name] = varDef.default;
      }
      
      // Validate select options
      if (varDef.type === 'select' && varDef.options && variables[varDef.name]) {
        if (!varDef.options.includes(variables[varDef.name])) {
          throw new Error(`Invalid value for ${varDef.name}: ${variables[varDef.name]}`);
        }
      }
    }
  }

  private async processTemplateFile(
    sourcePath: string,
    destPath: string,
    variables: Record<string, any>,
    transforms?: Transform[],
    options?: GenerateOptions
  ): Promise<void> {
    // Read template file
    const templateContent = await fs.readFile(sourcePath, 'utf-8');
    
    // Compile and render with handlebars
    const template = handlebars.compile(templateContent);
    let rendered = template(variables);
    
    // Apply transforms
    if (transforms) {
      rendered = this.applyTransforms(rendered, transforms, variables);
    }
    
    // Write file
    if (!options?.dryRun) {
      await fs.ensureDir(path.dirname(destPath));
      await fs.writeFile(destPath, rendered);
    }
    
    if (!options?.silent) {
      console.log(`Created: ${destPath}`);
    }
  }

  private applyTransforms(
    content: string,
    transforms: Transform[],
    variables: Record<string, any>
  ): string {
    let result = content;
    
    for (const transform of transforms) {
      switch (transform.type) {
        case 'replace':
          if (transform.config.pattern && transform.config.replacement) {
            result = result.replace(
              transform.config.pattern,
              transform.config.replacement
            );
          }
          break;
          
        case 'conditional':
          if (transform.config.condition && !transform.config.condition(variables)) {
            // Remove conditional blocks or apply alternative transform
            continue;
          }
          break;
          
        case 'case':
          // Apply case transformation to specific patterns
          // Implementation depends on specific requirements
          break;
      }
    }
    
    return result;
  }

  async installFeatures(
    projectPath: string,
    featureIds: string[],
    packageManager: string
  ): Promise<void> {
    for (const featureId of featureIds) {
      const feature = this.features.get(featureId);
      
      if (!feature) {
        console.warn(`Unknown feature: ${featureId}`);
        continue;
      }
      
      const context: FeatureContext = {
        projectPath,
        packageManager: packageManager as any,
        variables: {},
        features: featureIds
      };
      
      // Install dependencies
      if (feature.dependencies) {
        await this.installPackages(
          projectPath,
          feature.dependencies,
          false,
          packageManager
        );
      }
      
      if (feature.devDependencies) {
        await this.installPackages(
          projectPath,
          feature.devDependencies,
          true,
          packageManager
        );
      }
      
      // Run post-install hook
      if (feature.postInstall) {
        await feature.postInstall(context);
      }
    }
  }

  private async installPackages(
    projectPath: string,
    packages: string[],
    dev: boolean,
    packageManager: string
  ): Promise<void> {
    // Implementation would use child_process to run package manager commands
    const command = this.getInstallCommand(packageManager, packages, dev);
    
    // Execute command in project directory
    // This is a placeholder - actual implementation would use execSync or similar
    console.log(`Installing: ${packages.join(', ')}`);
  }

  private getInstallCommand(
    packageManager: string,
    packages: string[],
    dev: boolean
  ): string {
    const packageList = packages.join(' ');
    
    switch (packageManager) {
      case 'npm':
        return `npm install ${dev ? '--save-dev' : ''} ${packageList}`;
      case 'yarn':
        return `yarn add ${dev ? '--dev' : ''} ${packageList}`;
      case 'pnpm':
        return `pnpm add ${dev ? '--save-dev' : ''} ${packageList}`;
      default:
        throw new Error(`Unknown package manager: ${packageManager}`);
    }
  }

  registerFeature(feature: Feature): void {
    this.features.set(feature.id, feature);
  }

  registerTemplate(template: Template): void {
    this.templates.set(template.name, template);
  }
}