/**
 * @fileoverview OrdoJS Dead Code Eliminator - Comprehensive dead code elimination and optimization
 */

import {
  ExpressionType,
  OptimizationError,
  OptimizationType,
  StatementType,
  type ClientBlockNode,
  type ComponentAST,
  type ExpressionNode,
  type FunctionNode,
  type HTMLElementNode,
  type InterpolationNode,
  type MarkupBlockNode,
  type ServerBlockNode,
  type StatementNode
} from '../types/index.js';

/**
 * Dead code elimination options
 */
export interface DeadCodeEliminatorOptions {
  /**
   * Whether to perform aggressive tree shaking
   */
  aggressiveTreeShaking?: boolean;

  /**
   * Whether to inline small functions
   */
  inlineSmallFunctions?: boolean;

  /**
   * Maximum size (in AST nodes) for function inlining
   */
  maxInlineSize?: number;

  /**
   * Whether to remove unused variables
   */
  removeUnusedVariables?: boolean;

  /**
   * Whether to remove unreachable code
   */
  removeUnreachableCode?: boolean;

  /**
   * Whether to remove unused CSS rules
   */
  removeUnusedCSS?: boolean;

  /**
   * Whether to remove unused event handlers
   */
  removeUnusedEventHandlers?: boolean;

  /**
   * Whether to remove unused server functions
   */
  removeUnusedServerFunctions?: boolean;

  /**
   * Whether to perform constant folding
   */
  constantFolding?: boolean;

  /**
   * Whether to remove empty blocks
   */
  removeEmptyBlocks?: boolean;
}

/**
 * Default dead code elimination options
 */
const DEFAULT_OPTIONS: DeadCodeEliminatorOptions = {
  aggressiveTreeShaking: true,
  inlineSmallFunctions: true,
  maxInlineSize: 10,
  removeUnusedVariables: true,
  removeUnreachableCode: true,
  removeUnusedCSS: true,
  removeUnusedEventHandlers: true,
  removeUnusedServerFunctions: true,
  constantFolding: true,
  removeEmptyBlocks: true
};

/**
 * Usage analysis result
 */
export interface UsageAnalysis {
  usedVariables: Set<string>;
  usedFunctions: Set<string>;
  usedEventHandlers: Set<string>;
  usedCSSSelectors: Set<string>;
  usedServerFunctions: Set<string>;
  reachableStatements: Set<StatementNode>;
  constantExpressions: Map<ExpressionNode, any>;
}

/**
 * Dead code elimination result
 */
export interface DeadCodeEliminationResult {
  optimizedAST: ComponentAST;
  removedVariables: string[];
  removedFunctions: string[];
  removedEventHandlers: string[];
  removedCSSRules: string[];
  removedServerFunctions: string[];
  inlinedFunctions: string[];
  foldedConstants: number;
  removedEmptyBlocks: number;
  optimizationRatio: number;
  errors: OptimizationError[];
  warnings: OptimizationError[];
}

/**
 * Comprehensive dead code eliminator for OrdoJS components
 */
export class DeadCodeEliminator {
  private options: DeadCodeEliminatorOptions;
  private usageAnalysis!: UsageAnalysis;
  private errors: OptimizationError[] = [];
  private warnings: OptimizationError[] = [];

  constructor(options: Partial<DeadCodeEliminatorOptions> = {}) {
    this.options = { ...DEFAULT_OPTIONS, ...options };
    this.reset();
  }

  /**
   * Optimize a component AST by removing dead code
   */
  optimize(ast: ComponentAST): ComponentAST {
    this.reset();

    try {
      // Step 1: Analyze usage patterns
      this.analyzeUsage(ast);

      // Step 2: Perform constant folding if enabled
      if (this.options.constantFolding) {
        this.performConstantFolding(ast);
      }

      // Step 3: Remove unused variables
      if (this.options.removeUnusedVariables) {
        this.removeUnusedVariables(ast);
      }

      // Step 4: Remove unused functions
      this.removeUnusedFunctions(ast);

      // Step 5: Remove unused event handlers
      if (this.options.removeUnusedEventHandlers) {
        this.removeUnusedEventHandlers(ast);
      }

      // Step 6: Remove unused server functions
      if (this.options.removeUnusedServerFunctions) {
        this.removeUnusedServerFunctions(ast);
      }

      // Step 7: Remove unreachable code
      if (this.options.removeUnreachableCode) {
        this.removeUnreachableCode(ast);
      }

      // Step 8: Inline small functions
      if (this.options.inlineSmallFunctions) {
        this.inlineSmallFunctions(ast);
      }

      // Step 9: Remove empty blocks
      if (this.options.removeEmptyBlocks) {
        this.removeEmptyBlocks(ast);
      }

      // Step 10: Remove unused CSS rules
      if (this.options.removeUnusedCSS) {
        this.removeUnusedCSSRules(ast);
      }

      return ast;
    } catch (error) {
      const optimizationError = new OptimizationError(
        `Dead code elimination failed: ${error instanceof Error ? error.message : String(error)}`,
        { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } },
        OptimizationType.DEAD_CODE_ELIMINATION
      );
      this.errors.push(optimizationError);
      throw optimizationError;
    }
  }

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

  /**
   * Get optimization warnings
   */
  getWarnings(): OptimizationError[] {
    return [...this.warnings];
  }

  /**
   * Reset internal state
   */
  private reset(): void {
    this.usageAnalysis = {
      usedVariables: new Set(),
      usedFunctions: new Set(),
      usedEventHandlers: new Set(),
      usedCSSSelectors: new Set(),
      usedServerFunctions: new Set(),
      reachableStatements: new Set(),
      constantExpressions: new Map()
    };
    this.errors = [];
    this.warnings = [];
  }

  /**
   * Analyze usage patterns in the component
   */
  private analyzeUsage(ast: ComponentAST): void {
    const component = ast.component;

    // Analyze client block
    if (component.clientBlock) {
      this.analyzeClientBlockUsage(component.clientBlock);
    }

    // Analyze server block
    if (component.serverBlock) {
      this.analyzeServerBlockUsage(component.serverBlock);
    }

    // Analyze markup block
    this.analyzeMarkupBlockUsage(component.markupBlock);

    // Analyze style block
    if (component.styleBlock) {
      this.analyzeStyleBlockUsage(component.styleBlock);
    }
  }

  /**
   * Analyze usage in client block
   */
  private analyzeClientBlockUsage(clientBlock: ClientBlockNode): void {
    // Analyze reactive variables usage
    for (const variable of clientBlock.reactiveVariables) {
      this.usageAnalysis.usedVariables.add(variable.name);
    }

    // Analyze functions usage
    for (const func of clientBlock.functions) {
      this.usageAnalysis.usedFunctions.add(func.name);
      this.analyzeStatementUsage(func.body);
    }

    // Analyze event handlers usage
    for (const handler of clientBlock.eventHandlers) {
      this.usageAnalysis.usedEventHandlers.add(handler.eventName);
      if (typeof handler.handler === 'object' && 'expressionType' in handler.handler) {
        this.analyzeExpressionUsage(handler.handler as ExpressionNode);
      }
    }
  }

  /**
   * Analyze usage in server block
   */
  private analyzeServerBlockUsage(serverBlock: ServerBlockNode): void {
    // Analyze server functions usage
    for (const func of serverBlock.functions) {
      if (func.isPublic) {
        this.usageAnalysis.usedServerFunctions.add(func.name);
      }
      this.analyzeStatementUsage(func.body);
    }
  }

  /**
   * Analyze usage in markup block
   */
  private analyzeMarkupBlockUsage(markupBlock: MarkupBlockNode): void {
    // Analyze interpolations
    for (const interpolation of markupBlock.interpolations) {
      this.analyzeExpressionUsage(interpolation.expression);
    }

    // Analyze HTML elements
    for (const element of markupBlock.elements) {
      this.analyzeHTMLElementUsage(element);
    }
  }

  /**
   * Analyze usage in HTML element
   */
  private analyzeHTMLElementUsage(element: HTMLElementNode): void {
    // Analyze attributes
    for (const attr of element.attributes) {
      if (typeof attr.value === 'object') {
        this.analyzeExpressionUsage(attr.value as ExpressionNode);
      }
    }

    // Analyze children
    for (const child of element.children) {
      if (child.type === 'HTMLElement') {
        this.analyzeHTMLElementUsage(child as HTMLElementNode);
      } else if (child.type === 'Interpolation') {
        this.analyzeExpressionUsage((child as InterpolationNode).expression);
      }
    }
  }

  /**
   * Analyze usage in style block
   */
  private analyzeStyleBlockUsage(styleBlock: any): void {
    for (const rule of styleBlock.rules) {
      this.usageAnalysis.usedCSSSelectors.add(rule.selector);
    }
  }

  /**
   * Analyze expression usage
   */
  private analyzeExpressionUsage(expr: ExpressionNode): void {
    if (!expr) return;

    switch (expr.expressionType) {
      case ExpressionType.IDENTIFIER:
        if (expr.identifier) {
          this.usageAnalysis.usedVariables.add(expr.identifier);
        }
        break;

      case ExpressionType.CALL:
        if (expr.callee && expr.callee.identifier) {
          this.usageAnalysis.usedFunctions.add(expr.callee.identifier);
        }
        if (expr.arguments) {
          for (const arg of expr.arguments) {
            this.analyzeExpressionUsage(arg);
          }
        }
        break;

      case ExpressionType.BINARY:
        if (expr.left) this.analyzeExpressionUsage(expr.left);
        if (expr.right) this.analyzeExpressionUsage(expr.right);
        break;

      case ExpressionType.UNARY:
        if (expr.right) this.analyzeExpressionUsage(expr.right);
        break;

      case ExpressionType.MEMBER:
        if (expr.object) this.analyzeExpressionUsage(expr.object);
        if (expr.property) this.analyzeExpressionUsage(expr.property);
        break;

      case ExpressionType.ASSIGNMENT:
        if (expr.left) this.analyzeExpressionUsage(expr.left);
        if (expr.right) this.analyzeExpressionUsage(expr.right);
        break;
    }
  }

  /**
   * Analyze statement usage
   */
  private analyzeStatementUsage(statements: StatementNode[]): void {
    for (const stmt of statements) {
      this.usageAnalysis.reachableStatements.add(stmt);

      switch (stmt.statementType) {
        case StatementType.EXPRESSION:
          if (stmt.expression) {
            this.analyzeExpressionUsage(stmt.expression);
          }
          break;

        case StatementType.BLOCK:
          if (stmt.body) {
            this.analyzeStatementUsage(stmt.body);
          }
          break;

        case StatementType.IF:
          if (stmt.condition) {
            this.analyzeExpressionUsage(stmt.condition);
          }
          if (stmt.body) {
            this.analyzeStatementUsage(stmt.body);
          }
          break;

        case StatementType.FOR:
        case StatementType.WHILE:
          if (stmt.init) this.analyzeStatementUsage([stmt.init]);
          if (stmt.condition) this.analyzeExpressionUsage(stmt.condition);
          if (stmt.update) this.analyzeExpressionUsage(stmt.update);
          if (stmt.body) this.analyzeStatementUsage(stmt.body);
          break;

        case StatementType.RETURN:
          if (stmt.expression) {
            this.analyzeExpressionUsage(stmt.expression);
          }
          break;

        case StatementType.VARIABLE_DECLARATION:
          if (stmt.expression) {
            this.analyzeExpressionUsage(stmt.expression);
          }
          break;
      }
    }
  }

  /**
   * Perform constant folding
   */
  private performConstantFolding(ast: ComponentAST): void {
    // This would implement constant folding logic
    // For now, we'll add a placeholder for the implementation
    let foldedConstants = 0;

    const foldExpression = (expr: ExpressionNode): ExpressionNode => {
      if (!expr) return expr;

      switch (expr.expressionType) {
        case ExpressionType.BINARY:
          if (expr.left && expr.right &&
              expr.left.expressionType === ExpressionType.LITERAL &&
              expr.right.expressionType === ExpressionType.LITERAL) {
            // Fold binary operations on literals
            const left = expr.left.value;
            const right = expr.right.value;
            const operator = expr.operator;

            let result: any;
            switch (operator) {
              case '+':
                result = left + right;
                break;
              case '-':
                result = left - right;
                break;
              case '*':
                result = left * right;
                break;
              case '/':
                result = left / right;
                break;
              default:
                return expr;
            }

            foldedConstants++;
            return {
              ...expr,
              expressionType: ExpressionType.LITERAL,
              value: result
            };
          }
          break;
      }

      return expr;
    };

    // Apply constant folding to the entire AST
    this.applyExpressionTransformation(ast, foldExpression);
  }

  /**
   * Remove unused variables
   */
  private removeUnusedVariables(ast: ComponentAST): void {
    if (!ast.component.clientBlock) return;

    const originalCount = ast.component.clientBlock.reactiveVariables.length;
    ast.component.clientBlock.reactiveVariables = ast.component.clientBlock.reactiveVariables.filter(
      variable => this.usageAnalysis.usedVariables.has(variable.name)
    );

    const removedCount = originalCount - ast.component.clientBlock.reactiveVariables.length;
    if (removedCount > 0) {
      this.warnings.push(new OptimizationError(
        `Removed ${removedCount} unused reactive variables`,
        { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } },
        OptimizationType.DEAD_CODE_ELIMINATION
      ));
    }
  }

  /**
   * Remove unused functions
   */
  private removeUnusedFunctions(ast: ComponentAST): void {
    if (!ast.component.clientBlock) return;

    const originalCount = ast.component.clientBlock.functions.length;
    ast.component.clientBlock.functions = ast.component.clientBlock.functions.filter(
      func => this.usageAnalysis.usedFunctions.has(func.name)
    );

    const removedCount = originalCount - ast.component.clientBlock.functions.length;
    if (removedCount > 0) {
      this.warnings.push(new OptimizationError(
        `Removed ${removedCount} unused functions`,
        { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } },
        OptimizationType.DEAD_CODE_ELIMINATION
      ));
    }
  }

  /**
   * Remove unused event handlers
   */
  private removeUnusedEventHandlers(ast: ComponentAST): void {
    if (!ast.component.clientBlock) return;

    const originalCount = ast.component.clientBlock.eventHandlers.length;
    ast.component.clientBlock.eventHandlers = ast.component.clientBlock.eventHandlers.filter(
      handler => this.usageAnalysis.usedEventHandlers.has(handler.eventName)
    );

    const removedCount = originalCount - ast.component.clientBlock.eventHandlers.length;
    if (removedCount > 0) {
      this.warnings.push(new OptimizationError(
        `Removed ${removedCount} unused event handlers`,
        { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } },
        OptimizationType.DEAD_CODE_ELIMINATION
      ));
    }
  }

  /**
   * Remove unused server functions
   */
  private removeUnusedServerFunctions(ast: ComponentAST): void {
    if (!ast.component.serverBlock) return;

    const originalCount = ast.component.serverBlock.functions.length;
    ast.component.serverBlock.functions = ast.component.serverBlock.functions.filter(
      func => func.isPublic || this.usageAnalysis.usedServerFunctions.has(func.name)
    );

    const removedCount = originalCount - ast.component.serverBlock.functions.length;
    if (removedCount > 0) {
      this.warnings.push(new OptimizationError(
        `Removed ${removedCount} unused server functions`,
        { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } },
        OptimizationType.DEAD_CODE_ELIMINATION
      ));
    }
  }

  /**
   * Remove unreachable code
   */
  private removeUnreachableCode(ast: ComponentAST): void {
    // This would implement unreachable code detection and removal
    // For now, we'll add a placeholder for the implementation
    const removeUnreachableStatements = (statements: StatementNode[]): StatementNode[] => {
      return statements.filter(stmt => this.usageAnalysis.reachableStatements.has(stmt));
    };

    // Apply to client block functions
    if (ast.component.clientBlock) {
      for (const func of ast.component.clientBlock.functions) {
        func.body = removeUnreachableStatements(func.body);
      }
    }

    // Apply to server block functions
    if (ast.component.serverBlock) {
      for (const func of ast.component.serverBlock.functions) {
        func.body = removeUnreachableStatements(func.body);
      }
    }
  }

  /**
   * Inline small functions
   */
  private inlineSmallFunctions(ast: ComponentAST): void {
    if (!ast.component.clientBlock) return;

    const inlinedFunctions: string[] = [];

    for (const func of ast.component.clientBlock.functions) {
      if (this.shouldInlineFunction(func)) {
        // This would implement the actual inlining logic
        // For now, we'll mark it for inlining
        inlinedFunctions.push(func.name);
      }
    }

    if (inlinedFunctions.length > 0) {
      this.warnings.push(new OptimizationError(
        `Marked ${inlinedFunctions.length} functions for inlining`,
        { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } },
        OptimizationType.INLINE_EXPANSION
      ));
    }
  }

  /**
   * Check if a function should be inlined
   */
  private shouldInlineFunction(func: FunctionNode): boolean {
    if (!this.options.inlineSmallFunctions) return false;
    if (!this.options.maxInlineSize) return false;

    // Count AST nodes in function body
    let nodeCount = 0;
    const countNodes = (statements: StatementNode[]): void => {
      for (const stmt of statements) {
        nodeCount++;
        if (stmt.body) {
          countNodes(stmt.body);
        }
        if (stmt.expression) {
          nodeCount++;
        }
      }
    };

    countNodes(func.body);
    return nodeCount <= this.options.maxInlineSize!;
  }

  /**
   * Remove empty blocks
   */
  private removeEmptyBlocks(ast: ComponentAST): void {
    let removedBlocks = 0;

    const removeEmptyBlocksFromStatements = (statements: StatementNode[]): StatementNode[] => {
      return statements.filter(stmt => {
        if (stmt.statementType === StatementType.BLOCK) {
          if (!stmt.body || stmt.body.length === 0) {
            removedBlocks++;
            return false;
          }
          stmt.body = removeEmptyBlocksFromStatements(stmt.body);
        }
        return true;
      });
    };

    // Apply to client block functions
    if (ast.component.clientBlock) {
      for (const func of ast.component.clientBlock.functions) {
        func.body = removeEmptyBlocksFromStatements(func.body);
      }
    }

    // Apply to server block functions
    if (ast.component.serverBlock) {
      for (const func of ast.component.serverBlock.functions) {
        func.body = removeEmptyBlocksFromStatements(func.body);
      }
    }

    if (removedBlocks > 0) {
      this.warnings.push(new OptimizationError(
        `Removed ${removedBlocks} empty blocks`,
        { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } },
        OptimizationType.DEAD_CODE_ELIMINATION
      ));
    }
  }

  /**
   * Remove unused CSS rules
   */
  private removeUnusedCSSRules(ast: ComponentAST): void {
    if (!ast.component.styleBlock) return;

    const originalCount = ast.component.styleBlock.rules.length;
    ast.component.styleBlock.rules = ast.component.styleBlock.rules.filter(
      rule => this.usageAnalysis.usedCSSSelectors.has(rule.selector)
    );

    const removedCount = originalCount - ast.component.styleBlock.rules.length;
    if (removedCount > 0) {
      this.warnings.push(new OptimizationError(
        `Removed ${removedCount} unused CSS rules`,
        { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } },
        OptimizationType.DEAD_CODE_ELIMINATION
      ));
    }
  }

  /**
   * Apply transformation to all expressions in the AST
   */
  private applyExpressionTransformation(
    ast: ComponentAST,
    transform: (expr: ExpressionNode) => ExpressionNode
  ): void {
    const transformExpression = (expr: ExpressionNode): ExpressionNode => {
      if (!expr) return expr;

      // Transform children first
      if (expr.left) expr.left = transformExpression(expr.left);
      if (expr.right) expr.right = transformExpression(expr.right);
      if (expr.callee) expr.callee = transformExpression(expr.callee);
      if (expr.object) expr.object = transformExpression(expr.object);
      if (expr.property) expr.property = transformExpression(expr.property);
      if (expr.arguments) {
        expr.arguments = expr.arguments.map(transformExpression);
      }

      // Apply transformation
      return transform(expr);
    };

    // Apply to client block
    if (ast.component.clientBlock) {
      for (const func of ast.component.clientBlock.functions) {
        for (const stmt of func.body) {
          if (stmt.expression) {
            stmt.expression = transformExpression(stmt.expression);
          }
        }
      }
    }

    // Apply to server block
    if (ast.component.serverBlock) {
      for (const func of ast.component.serverBlock.functions) {
        for (const stmt of func.body) {
          if (stmt.expression) {
            stmt.expression = transformExpression(stmt.expression);
          }
        }
      }
    }

    // Apply to markup block
    for (const interpolation of ast.component.markupBlock.interpolations) {
      interpolation.expression = transformExpression(interpolation.expression);
    }

    // Apply to HTML elements
    const transformHTMLElement = (element: HTMLElementNode): void => {
      for (const attr of element.attributes) {
        if (typeof attr.value === 'object') {
          attr.value = transformExpression(attr.value as ExpressionNode);
        }
      }
      for (const child of element.children) {
        if (child.type === 'HTMLElement') {
          transformHTMLElement(child as HTMLElementNode);
        } else if (child.type === 'Interpolation') {
          (child as InterpolationNode).expression = transformExpression((child as InterpolationNode).expression);
        }
      }
    };

    for (const element of ast.component.markupBlock.elements) {
      transformHTMLElement(element);
    }
  }
}
