/**
 * @fileoverview CSS Optimization Engine for OrdoJS Framework
 * Handles CSS dead code elimination, minification, and optimization
 */

import {
    type CSSDeclarationNode,
    type CSSRuleNode,
    type ComponentAST,
    type StyleBlockNode
} from '../types/index.js';

/**
 * CSS optimization options
 */
export interface CSSOptimizationOptions {
  /**
   * Whether to remove unused CSS rules
   */
  removeUnusedRules?: boolean;

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

  /**
   * Whether to merge duplicate rules
   */
  mergeDuplicateRules?: boolean;

  /**
   * Whether to remove redundant declarations
   */
  removeRedundantDeclarations?: boolean;

  /**
   * Whether to optimize shorthand properties
   */
  optimizeShorthands?: boolean;

  /**
   * Whether to remove empty rules
   */
  removeEmptyRules?: boolean;

  /**
   * Whether to sort declarations for better compression
   */
  sortDeclarations?: boolean;

  /**
   * Preserve important comments
   */
  preserveImportantComments?: boolean;
}

/**
 * Default CSS optimization options
 */
const DEFAULT_CSS_OPTIMIZATION_OPTIONS: CSSOptimizationOptions = {
  removeUnusedRules: true,
  minify: true,
  mergeDuplicateRules: true,
  removeRedundantDeclarations: true,
  optimizeShorthands: true,
  removeEmptyRules: true,
  sortDeclarations: true,
  preserveImportantComments: false
};

/**
 * CSS usage analysis result
 */
export interface CSSUsageAnalysis {
  usedSelectors: Set<string>;
  usedClasses: Set<string>;
  usedIds: Set<string>;
  usedElements: Set<string>;
  usedPseudoClasses: Set<string>;
  usedPseudoElements: Set<string>;
}

/**
 * CSS optimization result
 */
export interface CSSOptimizationResult {
  optimizedCSS: string;
  originalSize: number;
  optimizedSize: number;
  compressionRatio: number;
  removedRules: number;
  mergedRules: number;
  optimizedDeclarations: number;
}

/**
 * CSS Optimizer for OrdoJS components
 */
export class OrdoJSCSSOptimizer {
  private options: CSSOptimizationOptions;

  constructor(options: Partial<CSSOptimizationOptions> = {}) {
    this.options = { ...DEFAULT_CSS_OPTIMIZATION_OPTIONS, ...options };
  }

  /**
   * Optimize CSS styles for a component
   */
  optimize(styleBlock: StyleBlockNode, componentAST?: ComponentAST): CSSOptimizationResult {
    const originalCSS = this.generateCSS(styleBlock);
    const originalSize = originalCSS.length;

    let optimizedRules = [...styleBlock.rules];
    let removedRules = 0;

    // Perform dead code elimination if component AST is provided
    if (componentAST && this.options.removeUnusedRules) {
      const usageAnalysis = this.analyzeUsage(componentAST);
      const beforeCount = optimizedRules.length;
      optimizedRules = this.removeUnusedRules(optimizedRules, usageAnalysis);
      removedRules += beforeCount - optimizedRules.length;
    }

    // Remove empty rules
    if (this.options.removeEmptyRules) {
      const beforeCount = optimizedRules.length;
      optimizedRules = this.removeEmptyRules(optimizedRules);
      removedRules += beforeCount - optimizedRules.length;
    }

    // Merge duplicate rules
    let mergedRules = 0;
    if (this.options.mergeDuplicateRules) {
      const result = this.mergeDuplicateRules(optimizedRules);
      optimizedRules = result.rules;
      mergedRules = result.mergedCount;
    }

    // Remove redundant declarations
    if (this.options.removeRedundantDeclarations) {
      optimizedRules = this.removeRedundantDeclarations(optimizedRules);
    }

    // Optimize shorthand properties
    let optimizedDeclarations = 0;
    if (this.options.optimizeShorthands) {
      const result = this.optimizeShorthands(optimizedRules);
      optimizedRules = result.rules;
      optimizedDeclarations = result.optimizedCount;
    }

    // Sort declarations for better compression
    if (this.options.sortDeclarations) {
      optimizedRules = this.sortDeclarations(optimizedRules);
    }

    // Generate optimized CSS
    const optimizedStyleBlock: StyleBlockNode = {
      ...styleBlock,
      rules: optimizedRules
    };

    const optimizedCSS = this.options.minify
      ? this.minifyCSS(optimizedStyleBlock)
      : this.generateCSS(optimizedStyleBlock);

    const optimizedSize = optimizedCSS.length;
    const compressionRatio = originalSize > 0 ? (originalSize - optimizedSize) / originalSize : 0;

    return {
      optimizedCSS,
      originalSize,
      optimizedSize,
      compressionRatio,
      removedRules,
      mergedRules,
      optimizedDeclarations
    };
  }

  /**
   * Analyze CSS usage in component AST
   */
  private analyzeUsage(componentAST: ComponentAST): CSSUsageAnalysis {
    const analysis: CSSUsageAnalysis = {
      usedSelectors: new Set(),
      usedClasses: new Set(),
      usedIds: new Set(),
      usedElements: new Set(),
      usedPseudoClasses: new Set(),
      usedPseudoElements: new Set()
    };

    // Analyze markup block for used elements and classes
    if (componentAST.component.markupBlock) {
      this.analyzeMarkupUsage(componentAST.component.markupBlock, analysis);
    }

    // Analyze client block for dynamic class usage
    if (componentAST.component.clientBlock) {
      this.analyzeClientUsage(componentAST.component.clientBlock, analysis);
    }

    return analysis;
  }

  /**
   * Analyze markup block for CSS usage
   */
  private analyzeMarkupUsage(markupBlock: any, analysis: CSSUsageAnalysis): void {
    // Recursively analyze HTML elements
    const analyzeElement = (element: any): void => {
      if (element.type === 'HTMLElement') {
        // Add element tag name
        analysis.usedElements.add(element.tagName.toLowerCase());

        // Analyze attributes for classes and IDs
        element.attributes?.forEach((attr: any) => {
          if (attr.name === 'class') {
            const classes = typeof attr.value === 'string'
              ? attr.value.split(/\s+/).filter(Boolean)
              : [];
            classes.forEach((cls: string) => analysis.usedClasses.add(cls));
          } else if (attr.name === 'id') {
            if (typeof attr.value === 'string') {
              analysis.usedIds.add(attr.value);
            }
          }
        });

        // Recursively analyze children
        element.children?.forEach(analyzeElement);
      }
    };

    markupBlock.elements?.forEach(analyzeElement);


  }

  /**
   * Analyze client block for dynamic CSS usage
   */
  private analyzeClientUsage(clientBlock: any, analysis: CSSUsageAnalysis): void {
    // This is a simplified analysis - in a real implementation,
    // we would need to analyze JavaScript expressions for dynamic class usage
    // For now, we'll be conservative and not remove any rules when dynamic usage is detected
  }

  /**
   * Remove unused CSS rules based on usage analysis
   */
  private removeUnusedRules(rules: CSSRuleNode[], usage: CSSUsageAnalysis): CSSRuleNode[] {
    return rules.filter(rule => this.isRuleUsed(rule, usage));
  }

  /**
   * Check if a CSS rule is used based on usage analysis
   */
  private isRuleUsed(rule: CSSRuleNode, usage: CSSUsageAnalysis): boolean {
    const selector = rule.selector.trim();

    // Always keep global selectors and pseudo-selectors
    if (selector.includes(':root') || selector.includes('html') || selector.includes('body')) {
      return true;
    }

    // Parse selector to check for used elements, classes, and IDs
    const selectorParts = this.parseSelector(selector);

    // For complex selectors (with multiple parts), ALL parts must be used
    // For simple selectors, ANY part being used is sufficient
    if (selectorParts.length > 1) {
      // Complex selector - all parts must be used
      return selectorParts.every(part => this.isPartUsed(part, usage));
    } else {
      // Simple selector - any part being used is sufficient
      return selectorParts.some(part => this.isPartUsed(part, usage));
    }
  }

  /**
   * Check if a selector part is used
   */
  private isPartUsed(part: {
    element?: string;
    classes: string[];
    ids: string[];
    pseudoClasses: string[];
    pseudoElements: string[];
  }, usage: CSSUsageAnalysis): boolean {
    // Check element selectors
    if (part.element && usage.usedElements.has(part.element)) {
      return true;
    }

    // Check class selectors
    if (part.classes.some(cls => usage.usedClasses.has(cls))) {
      return true;
    }

    // Check ID selectors
    if (part.ids.some(id => usage.usedIds.has(id))) {
      return true;
    }

    // Keep pseudo-class and pseudo-element selectors if base is used
    if (part.pseudoClasses.length > 0 || part.pseudoElements.length > 0) {
      // For pseudo-selectors, check if the base element or class is used
      if (part.element && usage.usedElements.has(part.element)) {
        return true;
      }
      if (part.classes.some(cls => usage.usedClasses.has(cls))) {
        return true;
      }
      if (part.ids.some(id => usage.usedIds.has(id))) {
        return true;
      }
    }

    return false;
  }

  /**
   * Parse CSS selector into components
   */
  private parseSelector(selector: string): Array<{
    element?: string;
    classes: string[];
    ids: string[];
    pseudoClasses: string[];
    pseudoElements: string[];
  }> {
    // Split by combinators and parse each part
    const parts = selector.split(/\s*[>+~]\s*|\s+/);

    return parts.map(part => {
      const result = {
        element: undefined as string | undefined,
        classes: [] as string[],
        ids: [] as string[],
        pseudoClasses: [] as string[],
        pseudoElements: [] as string[]
      };

      const trimmedPart = part.trim();

      // Skip empty parts
      if (!trimmedPart) {
        return result;
      }

      // Extract pseudo-elements first (they have :: prefix)
      const pseudoElementMatches = trimmedPart.match(/::([a-zA-Z0-9_-]+)/g);
      if (pseudoElementMatches) {
        result.pseudoElements = pseudoElementMatches.map(match => match.substring(2));
      }

      // Extract pseudo-classes (single : prefix, but not ::)
      const pseudoClassMatches = trimmedPart.match(/:(?!:)([a-zA-Z0-9_-]+)/g);
      if (pseudoClassMatches) {
        result.pseudoClasses = pseudoClassMatches.map(match => match.substring(1));
      }

      // Extract classes
      const classMatches = trimmedPart.match(/\.([a-zA-Z0-9_-]+)/g);
      if (classMatches) {
        result.classes = classMatches.map(match => match.substring(1));
      }

      // Extract IDs
      const idMatches = trimmedPart.match(/#([a-zA-Z0-9_-]+)/g);
      if (idMatches) {
        result.ids = idMatches.map(match => match.substring(1));
      }

      // Extract element (what's left after removing classes, IDs, and pseudo-selectors)
      let elementPart = trimmedPart.replace(/[.#][a-zA-Z0-9_-]+|::?[a-zA-Z0-9_-]+/g, '').trim();
      if (elementPart && elementPart !== '*') {
        result.element = elementPart;
      }

      return result;
    });
  }

  /**
   * Remove empty CSS rules
   */
  private removeEmptyRules(rules: CSSRuleNode[]): CSSRuleNode[] {
    return rules.filter(rule => rule.declarations.length > 0);
  }

  /**
   * Merge duplicate CSS rules
   */
  private mergeDuplicateRules(rules: CSSRuleNode[]): { rules: CSSRuleNode[]; mergedCount: number } {
    const selectorMap = new Map<string, CSSRuleNode>();
    let mergedCount = 0;

    for (const rule of rules) {
      const selector = rule.selector;

      if (selectorMap.has(selector)) {
        // Merge declarations
        const existingRule = selectorMap.get(selector)!;
        const mergedDeclarations = [...existingRule.declarations];

        // Add new declarations, overriding existing ones with same property
        for (const newDecl of rule.declarations) {
          const existingIndex = mergedDeclarations.findIndex(
            decl => decl.property === newDecl.property
          );

          if (existingIndex >= 0) {
            mergedDeclarations[existingIndex] = newDecl;
          } else {
            mergedDeclarations.push(newDecl);
          }
        }

        selectorMap.set(selector, {
          ...existingRule,
          declarations: mergedDeclarations
        });

        mergedCount++;
      } else {
        selectorMap.set(selector, rule);
      }
    }

    return {
      rules: Array.from(selectorMap.values()),
      mergedCount
    };
  }

  /**
   * Remove redundant CSS declarations
   */
  private removeRedundantDeclarations(rules: CSSRuleNode[]): CSSRuleNode[] {
    return rules.map(rule => {
      const seenProperties = new Set<string>();
      const uniqueDeclarations: CSSDeclarationNode[] = [];

      // Keep only the last declaration for each property
      for (let i = rule.declarations.length - 1; i >= 0; i--) {
        const decl = rule.declarations[i];
        if (!seenProperties.has(decl.property)) {
          seenProperties.add(decl.property);
          uniqueDeclarations.unshift(decl);
        }
      }

      return {
        ...rule,
        declarations: uniqueDeclarations
      };
    });
  }

  /**
   * Optimize shorthand properties
   */
  private optimizeShorthands(rules: CSSRuleNode[]): { rules: CSSRuleNode[]; optimizedCount: number } {
    let optimizedCount = 0;

    const optimizedRules = rules.map(rule => {
      const declarations = [...rule.declarations];
      const optimizedDeclarations: CSSDeclarationNode[] = [];

      // Group related properties for shorthand optimization
      const marginProps = new Map<string, CSSDeclarationNode>();
      const paddingProps = new Map<string, CSSDeclarationNode>();
      const borderProps = new Map<string, CSSDeclarationNode>();

      for (const decl of declarations) {
        if (decl.property.startsWith('margin-')) {
          marginProps.set(decl.property, decl);
        } else if (decl.property.startsWith('padding-')) {
          paddingProps.set(decl.property, decl);
        } else if (decl.property.startsWith('border-') && decl.property.endsWith('-width')) {
          borderProps.set(decl.property, decl);
        } else {
          optimizedDeclarations.push(decl);
        }
      }

      // Optimize margin shorthand
      const marginShorthand = this.createShorthand('margin', marginProps);
      if (marginShorthand) {
        optimizedDeclarations.push(marginShorthand);
        optimizedCount++;
      } else {
        optimizedDeclarations.push(...marginProps.values());
      }

      // Optimize padding shorthand
      const paddingShorthand = this.createShorthand('padding', paddingProps);
      if (paddingShorthand) {
        optimizedDeclarations.push(paddingShorthand);
        optimizedCount++;
      } else {
        optimizedDeclarations.push(...paddingProps.values());
      }

      // Add border properties as-is (more complex optimization needed)
      optimizedDeclarations.push(...borderProps.values());

      return {
        ...rule,
        declarations: optimizedDeclarations
      };
    });

    return { rules: optimizedRules, optimizedCount };
  }

  /**
   * Create shorthand property from individual properties
   */
  private createShorthand(
    property: string,
    props: Map<string, CSSDeclarationNode>
  ): CSSDeclarationNode | null {
    const top = props.get(`${property}-top`);
    const right = props.get(`${property}-right`);
    const bottom = props.get(`${property}-bottom`);
    const left = props.get(`${property}-left`);

    // Only create shorthand if all four properties are present
    if (!top || !right || !bottom || !left) {
      return null;
    }

    // Create shorthand value
    let shorthandValue: string;

    if (top.value === right.value && right.value === bottom.value && bottom.value === left.value) {
      // All same: margin: 10px
      shorthandValue = top.value;
    } else if (top.value === bottom.value && right.value === left.value) {
      // Vertical/horizontal: margin: 10px 20px
      shorthandValue = `${top.value} ${right.value}`;
    } else if (right.value === left.value) {
      // Top/horizontal/bottom: margin: 10px 20px 30px
      shorthandValue = `${top.value} ${right.value} ${bottom.value}`;
    } else {
      // All different: margin: 10px 20px 30px 40px
      shorthandValue = `${top.value} ${right.value} ${bottom.value} ${left.value}`;
    }

    return {
      type: 'CSSDeclaration',
      property,
      value: shorthandValue,
      important: false,
      range: top.range
    };
  }

  /**
   * Sort CSS declarations for better compression
   */
  private sortDeclarations(rules: CSSRuleNode[]): CSSRuleNode[] {
    return rules.map(rule => ({
      ...rule,
      declarations: [...rule.declarations].sort((a, b) =>
        a.property.localeCompare(b.property)
      )
    }));
  }

  /**
   * Generate CSS string from StyleBlockNode
   */
  private generateCSS(styleBlock: StyleBlockNode): string {
    return styleBlock.rules.map(rule => {
      const declarations = rule.declarations.map(decl =>
        `  ${decl.property}: ${decl.value};`
      ).join('\n');

      return `${rule.selector} {\n${declarations}\n}`;
    }).join('\n\n');
  }

  /**
   * Minify CSS output
   */
  private minifyCSS(styleBlock: StyleBlockNode): string {
    return styleBlock.rules.map(rule => {
      const declarations = rule.declarations.map(decl =>
        `${decl.property}:${decl.value}`
      ).join(';');

      return `${rule.selector}{${declarations}}`;
    }).join('');
  }

  /**
   * Optimize multiple style blocks and generate a single optimized bundle
   */
  optimizeBundle(styleBlocks: StyleBlockNode[], componentASTs?: ComponentAST[]): CSSOptimizationResult {
    // Merge all rules from all style blocks
    const allRules: CSSRuleNode[] = [];

    styleBlocks.forEach(styleBlock => {
      allRules.push(...styleBlock.rules);
    });

    // Create a combined style block
    const combinedStyleBlock: StyleBlockNode = {
      type: 'StyleBlock',
      rules: allRules,
      scoped: false,
      range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
    };

    // If component ASTs are provided, create combined usage analysis
    let combinedAST: ComponentAST | undefined;
    if (componentASTs && componentASTs.length > 0) {
      // For simplicity, we'll use the first AST as the base
      // In a real implementation, we'd need to merge usage analysis from all components
      combinedAST = componentASTs[0];
    }

    return this.optimize(combinedStyleBlock, combinedAST);
  }
}
