/**
 * @fileoverview Tests for OrdoJS DOM Optimizer
 */

import { beforeEach, describe, expect, it } from 'vitest';
import {
    DirectiveType,
    ExpressionType,
    type AttributeNode,
    type ClientBlockNode,
    type ComponentAST,
    type ComponentNode,
    type ExpressionNode,
    type HTMLElementNode,
    type InterpolationNode,
    type MarkupBlockNode,
    type ReactiveVariableNode,
    type SourcePosition,
    type SourceRange
} from '../types/index.js';
import { DependencyAnalyzer } from './dependency-analyzer.js';
import { DOMOptimizer } from './dom-optimizer.js';

describe('DOMOptimizer', () => {
  let optimizer: DOMOptimizer;
  let analyzer: DependencyAnalyzer;
  let mockSourceRange: SourceRange;
  let mockSourcePosition: SourcePosition;

  beforeEach(() => {
    optimizer = new DOMOptimizer();
    analyzer = new DependencyAnalyzer();
    mockSourcePosition = { line: 1, column: 1, offset: 0 };
    mockSourceRange = { start: mockSourcePosition, end: mockSourcePosition };
  });

  // Helper function to create a mock expression
  function createMockExpression(
    type: ExpressionType,
    identifier?: string,
    value?: any,
    left?: ExpressionNode,
    right?: ExpressionNode,
    operator?: string
  ): ExpressionNode {
    return {
      type: 'Expression',
      expressionType: type,
      identifier,
      value,
      left,
      right,
      operator,
      range: mockSourceRange
    };
  }

  // Helper function to create a mock reactive variable
  function createMockReactiveVariable(
    name: string,
    initialValue: ExpressionNode,
    isConst = false
  ): ReactiveVariableNode {
    return {
      type: 'ReactiveVariable',
      name,
      initialValue,
      dataType: { name: 'any', isArray: false, isOptional: false, genericTypes: [] },
      isConst,
      range: mockSourceRange
    };
  }

  // Helper function to create a mock interpolation
  function createMockInterpolation(expression: ExpressionNode): InterpolationNode {
    return {
      type: 'Interpolation',
      expression,
      range: mockSourceRange
    };
  }

  // Helper function to create a mock attribute
  function createMockAttribute(
    name: string,
    value: string | ExpressionNode,
    isDirective = false,
    directiveType?: DirectiveType
  ): AttributeNode {
    return {
      type: 'Attribute',
      name,
      value,
      isDirective,
      directiveType,
      range: mockSourceRange
    };
  }

  // Helper function to create a mock HTML element
  function createMockHTMLElement(
    tagName: string,
    attributes: AttributeNode[] = [],
    children: any[] = []
  ): HTMLElementNode {
    return {
      type: 'HTMLElement',
      tagName,
      attributes,
      children,
      isSelfClosing: false,
      isVoidElement: false,
      range: mockSourceRange
    };
  }

  // Helper function to create a complete component AST
  function createMockComponentAST(
    reactiveVariables: ReactiveVariableNode[],
    elements: HTMLElementNode[],
    interpolations: InterpolationNode[] = []
  ): ComponentAST {
    const clientBlock: ClientBlockNode = {
      type: 'ClientBlock',
      reactiveVariables,
      computedValues: [],
      eventHandlers: [],
      functions: [],
      lifecycle: [],
      range: mockSourceRange,
      children: reactiveVariables
    };

    const markupBlock: MarkupBlockNode = {
      type: 'MarkupBlock',
      elements,
      textNodes: [],
      interpolations,
      range: mockSourceRange,
      children: [...elements, ...interpolations]
    };

    const component: ComponentNode = {
      type: 'Component',
      name: 'TestComponent',
      props: [],
      clientBlock,
      markupBlock,
      range: mockSourceRange,
      children: [clientBlock, markupBlock]
    };

    return {
      component,
      dependencies: [],
      exports: [],
      sourceMap: {
        version: 3,
        sources: [],
        names: [],
        mappings: '',
        sourcesContent: []
      }
    };
  }

  describe('Basic DOM Optimization', () => {
    it('should generate update code for text interpolations', () => {
      const countVar = createMockReactiveVariable(
        'count',
        createMockExpression(ExpressionType.LITERAL, undefined, 0)
      );

      const interpolation = createMockInterpolation(
        createMockExpression(ExpressionType.IDENTIFIER, 'count')
      );

      const ast = createMockComponentAST([countVar], [], [interpolation]);
      const dependencyGraph = analyzer.analyze(ast);

      const result = optimizer.optimize(ast, dependencyGraph);

      expect(result.updateCode).toContain('updateFunctions');
      expect(result.updateCode).toContain('textContent');
      expect(result.updateCode).toContain('component.state.count');
    });

    it('should generate update code for attribute bindings', () => {
      const titleVar = createMockReactiveVariable(
        'title',
        createMockExpression(ExpressionType.LITERAL, undefined, 'Hello')
      );

      const titleAttr = createMockAttribute(
        'title',
        createMockExpression(ExpressionType.IDENTIFIER, 'title'),
        true
      );

      const element = createMockHTMLElement('div', [titleAttr]);
      const ast = createMockComponentAST([titleVar], [element]);
      const dependencyGraph = analyzer.analyze(ast);

      const result = optimizer.optimize(ast, dependencyGraph);

      expect(result.updateCode).toContain('setAttribute');
      expect(result.updateCode).toContain('title');
      expect(result.updateCode).toContain('component.state.title');
    });

    it('should generate setup code for two-way bindings', () => {
      const valueVar = createMockReactiveVariable(
        'value',
        createMockExpression(ExpressionType.LITERAL, undefined, '')
      );

      const bindAttr = createMockAttribute(
        'bind:value',
        createMockExpression(ExpressionType.IDENTIFIER, 'value'),
        true,
        DirectiveType.BIND
      );

      const element = createMockHTMLElement('input', [bindAttr]);
      const ast = createMockComponentAST([valueVar], [element]);
      const dependencyGraph = analyzer.analyze(ast);

      const result = optimizer.optimize(ast, dependencyGraph);

      expect(result.setupCode).toContain('setupTwoWayBindings');
      expect(result.setupCode).toContain('addEventListener');
      expect(result.setupCode).toContain('input');
      expect(result.setupCode).toContain('component.state.value');
    });

    it('should generate class toggle updates', () => {
      const activeVar = createMockReactiveVariable(
        'active',
        createMockExpression(ExpressionType.LITERAL, undefined, false)
      );

      const classAttr = createMockAttribute(
        'class:active',
        createMockExpression(ExpressionType.IDENTIFIER, 'active'),
        true,
        DirectiveType.CLASS
      );

      const element = createMockHTMLElement('div', [classAttr]);
      const ast = createMockComponentAST([activeVar], [element]);
      const dependencyGraph = analyzer.analyze(ast);

      const result = optimizer.optimize(ast, dependencyGraph);

      expect(result.updateCode).toContain('classList.toggle');
      expect(result.updateCode).toContain('active');
      expect(result.updateCode).toContain('component.state.active');
    });

    it('should generate style updates', () => {
      const colorVar = createMockReactiveVariable(
        'color',
        createMockExpression(ExpressionType.LITERAL, undefined, 'red')
      );

      const styleAttr = createMockAttribute(
        'style:color',
        createMockExpression(ExpressionType.IDENTIFIER, 'color'),
        true,
        DirectiveType.STYLE
      );

      const element = createMockHTMLElement('div', [styleAttr]);
      const ast = createMockComponentAST([colorVar], [element]);
      const dependencyGraph = analyzer.analyze(ast);

      const result = optimizer.optimize(ast, dependencyGraph);

      expect(result.updateCode).toContain('el.style.color');
      expect(result.updateCode).toContain('component.state.color');
    });
  });

  describe('Selective Updates', () => {
    it('should generate selective updates for specific variables', () => {
      const countVar = createMockReactiveVariable(
        'count',
        createMockExpression(ExpressionType.LITERAL, undefined, 0)
      );

      const nameVar = createMockReactiveVariable(
        'name',
        createMockExpression(ExpressionType.LITERAL, undefined, 'test')
      );

      const countInterpolation = createMockInterpolation(
        createMockExpression(ExpressionType.IDENTIFIER, 'count')
      );

      const nameInterpolation = createMockInterpolation(
        createMockExpression(ExpressionType.IDENTIFIER, 'name')
      );

      const ast = createMockComponentAST(
        [countVar, nameVar],
        [],
        [countInterpolation, nameInterpolation]
      );
      const dependencyGraph = analyzer.analyze(ast);

      // Generate selective updates for only 'count'
      const selectiveCode = optimizer.generateSelectiveUpdates(ast, dependencyGraph, ['count']);

      expect(selectiveCode).toContain('component.state.count');
      expect(selectiveCode).not.toContain('component.state.name');
    });

    it('should handle complex expressions in selective updates', () => {
      const aVar = createMockReactiveVariable(
        'a',
        createMockExpression(ExpressionType.LITERAL, undefined, 1)
      );

      const bVar = createMockReactiveVariable(
        'b',
        createMockExpression(ExpressionType.LITERAL, undefined, 2)
      );

      // Create expression: a + b
      const complexInterpolation = createMockInterpolation(
        createMockExpression(
          ExpressionType.BINARY,
          undefined,
          undefined,
          createMockExpression(ExpressionType.IDENTIFIER, 'a'),
          createMockExpression(ExpressionType.IDENTIFIER, 'b'),
          '+'
        )
      );

      const ast = createMockComponentAST([aVar, bVar], [], [complexInterpolation]);
      const dependencyGraph = analyzer.analyze(ast);

      // Generate selective updates for only 'a'
      const selectiveCode = optimizer.generateSelectiveUpdates(ast, dependencyGraph, ['a']);

      expect(selectiveCode).toContain('component.state.a');
      expect(selectiveCode).toContain('component.state.b');
      expect(selectiveCode).toContain('+');
    });
  });

  describe('Batched Updates', () => {
    it('should batch similar update operations', () => {
      const var1 = createMockReactiveVariable(
        'var1',
        createMockExpression(ExpressionType.LITERAL, undefined, 'value1')
      );

      const var2 = createMockReactiveVariable(
        'var2',
        createMockExpression(ExpressionType.LITERAL, undefined, 'value2')
      );

      const attr1 = createMockAttribute(
        'title',
        createMockExpression(ExpressionType.IDENTIFIER, 'var1'),
        true
      );

      const attr2 = createMockAttribute(
        'alt',
        createMockExpression(ExpressionType.IDENTIFIER, 'var2'),
        true
      );

      const element1 = createMockHTMLElement('div', [attr1]);
      const element2 = createMockHTMLElement('img', [attr2]);

      const ast = createMockComponentAST([var1, var2], [element1, element2]);
      const dependencyGraph = analyzer.analyze(ast);

      const result = optimizer.optimize(ast, dependencyGraph);

      // Should have separate batches for each variable
      expect(result.updateCode).toContain('batch_0');
      expect(result.updateCode).toContain('batch_1');
      expect(result.updateCode).toContain('updateFunctions');
    });

    it('should prioritize update operations correctly', () => {
      const visibleVar = createMockReactiveVariable(
        'visible',
        createMockExpression(ExpressionType.LITERAL, undefined, true)
      );

      const colorVar = createMockReactiveVariable(
        'color',
        createMockExpression(ExpressionType.LITERAL, undefined, 'red')
      );

      const textVar = createMockReactiveVariable(
        'text',
        createMockExpression(ExpressionType.LITERAL, undefined, 'Hello')
      );

      const styleAttr = createMockAttribute(
        'style:color',
        createMockExpression(ExpressionType.IDENTIFIER, 'color'),
        true,
        DirectiveType.STYLE
      );

      const element = createMockHTMLElement('div', [styleAttr]);

      const textInterpolation = createMockInterpolation(
        createMockExpression(ExpressionType.IDENTIFIER, 'text')
      );

      const ast = createMockComponentAST(
        [visibleVar, colorVar, textVar],
        [element],
        [textInterpolation]
      );
      const dependencyGraph = analyzer.analyze(ast);

      const result = optimizer.optimize(ast, dependencyGraph);

      // Should contain different types of updates with proper prioritization
      expect(result.updateCode).toContain('el.style.color');
      expect(result.updateCode).toContain('textContent');
    });
  });

  describe('Complex Scenarios', () => {
    it('should handle nested elements with multiple bindings', () => {
      // Simplify the test to use direct variable references instead of member expressions
      const activeVar = createMockReactiveVariable(
        'active',
        createMockExpression(ExpressionType.LITERAL, undefined, true)
      );

      const colorVar = createMockReactiveVariable(
        'color',
        createMockExpression(ExpressionType.LITERAL, undefined, 'blue')
      );

      const classAttr = createMockAttribute(
        'class:active',
        createMockExpression(ExpressionType.IDENTIFIER, 'active'),
        true,
        DirectiveType.CLASS
      );

      const styleAttr = createMockAttribute(
        'style:color',
        createMockExpression(ExpressionType.IDENTIFIER, 'color'),
        true,
        DirectiveType.STYLE
      );

      const parentElement = createMockHTMLElement('div', [classAttr]);
      const childElement = createMockHTMLElement('span', [styleAttr]);
      parentElement.children = [childElement];

      const ast = createMockComponentAST([activeVar, colorVar], [parentElement]);
      const dependencyGraph = analyzer.analyze(ast);

      const result = optimizer.optimize(ast, dependencyGraph);

      expect(result.updateCode).toContain('classList.toggle');
      expect(result.updateCode).toContain('el.style.color');
      expect(result.updateCode).toContain('component.state.active');
      expect(result.updateCode).toContain('component.state.color');
    });

    it('should handle multiple two-way bindings', () => {
      const nameVar = createMockReactiveVariable(
        'name',
        createMockExpression(ExpressionType.LITERAL, undefined, '')
      );

      const emailVar = createMockReactiveVariable(
        'email',
        createMockExpression(ExpressionType.LITERAL, undefined, '')
      );

      const checkedVar = createMockReactiveVariable(
        'checked',
        createMockExpression(ExpressionType.LITERAL, undefined, false)
      );

      const nameBinding = createMockAttribute(
        'bind:value',
        createMockExpression(ExpressionType.IDENTIFIER, 'name'),
        true,
        DirectiveType.BIND
      );

      const emailBinding = createMockAttribute(
        'bind:value',
        createMockExpression(ExpressionType.IDENTIFIER, 'email'),
        true,
        DirectiveType.BIND
      );

      const checkedBinding = createMockAttribute(
        'bind:checked',
        createMockExpression(ExpressionType.IDENTIFIER, 'checked'),
        true,
        DirectiveType.BIND
      );

      const nameInput = createMockHTMLElement('input', [nameBinding]);
      const emailInput = createMockHTMLElement('input', [emailBinding]);
      const checkbox = createMockHTMLElement('input', [checkedBinding]);

      const ast = createMockComponentAST(
        [nameVar, emailVar, checkedVar],
        [nameInput, emailInput, checkbox]
      );
      const dependencyGraph = analyzer.analyze(ast);

      const result = optimizer.optimize(ast, dependencyGraph);

      expect(result.setupCode).toContain('component.state.name');
      expect(result.setupCode).toContain('component.state.email');
      expect(result.setupCode).toContain('component.state.checked');
      expect(result.setupCode).toContain('input');
      expect(result.setupCode).toContain('change');
    });

    it('should generate efficient code for large numbers of bindings', () => {
      const variables: ReactiveVariableNode[] = [];
      const elements: HTMLElementNode[] = [];

      // Create 10 reactive variables and corresponding elements
      for (let i = 0; i < 10; i++) {
        const variable = createMockReactiveVariable(
          `var${i}`,
          createMockExpression(ExpressionType.LITERAL, undefined, `value${i}`)
        );
        variables.push(variable);

        const attr = createMockAttribute(
          'title',
          createMockExpression(ExpressionType.IDENTIFIER, `var${i}`),
          true
        );

        const element = createMockHTMLElement('div', [attr]);
        elements.push(element);
      }

      const ast = createMockComponentAST(variables, elements);
      const dependencyGraph = analyzer.analyze(ast);

      const result = optimizer.optimize(ast, dependencyGraph);

      // Should generate efficient batched updates
      expect(result.updateCode).toContain('updateFunctions');
      // Each variable creates both a property update and an attribute update, so we expect 20 batches
      expect(result.updateCode.split('batch_').length - 1).toBe(20); // 2 batches per variable (property + attribute)
      expect(result.updateCode).toContain('querySelectorAll');
      expect(result.updateCode).toContain('forEach');
    });
  });

  describe('Error Handling and Edge Cases', () => {
    it('should handle components with no reactive variables', () => {
      const element = createMockHTMLElement('div');
      const ast = createMockComponentAST([], [element]);
      const dependencyGraph = analyzer.analyze(ast);

      const result = optimizer.optimize(ast, dependencyGraph);

      expect(result.updateCode).toContain('updateFunctions');
      expect(result.setupCode).toBe('');
      expect(result.cleanupCode).toBe('');
    });

    it('should handle empty markup blocks', () => {
      const variable = createMockReactiveVariable(
        'test',
        createMockExpression(ExpressionType.LITERAL, undefined, 'value')
      );

      const ast = createMockComponentAST([variable], []);
      const dependencyGraph = analyzer.analyze(ast);

      const result = optimizer.optimize(ast, dependencyGraph);

      expect(result.updateCode).toContain('updateFunctions');
      expect(result.setupCode).toBe('');
    });

    it('should handle expressions with no dependencies', () => {
      const literalInterpolation = createMockInterpolation(
        createMockExpression(ExpressionType.LITERAL, undefined, 'static text')
      );

      const ast = createMockComponentAST([], [], [literalInterpolation]);
      const dependencyGraph = analyzer.analyze(ast);

      const result = optimizer.optimize(ast, dependencyGraph);

      // Should still generate update code, but with no variable dependencies
      expect(result.updateCode).toContain('updateFunctions');
    });
  });
});
