/**
 * @fileoverview OrdoJS Performance Benchmarker - Comprehensive performance measurement and optimization
 */

import {
    OptimizationError,
    OptimizationType,
    type GeneratedCode
} from '../types/index.js';
import { OrdoJSCodeGenerator } from './code-generator.js';
import { DeadCodeEliminator } from './dead-code-eliminator.js';
import { OrdoJSLexer } from './lexer.js';
import { OrdoJSParser } from './parser.js';

/**
 * Performance metrics for different operations
 */
export interface PerformanceMetrics {
  /** Compilation time in milliseconds */
  compilationTime: number;
  /** Lexical analysis time in milliseconds */
  lexingTime: number;
  /** Parsing time in milliseconds */
  parsingTime: number;
  /** Code generation time in milliseconds */
  codeGenerationTime: number;
  /** Optimization time in milliseconds */
  optimizationTime: number;
  /** Bundle size in bytes */
  bundleSize: number;
  /** Gzipped bundle size in bytes */
  gzippedSize: number;
  /** Brotli compressed bundle size in bytes */
  brotliSize: number;
  /** Number of AST nodes */
  astNodeCount: number;
  /** Number of tokens generated */
  tokenCount: number;
  /** Memory usage in bytes */
  memoryUsage: number;
  /** Hydration time in milliseconds */
  hydrationTime?: number;
  /** Runtime performance metrics */
  runtimeMetrics?: RuntimeMetrics;
}

/**
 * Runtime performance metrics
 */
export interface RuntimeMetrics {
  /** Time to first meaningful paint in milliseconds */
  timeToFirstPaint: number;
  /** Time to interactive in milliseconds */
  timeToInteractive: number;
  /** Memory usage during runtime in bytes */
  runtimeMemoryUsage: number;
  /** Number of DOM operations */
  domOperations: number;
  /** Reactive update time in milliseconds */
  reactiveUpdateTime: number;
}

/**
 * Benchmark configuration
 */
export interface BenchmarkConfig {
  /** Number of iterations for averaging */
  iterations: number;
  /** Whether to include runtime benchmarks */
  includeRuntime: boolean;
  /** Whether to measure memory usage */
  measureMemory: boolean;
  /** Whether to generate compression metrics */
  includeCompression: boolean;
  /** Target bundle size for optimization */
  targetBundleSize?: number;
  /** Performance thresholds */
  thresholds: PerformanceThresholds;
}

/**
 * Performance thresholds for validation
 */
export interface PerformanceThresholds {
  /** Maximum compilation time in milliseconds */
  maxCompilationTime: number;
  /** Maximum bundle size in bytes */
  maxBundleSize: number;
  /** Maximum hydration time in milliseconds */
  maxHydrationTime: number;
  /** Maximum time to first paint in milliseconds */
  maxTimeToFirstPaint: number;
  /** Maximum time to interactive in milliseconds */
  maxTimeToInteractive: number;
}

/**
 * Benchmark result
 */
export interface BenchmarkResult {
  /** Component name */
  componentName: string;
  /** Average metrics across iterations */
  averageMetrics: PerformanceMetrics;
  /** All individual measurements */
  measurements: PerformanceMetrics[];
  /** Performance validation results */
  validation: PerformanceValidation;
  /** Optimization suggestions */
  suggestions: string[];
  /** Comparison with baseline */
  comparison?: PerformanceComparison;
}

/**
 * Performance validation results
 */
export interface PerformanceValidation {
  /** Whether all thresholds are met */
  passed: boolean;
  /** Failed threshold checks */
  failures: string[];
  /** Performance score (0-100) */
  score: number;
}

/**
 * Performance comparison with baseline
 */
export interface PerformanceComparison {
  /** Baseline metrics */
  baseline: PerformanceMetrics;
  /** Current metrics */
  current: PerformanceMetrics;
  /** Percentage improvement/degradation */
  improvement: {
    compilationTime: number;
    bundleSize: number;
    hydrationTime: number;
    overall: number;
  };
}

/**
 * Default benchmark configuration
 */
const DEFAULT_BENCHMARK_CONFIG: BenchmarkConfig = {
  iterations: 10,
  includeRuntime: true,
  measureMemory: true,
  includeCompression: true,
  thresholds: {
    maxCompilationTime: 1000, // 1 second
    maxBundleSize: 100000, // 100KB
    maxHydrationTime: 100, // 100ms
    maxTimeToFirstPaint: 2000, // 2 seconds
    maxTimeToInteractive: 3000 // 3 seconds
  }
};

/**
 * Comprehensive performance benchmarker for OrdoJS components
 */
export class PerformanceBenchmarker {
  private config: BenchmarkConfig;
  private baselineMetrics: Map<string, PerformanceMetrics> = new Map();
  private errors: OptimizationError[] = [];

  constructor(config: Partial<BenchmarkConfig> = {}) {
    this.config = { ...DEFAULT_BENCHMARK_CONFIG, ...config };
  }

  /**
   * Benchmark a component's performance
   */
  async benchmarkComponent(
    source: string,
    componentName: string,
    baselineName?: string
  ): Promise<BenchmarkResult> {
    this.errors = [];
    const measurements: PerformanceMetrics[] = [];

    try {
      // Run multiple iterations for averaging
      for (let i = 0; i < this.config.iterations; i++) {
        const metrics = await this.measurePerformance(source, componentName);
        measurements.push(metrics);
      }

      // Calculate average metrics
      const averageMetrics = this.calculateAverageMetrics(measurements);

      // Validate performance against thresholds
      const validation = this.validatePerformance(averageMetrics);

      // Generate optimization suggestions
      const suggestions = this.generateSuggestions(averageMetrics, validation);

      // Compare with baseline if provided
      let comparison: PerformanceComparison | undefined;
      if (baselineName && this.baselineMetrics.has(baselineName)) {
        comparison = this.compareWithBaseline(averageMetrics, this.baselineMetrics.get(baselineName)!);
      }

      return {
        componentName,
        averageMetrics,
        measurements,
        validation,
        suggestions,
        comparison
      };
    } catch (error) {
      const benchmarkError = new OptimizationError(
        `Benchmark failed: ${error instanceof Error ? error.message : String(error)}`,
        { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } },
        OptimizationType.DEPENDENCY_OPTIMIZATION
      );
      this.errors.push(benchmarkError);
      throw benchmarkError;
    }
  }

  /**
   * Set baseline metrics for comparison
   */
  setBaseline(name: string, metrics: PerformanceMetrics): void {
    this.baselineMetrics.set(name, metrics);
  }

  /**
   * Get baseline metrics
   */
  getBaseline(name: string): PerformanceMetrics | undefined {
    return this.baselineMetrics.get(name);
  }

  /**
   * Get benchmark errors
   */
  getErrors(): OptimizationError[] {
    return [...this.errors];
  }

  /**
   * Measure performance of a single compilation
   */
  private async measurePerformance(source: string, componentName: string): Promise<PerformanceMetrics> {
    const startTime = performance.now();
    let memoryUsage = 0;

    if (this.config.measureMemory) {
      memoryUsage = this.getMemoryUsage();
    }

    // Measure lexical analysis
    const lexingStart = performance.now();
    const lexer = new OrdoJSLexer(source);
    const tokenStream = lexer.tokenize();
    const lexingTime = performance.now() - lexingStart;

    // Measure parsing
    const parsingStart = performance.now();
    const parser = new OrdoJSParser(tokenStream);
    const ast = parser.parse();
    const parsingTime = performance.now() - parsingStart;

    // Measure code generation
    const generationStart = performance.now();
    const generator = new OrdoJSCodeGenerator();
    const generatedCode = generator.generate(ast);
    const codeGenerationTime = performance.now() - generationStart;

    // Measure optimization
    const optimizationStart = performance.now();
    const eliminator = new DeadCodeEliminator();
    const optimizedAST = eliminator.optimize(ast);
    const optimizationTime = performance.now() - optimizationStart;

    // Calculate bundle sizes
    const bundleSize = this.calculateBundleSize(generatedCode);
    const gzippedSize = this.config.includeCompression ? this.calculateGzippedSize(generatedCode) : 0;
    const brotliSize = this.config.includeCompression ? this.calculateBrotliSize(generatedCode) : 0;

    // Count AST nodes and tokens
    const astNodeCount = this.countASTNodes(ast.component);
    const tokenCount = tokenStream.tokens.length;

    // Measure runtime performance if enabled
    let runtimeMetrics: RuntimeMetrics | undefined;
    if (this.config.includeRuntime) {
      runtimeMetrics = await this.measureRuntimePerformance(generatedCode, componentName);
    }

    const totalTime = performance.now() - startTime;

    return {
      compilationTime: totalTime,
      lexingTime,
      parsingTime,
      codeGenerationTime,
      optimizationTime,
      bundleSize,
      gzippedSize,
      brotliSize,
      astNodeCount,
      tokenCount,
      memoryUsage,
      runtimeMetrics
    };
  }

  /**
   * Calculate average metrics across measurements
   */
  private calculateAverageMetrics(measurements: PerformanceMetrics[]): PerformanceMetrics {
    const sum = measurements.reduce((acc, metrics) => ({
      compilationTime: acc.compilationTime + metrics.compilationTime,
      lexingTime: acc.lexingTime + metrics.lexingTime,
      parsingTime: acc.parsingTime + metrics.parsingTime,
      codeGenerationTime: acc.codeGenerationTime + metrics.codeGenerationTime,
      optimizationTime: acc.optimizationTime + metrics.optimizationTime,
      bundleSize: acc.bundleSize + metrics.bundleSize,
      gzippedSize: acc.gzippedSize + metrics.gzippedSize,
      brotliSize: acc.brotliSize + metrics.brotliSize,
      astNodeCount: acc.astNodeCount + metrics.astNodeCount,
      tokenCount: acc.tokenCount + metrics.tokenCount,
      memoryUsage: acc.memoryUsage + metrics.memoryUsage,
      hydrationTime: (acc.hydrationTime || 0) + (metrics.hydrationTime || 0),
      runtimeMetrics: acc.runtimeMetrics
    }), {
      compilationTime: 0,
      lexingTime: 0,
      parsingTime: 0,
      codeGenerationTime: 0,
      optimizationTime: 0,
      bundleSize: 0,
      gzippedSize: 0,
      brotliSize: 0,
      astNodeCount: 0,
      tokenCount: 0,
      memoryUsage: 0,
      hydrationTime: 0,
      runtimeMetrics: undefined
    });

    const count = measurements.length;
    return {
      compilationTime: sum.compilationTime / count,
      lexingTime: sum.lexingTime / count,
      parsingTime: sum.parsingTime / count,
      codeGenerationTime: sum.codeGenerationTime / count,
      optimizationTime: sum.optimizationTime / count,
      bundleSize: Math.round(sum.bundleSize / count),
      gzippedSize: Math.round(sum.gzippedSize / count),
      brotliSize: Math.round(sum.brotliSize / count),
      astNodeCount: Math.round(sum.astNodeCount / count),
      tokenCount: Math.round(sum.tokenCount / count),
      memoryUsage: Math.round(sum.memoryUsage / count),
      hydrationTime: sum.hydrationTime ? sum.hydrationTime / count : undefined,
      runtimeMetrics: sum.runtimeMetrics
    };
  }

  /**
   * Validate performance against thresholds
   */
  private validatePerformance(metrics: PerformanceMetrics): PerformanceValidation {
    const failures: string[] = [];
    let score = 100;

    // Check compilation time
    if (metrics.compilationTime > this.config.thresholds.maxCompilationTime) {
      failures.push(`Compilation time (${metrics.compilationTime.toFixed(2)}ms) exceeds threshold (${this.config.thresholds.maxCompilationTime}ms)`);
      score -= 20;
    }

    // Check bundle size
    if (metrics.bundleSize > this.config.thresholds.maxBundleSize) {
      failures.push(`Bundle size (${metrics.bundleSize} bytes) exceeds threshold (${this.config.thresholds.maxBundleSize} bytes)`);
      score -= 25;
    }

    // Check hydration time
    if (metrics.hydrationTime && metrics.hydrationTime > this.config.thresholds.maxHydrationTime) {
      failures.push(`Hydration time (${metrics.hydrationTime.toFixed(2)}ms) exceeds threshold (${this.config.thresholds.maxHydrationTime}ms)`);
      score -= 15;
    }

    // Check runtime metrics if available
    if (metrics.runtimeMetrics) {
      if (metrics.runtimeMetrics.timeToFirstPaint > this.config.thresholds.maxTimeToFirstPaint) {
        failures.push(`Time to first paint (${metrics.runtimeMetrics.timeToFirstPaint}ms) exceeds threshold (${this.config.thresholds.maxTimeToFirstPaint}ms)`);
        score -= 20;
      }

      if (metrics.runtimeMetrics.timeToInteractive > this.config.thresholds.maxTimeToInteractive) {
        failures.push(`Time to interactive (${metrics.runtimeMetrics.timeToInteractive}ms) exceeds threshold (${this.config.thresholds.maxTimeToInteractive}ms)`);
        score -= 20;
      }
    }

    return {
      passed: failures.length === 0,
      failures,
      score: Math.max(0, score)
    };
  }

  /**
   * Generate optimization suggestions
   */
  private generateSuggestions(metrics: PerformanceMetrics, validation: PerformanceValidation): string[] {
    const suggestions: string[] = [];

    if (metrics.compilationTime > this.config.thresholds.maxCompilationTime * 0.8) {
      suggestions.push('Consider optimizing lexer and parser for faster compilation');
    }

    if (metrics.bundleSize > this.config.thresholds.maxBundleSize * 0.8) {
      suggestions.push('Enable aggressive tree shaking to reduce bundle size');
      suggestions.push('Consider code splitting for large components');
    }

    if (metrics.astNodeCount > 1000) {
      suggestions.push('Component has many AST nodes - consider simplifying the component structure');
    }

    if (metrics.tokenCount > 5000) {
      suggestions.push('Large token count detected - consider optimizing the source code');
    }

    if (metrics.optimizationTime > metrics.compilationTime * 0.3) {
      suggestions.push('Optimization is taking significant time - consider adjusting optimization settings');
    }

    if (metrics.gzippedSize > metrics.bundleSize * 0.7) {
      suggestions.push('Bundle has poor compression ratio - consider code optimization');
    }

    return suggestions;
  }

  /**
   * Compare with baseline metrics
   */
  private compareWithBaseline(current: PerformanceMetrics, baseline: PerformanceMetrics): PerformanceComparison {
    const calculateImprovement = (current: number, baseline: number): number => {
      return ((baseline - current) / baseline) * 100;
    };

    return {
      baseline,
      current,
      improvement: {
        compilationTime: calculateImprovement(current.compilationTime, baseline.compilationTime),
        bundleSize: calculateImprovement(current.bundleSize, baseline.bundleSize),
        hydrationTime: current.hydrationTime && baseline.hydrationTime
          ? calculateImprovement(current.hydrationTime, baseline.hydrationTime)
          : 0,
        overall: calculateImprovement(
          (current.compilationTime + current.bundleSize / 1000),
          (baseline.compilationTime + baseline.bundleSize / 1000)
        )
      }
    };
  }

  /**
   * Calculate bundle size
   */
  private calculateBundleSize(generatedCode: GeneratedCode): number {
    let size = 0;
    if (generatedCode.client) size += new TextEncoder().encode(generatedCode.client).length;
    if (generatedCode.server) size += new TextEncoder().encode(generatedCode.server).length;
    if (generatedCode.html) size += new TextEncoder().encode(generatedCode.html).length;
    if (generatedCode.css) size += new TextEncoder().encode(generatedCode.css).length;
    return size;
  }

  /**
   * Calculate gzipped size (simplified)
   */
  private calculateGzippedSize(generatedCode: GeneratedCode): number {
    // This is a simplified calculation - in a real implementation, you'd use actual gzip compression
    const bundleSize = this.calculateBundleSize(generatedCode);
    return Math.round(bundleSize * 0.3); // Assume 70% compression ratio
  }

  /**
   * Calculate Brotli size (simplified)
   */
  private calculateBrotliSize(generatedCode: GeneratedCode): number {
    // This is a simplified calculation - in a real implementation, you'd use actual Brotli compression
    const bundleSize = this.calculateBundleSize(generatedCode);
    return Math.round(bundleSize * 0.25); // Assume 75% compression ratio
  }

  /**
   * Count AST nodes
   */
  private countASTNodes(component: any): number {
    let count = 1; // Count the component itself

    const countNodes = (node: any): void => {
      if (node.children) {
        count += node.children.length;
        node.children.forEach(countNodes);
      }
      if (node.body) {
        count += node.body.length;
        node.body.forEach(countNodes);
      }
      if (node.elements) {
        count += node.elements.length;
        node.elements.forEach(countNodes);
      }
      if (node.reactiveVariables) {
        count += node.reactiveVariables.length;
      }
      if (node.functions) {
        count += node.functions.length;
      }
      if (node.eventHandlers) {
        count += node.eventHandlers.length;
      }
    };

    countNodes(component);
    return count;
  }

  /**
   * Get memory usage
   */
  private getMemoryUsage(): number {
    if (typeof process !== 'undefined' && process.memoryUsage) {
      return process.memoryUsage().heapUsed;
    }
    return 0;
  }

  /**
   * Measure runtime performance
   */
  private async measureRuntimePerformance(generatedCode: GeneratedCode, componentName: string): Promise<RuntimeMetrics> {
    // This is a simplified runtime measurement
    // In a real implementation, you'd run the component in a browser environment

    const startTime = performance.now();

    // Simulate runtime measurements
    const timeToFirstPaint = Math.random() * 1000 + 500; // 500-1500ms
    const timeToInteractive = timeToFirstPaint + Math.random() * 1000 + 500; // Additional 500-1500ms
    const runtimeMemoryUsage = Math.random() * 1000000 + 500000; // 500KB-1.5MB
    const domOperations = Math.floor(Math.random() * 100) + 10; // 10-110 operations
    const reactiveUpdateTime = Math.random() * 50 + 10; // 10-60ms

    return {
      timeToFirstPaint,
      timeToInteractive,
      runtimeMemoryUsage,
      domOperations,
      reactiveUpdateTime
    };
  }

  /**
   * Generate performance report
   */
  generateReport(result: BenchmarkResult): string {
    const { averageMetrics, validation, suggestions, comparison } = result;

    let report = `# Performance Report: ${result.componentName}\n\n`;

    // Compilation metrics
    report += `## Compilation Metrics\n`;
    report += `- Compilation Time: ${averageMetrics.compilationTime.toFixed(2)}ms\n`;
    report += `- Lexing Time: ${averageMetrics.lexingTime.toFixed(2)}ms\n`;
    report += `- Parsing Time: ${averageMetrics.parsingTime.toFixed(2)}ms\n`;
    report += `- Code Generation Time: ${averageMetrics.codeGenerationTime.toFixed(2)}ms\n`;
    report += `- Optimization Time: ${averageMetrics.optimizationTime.toFixed(2)}ms\n\n`;

    // Bundle metrics
    report += `## Bundle Metrics\n`;
    report += `- Bundle Size: ${averageMetrics.bundleSize} bytes\n`;
    report += `- Gzipped Size: ${averageMetrics.gzippedSize} bytes\n`;
    report += `- Brotli Size: ${averageMetrics.brotliSize} bytes\n`;
    report += `- AST Nodes: ${averageMetrics.astNodeCount}\n`;
    report += `- Tokens: ${averageMetrics.tokenCount}\n\n`;

    // Runtime metrics
    if (averageMetrics.runtimeMetrics) {
      report += `## Runtime Metrics\n`;
      report += `- Time to First Paint: ${averageMetrics.runtimeMetrics.timeToFirstPaint.toFixed(2)}ms\n`;
      report += `- Time to Interactive: ${averageMetrics.runtimeMetrics.timeToInteractive.toFixed(2)}ms\n`;
      report += `- Runtime Memory: ${averageMetrics.runtimeMetrics.runtimeMemoryUsage} bytes\n`;
      report += `- DOM Operations: ${averageMetrics.runtimeMetrics.domOperations}\n`;
      report += `- Reactive Update Time: ${averageMetrics.runtimeMetrics.reactiveUpdateTime.toFixed(2)}ms\n\n`;
    }

    // Validation results
    report += `## Performance Validation\n`;
    report += `- Status: ${validation.passed ? '✅ PASSED' : '❌ FAILED'}\n`;
    report += `- Score: ${validation.score}/100\n`;
    if (validation.failures.length > 0) {
      report += `- Failures:\n`;
      validation.failures.forEach(failure => {
        report += `  - ${failure}\n`;
      });
    }
    report += `\n`;

    // Suggestions
    if (suggestions.length > 0) {
      report += `## Optimization Suggestions\n`;
      suggestions.forEach(suggestion => {
        report += `- ${suggestion}\n`;
      });
      report += `\n`;
    }

    // Comparison
    if (comparison) {
      report += `## Comparison with Baseline\n`;
      report += `- Compilation Time: ${comparison.improvement.compilationTime.toFixed(2)}% ${comparison.improvement.compilationTime > 0 ? 'improvement' : 'degradation'}\n`;
      report += `- Bundle Size: ${comparison.improvement.bundleSize.toFixed(2)}% ${comparison.improvement.bundleSize > 0 ? 'improvement' : 'degradation'}\n`;
      report += `- Overall: ${comparison.improvement.overall.toFixed(2)}% ${comparison.improvement.overall > 0 ? 'improvement' : 'degradation'}\n`;
    }

    return report;
  }
}
