/**
 * @fileoverview OrdoJS CLI - Asset optimization utilities for production builds
 */

import brotli from 'brotli';
import { filesize } from 'filesize';
import gzipSize from 'gzip-size';
import path from 'path';
import { Terser } from 'terser';
import { readFile, writeFile } from './fs.js';
import { logger } from './logger.js';

/**
 * Asset optimization options
 */
export interface AssetOptimizationOptions {
  /**
   * Whether to minify JavaScript
   */
  minifyJs?: boolean;

  /**
   * Whether to minify CSS
   */
  minifyCss?: boolean;

  /**
   * Whether to generate Brotli compressed versions
   */
  brotli?: boolean;

  /**
   * Whether to generate Gzip compressed versions
   */
  gzip?: boolean;

  /**
   * Whether to generate size reports
   */
  sizeReport?: boolean;

  /**
   * Terser options for JavaScript minification
   */
  terserOptions?: any;
}

/**
 * Default optimization options
 */
const DEFAULT_OPTIONS: AssetOptimizationOptions = {
  minifyJs: true,
  minifyCss: true,
  brotli: true,
  gzip: true,
  sizeReport: true,
  terserOptions: {
    compress: {
      passes: 2,
      drop_console: true,
      drop_debugger: true
    },
    mangle: true,
    format: {
      comments: false
    }
  }
};

/**
 * Asset size information
 */
export interface AssetSizeInfo {
  /**
   * Original file size in bytes
   */
  originalSize: number;

  /**
   * Minified file size in bytes
   */
  minifiedSize?: number;

  /**
   * Gzip compressed size in bytes
   */
  gzipSize?: number;

  /**
   * Brotli compressed size in bytes
   */
  brotliSize?: number;

  /**
   * Compression ratio (1 - minifiedSize/originalSize)
   */
  compressionRatio?: number;

  /**
   * Human-readable original size
   */
  originalSizeHuman: string;

  /**
   * Human-readable minified size
   */
  minifiedSizeHuman?: string;

  /**
   * Human-readable gzip size
   */
  gzipSizeHuman?: string;

  /**
   * Human-readable brotli size
   */
  brotliSizeHuman?: string;
}

/**
 * Optimization result for a single asset
 */
export interface AssetOptimizationResult {
  /**
   * Asset file path
   */
  filePath: string;

  /**
   * Asset type
   */
  type: 'js' | 'css' | 'html' | 'other';

  /**
   * Size information
   */
  sizes: AssetSizeInfo;

  /**
   * Whether minification was successful
   */
  minified: boolean;

  /**
   * Whether Gzip compression was successful
   */
  gzipped: boolean;

  /**
   * Whether Brotli compression was successful
   */
  brotlified: boolean;
}

/**
 * Optimization results for all assets
 */
export interface OptimizationResults {
  /**
   * Results for individual assets
   */
  assets: AssetOptimizationResult[];

  /**
   * Total original size in bytes
   */
  totalOriginalSize: number;

  /**
   * Total minified size in bytes
   */
  totalMinifiedSize: number;

  /**
   * Total Gzip size in bytes
   */
  totalGzipSize: number;

  /**
   * Total Brotli size in bytes
   */
  totalBrotliSize: number;

  /**
   * Overall compression ratio
   */
  overallCompressionRatio: number;

  /**
   * Human-readable total original size
   */
  totalOriginalSizeHuman: string;

  /**
   * Human-readable total minified size
   */
  totalMinifiedSizeHuman: string;

  /**
   * Human-readable total Gzip size
   */
  totalGzipSizeHuman: string;

  /**
   * Human-readable total Brotli size
   */
  totalBrotliSizeHuman: string;
}

/**
 * Asset optimizer for production builds
 */
export class AssetOptimizer {
  private options: AssetOptimizationOptions;

  /**
   * Create a new asset optimizer
   */
  constructor(options: AssetOptimizationOptions = {}) {
    this.options = { ...DEFAULT_OPTIONS, ...options };
  }

  /**
   * Optimize a JavaScript file
   */
  async optimizeJavaScript(filePath: string): Promise<AssetOptimizationResult> {
    const content = await readFile(filePath);
    const originalSize = content.length;
    const originalSizeHuman = filesize(originalSize) as string;
    let minifiedContent = content;
    let minifiedSize = originalSize;
    let minified = false;

    // Minify JavaScript if enabled
    if (this.options.minifyJs) {
      try {
        const result = await Terser.minify(content, this.options.terserOptions);
        if (result.code) {
          minifiedContent = result.code;
          minifiedSize = minifiedContent.length;
          minified = true;

          // Write minified file
          await writeFile(filePath, minifiedContent);
        }
      } catch (error) {
        logger.warn(`Failed to minify ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
      }
    }

    // Calculate compression ratio
    const compressionRatio = originalSize > 0 ? 1 - minifiedSize / originalSize : 0;

    // Generate size information
    const sizes: AssetSizeInfo = {
      originalSize,
      minifiedSize,
      compressionRatio,
      originalSizeHuman,
      minifiedSizeHuman: filesize(minifiedSize) as string
    };

    // Generate Gzip compressed version if enabled
    let gzipped = false;
    if (this.options.gzip) {
      try {
        const gzipSizeValue = await gzipSize(minifiedContent);
        sizes.gzipSize = gzipSizeValue;
        sizes.gzipSizeHuman = filesize(gzipSizeValue) as string;
        gzipped = true;

        // Write gzipped file
        const gzipFilePath = `${filePath}.gz`;
        // We don't actually write the gzipped file here as it's typically
        // handled by the web server or CDN, but we could if needed
      } catch (error) {
        logger.warn(`Failed to gzip ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
      }
    }

    // Generate Brotli compressed version if enabled
    let brotlified = false;
    if (this.options.brotli) {
      try {
        const buffer = Buffer.from(minifiedContent);
        const compressed = brotli.compress(buffer, {
          mode: 1, // text mode
          quality: 11, // maximum compression
          lgwin: 24 // maximum window size
        });

        if (compressed) {
          const brotliSizeValue = compressed.length;
          sizes.brotliSize = brotliSizeValue;
          sizes.brotliSizeHuman = filesize(brotliSizeValue) as string;
          brotlified = true;

          // Write brotli file
          const brotliFilePath = `${filePath}.br`;
          await writeFile(brotliFilePath, compressed);
        }
      } catch (error) {
        logger.warn(`Failed to brotli compress ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
      }
    }

    return {
      filePath,
      type: 'js',
      sizes,
      minified,
      gzipped,
      brotlified
    };
  }

  /**
   * Optimize a CSS file
   * Note: Basic implementation - the actual CSS minification is handled by the CSS optimizer in the core package
   */
  async optimizeCSS(filePath: string): Promise<AssetOptimizationResult> {
    const content = await readFile(filePath);
    const originalSize = content.length;
    const originalSizeHuman = filesize(originalSize) as string;

    // CSS is already minified by the CSS optimizer in the core package
    // This is just for generating size information and compression
    const minifiedSize = originalSize;
    const minifiedContent = content;
    const minified = true;

    // Calculate compression ratio
    const compressionRatio = 0; // Already minified

    // Generate size information
    const sizes: AssetSizeInfo = {
      originalSize,
      minifiedSize,
      compressionRatio,
      originalSizeHuman,
      minifiedSizeHuman: filesize(minifiedSize) as string
    };

    // Generate Gzip compressed version if enabled
    let gzipped = false;
    if (this.options.gzip) {
      try {
        const gzipSizeValue = await gzipSize(minifiedContent);
        sizes.gzipSize = gzipSizeValue;
        sizes.gzipSizeHuman = filesize(gzipSizeValue) as string;
        gzipped = true;
      } catch (error) {
        logger.warn(`Failed to gzip ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
      }
    }

    // Generate Brotli compressed version if enabled
    let brotlified = false;
    if (this.options.brotli) {
      try {
        const buffer = Buffer.from(minifiedContent);
        const compressed = brotli.compress(buffer, {
          mode: 1, // text mode
          quality: 11, // maximum compression
          lgwin: 24 // maximum window size
        });

        if (compressed) {
          const brotliSizeValue = compressed.length;
          sizes.brotliSize = brotliSizeValue;
          sizes.brotliSizeHuman = filesize(brotliSizeValue) as string;
          brotlified = true;

          // Write brotli file
          const brotliFilePath = `${filePath}.br`;
          await writeFile(brotliFilePath, compressed);
        }
      } catch (error) {
        logger.warn(`Failed to brotli compress ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
      }
    }

    return {
      filePath,
      type: 'css',
      sizes,
      minified,
      gzipped,
      brotlified
    };
  }

  /**
   * Optimize an HTML file
   */
  async optimizeHTML(filePath: string): Promise<AssetOptimizationResult> {
    const content = await readFile(filePath);
    const originalSize = content.length;
    const originalSizeHuman = filesize(originalSize) as string;
    let minifiedContent = content;
    let minifiedSize = originalSize;
    let minified = false;

    // Simple HTML minification
    if (this.options.minifyJs) {
      try {
        // Very basic HTML minification - in a real implementation, we'd use a proper HTML minifier
        minifiedContent = content
          .replace(/<!--[\s\S]*?-->/g, '') // Remove comments
          .replace(/\s{2,}/g, ' ') // Remove extra spaces
          .replace(/>\s+</g, '><') // Remove spaces between tags
          .trim();

        minifiedSize = minifiedContent.length;
        minified = true;

        // Write minified file
        await writeFile(filePath, minifiedContent);
      } catch (error) {
        logger.warn(`Failed to minify ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
      }
    }

    // Calculate compression ratio
    const compressionRatio = originalSize > 0 ? 1 - minifiedSize / originalSize : 0;

    // Generate size information
    const sizes: AssetSizeInfo = {
      originalSize,
      minifiedSize,
      compressionRatio,
      originalSizeHuman,
      minifiedSizeHuman: filesize(minifiedSize) as string
    };

    // Generate Gzip compressed version if enabled
    let gzipped = false;
    if (this.options.gzip) {
      try {
        const gzipSizeValue = await gzipSize(minifiedContent);
        sizes.gzipSize = gzipSizeValue;
        sizes.gzipSizeHuman = filesize(gzipSizeValue) as string;
        gzipped = true;
      } catch (error) {
        logger.warn(`Failed to gzip ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
      }
    }

    // Generate Brotli compressed version if enabled
    let brotlified = false;
    if (this.options.brotli) {
      try {
        const buffer = Buffer.from(minifiedContent);
        const compressed = brotli.compress(buffer, {
          mode: 1, // text mode
          quality: 11, // maximum compression
          lgwin: 24 // maximum window size
        });

        if (compressed) {
          const brotliSizeValue = compressed.length;
          sizes.brotliSize = brotliSizeValue;
          sizes.brotliSizeHuman = filesize(brotliSizeValue) as string;
          brotlified = true;

          // Write brotli file
          const brotliFilePath = `${filePath}.br`;
          await writeFile(brotliFilePath, compressed);
        }
      } catch (error) {
        logger.warn(`Failed to brotli compress ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
      }
    }

    return {
      filePath,
      type: 'html',
      sizes,
      minified,
      gzipped,
      brotlified
    };
  }

  /**
   * Optimize a directory of assets
   */
  async optimizeDirectory(dir: string, patterns: string[] = ['**/*.js', '**/*.css', '**/*.html']): Promise<OptimizationResults> {
    const results: AssetOptimizationResult[] = [];
    let totalOriginalSize = 0;
    let totalMinifiedSize = 0;
    let totalGzipSize = 0;
    let totalBrotliSize = 0;

    // Process each file pattern
    for (const pattern of patterns) {
      const files = await glob(path.join(dir, pattern));

      for (const file of files) {
        let result: AssetOptimizationResult;

        // Determine file type and optimize accordingly
        if (file.endsWith('.js')) {
          result = await this.optimizeJavaScript(file);
        } else if (file.endsWith('.css')) {
          result = await this.optimizeCSS(file);
        } else if (file.endsWith('.html')) {
          result = await this.optimizeHTML(file);
        } else {
          // Skip other file types
          continue;
        }

        results.push(result);

        // Update totals
        totalOriginalSize += result.sizes.originalSize;
        totalMinifiedSize += result.sizes.minifiedSize || result.sizes.originalSize;
        totalGzipSize += result.sizes.gzipSize || 0;
        totalBrotliSize += result.sizes.brotliSize || 0;
      }
    }

    // Calculate overall compression ratio
    const overallCompressionRatio = totalOriginalSize > 0 ? 1 - totalMinifiedSize / totalOriginalSize : 0;

    return {
      assets: results,
      totalOriginalSize,
      totalMinifiedSize,
      totalGzipSize,
      totalBrotliSize,
      overallCompressionRatio,
      totalOriginalSizeHuman: filesize(totalOriginalSize) as string,
      totalMinifiedSizeHuman: filesize(totalMinifiedSize) as string,
      totalGzipSizeHuman: filesize(totalGzipSize) as string,
      totalBrotliSizeHuman: filesize(totalBrotliSize) as string
    };
  }

  /**
   * Generate a size report for the optimized assets
   */
  generateSizeReport(results: OptimizationResults): string {
    let report = '\n=== Asset Size Report ===\n\n';

    // Add summary
    report += 'Summary:\n';
    report += `  Total Original Size: ${results.totalOriginalSizeHuman}\n`;
    report += `  Total Minified Size: ${results.totalMinifiedSizeHuman} (${Math.round(results.overallCompressionRatio * 100)}% reduction)\n`;
    report += `  Total Gzip Size: ${results.totalGzipSizeHuman}\n`;
    report += `  Total Brotli Size: ${results.totalBrotliSizeHuman}\n\n`;

    // Group assets by type
    const jsAssets = results.assets.filter(asset => asset.type === 'js');
    const cssAssets = results.assets.filter(asset => asset.type === 'css');
    const htmlAssets = results.assets.filter(asset => asset.type === 'html');

    // Add JavaScript assets
    if (jsAssets.length > 0) {
      report += 'JavaScript Assets:\n';
      for (const asset of jsAssets) {
        report += `  ${path.basename(asset.filePath)}\n`;
        report += `    Original: ${asset.sizes.originalSizeHuman}\n`;
        report += `    Minified: ${asset.sizes.minifiedSizeHuman}\n`;
        if (asset.sizes.gzipSizeHuman) {
          report += `    Gzip: ${asset.sizes.gzipSizeHuman}\n`;
        }
        if (asset.sizes.brotliSizeHuman) {
          report += `    Brotli: ${asset.sizes.brotliSizeHuman}\n`;
        }
      }
      report += '\n';
    }

    // Add CSS assets
    if (cssAssets.length > 0) {
      report += 'CSS Assets:\n';
      for (const asset of cssAssets) {
        report += `  ${path.basename(asset.filePath)}\n`;
        report += `    Original: ${asset.sizes.originalSizeHuman}\n`;
        report += `    Minified: ${asset.sizes.minifiedSizeHuman}\n`;
        if (asset.sizes.gzipSizeHuman) {
          report += `    Gzip: ${asset.sizes.gzipSizeHuman}\n`;
        }
        if (asset.sizes.brotliSizeHuman) {
          report += `    Brotli: ${asset.sizes.brotliSizeHuman}\n`;
        }
      }
      report += '\n';
    }

    // Add HTML assets
    if (htmlAssets.length > 0) {
      report += 'HTML Assets:\n';
      for (const asset of htmlAssets) {
        report += `  ${path.basename(asset.filePath)}\n`;
        report += `    Original: ${asset.sizes.originalSizeHuman}\n`;
        report += `    Minified: ${asset.sizes.minifiedSizeHuman}\n`;
        if (asset.sizes.gzipSizeHuman) {
          report += `    Gzip: ${asset.sizes.gzipSizeHuman}\n`;
        }
        if (asset.sizes.brotliSizeHuman) {
          report += `    Brotli: ${asset.sizes.brotliSizeHuman}\n`;
        }
      }
      report += '\n';
    }

    return report;
  }
}
