/**
 * @fileoverview Tests for CSS Optimizer
 */

import { describe, expect, it } from 'vitest';
import type { CSSDeclarationNode, CSSRuleNode, ComponentAST, StyleBlockNode } from '../types/index.js';
import { OrdoJSCSSOptimizer } from './css-optimizer.js';

// Helper function to create a CSS declaration
function createDeclaration(property: string, value: string): CSSDeclarationNode {
  return {
    type: 'CSSDeclaration',
    property,
    value,
    important: false,
    range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
  };
}

// Helper function to create a CSS rule
function createRule(selector: string, declarations: CSSDeclarationNode[]): CSSRuleNode {
  return {
    type: 'CSSRule',
    selector,
    declarations,
    range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
  };
}

// Helper function to create a style block
function createStyleBlock(rules: CSSRuleNode[], scoped: boolean = true): StyleBlockNode {
  return {
    type: 'StyleBlock',
    rules,
    scoped,
    range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
  };
}

// Helper function to create a mock component AST
function createMockComponentAST(usedElements: string[] = [], usedClasses: string[] = []): ComponentAST {
  const mockElements = usedElements.map(tagName => ({
    type: 'HTMLElement',
    tagName,
    attributes: usedClasses.length > 0 ? [
      {
        name: 'class',
        value: usedClasses.join(' ')
      }
    ] : [],
    children: []
  }));

  // Also create elements for each class (in case no elements are specified)
  if (usedElements.length === 0 && usedClasses.length > 0) {
    mockElements.push({
      type: 'HTMLElement',
      tagName: 'div',
      attributes: [
        {
          name: 'class',
          value: usedClasses.join(' ')
        }
      ],
      children: []
    });
  }

  return {
    component: {
      type: 'Component',
      name: 'TestComponent',
      props: [],
      markupBlock: {
        type: 'MarkupBlock',
        elements: mockElements,
        textNodes: [],
        interpolations: [],
        range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
      },
      range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
    },
    dependencies: [],
    exports: [],
    sourceMap: {
      version: 3,
      sources: [],
      names: [],
      mappings: '',
      sourcesContent: []
    }
  } as ComponentAST;
}

describe('OrdoJSCSSOptimizer', () => {
  let optimizer: OrdoJSCSSOptimizer;

  beforeEach(() => {
    optimizer = new OrdoJSCSSOptimizer();
  });

  describe('Basic Optimization', () => {
    it('should optimize CSS with default options', () => {
      const styleBlock = createStyleBlock([
        createRule('.button', [
          createDeclaration('color', 'red'),
          createDeclaration('font-size', '16px')
        ])
      ]);

      const result = optimizer.optimize(styleBlock);

      expect(result.optimizedCSS).toBeTruthy();
      expect(result.originalSize).toBeGreaterThan(0);
      expect(result.optimizedSize).toBeGreaterThan(0);
      expect(result.compressionRatio).toBeGreaterThanOrEqual(0);
    });

    it('should minify CSS when minification is enabled', () => {
      const optimizer = new OrdoJSCSSOptimizer({ minify: true });
      const styleBlock = createStyleBlock([
        createRule('.button', [
          createDeclaration('color', 'red'),
          createDeclaration('font-size', '16px')
        ])
      ]);

      const result = optimizer.optimize(styleBlock);

      // Minified CSS should not contain newlines or extra spaces
      expect(result.optimizedCSS).not.toContain('\n');
      expect(result.optimizedCSS).not.toContain('  ');
      expect(result.optimizedCSS).toContain('.button{color:red;font-size:16px}');
    });

    it('should preserve CSS when minification is disabled', () => {
      const optimizer = new OrdoJSCSSOptimizer({ minify: false });
      const styleBlock = createStyleBlock([
        createRule('.button', [
          createDeclaration('color', 'red')
        ])
      ]);

      const result = optimizer.optimize(styleBlock);

      // Non-minified CSS should contain newlines and proper formatting
      expect(result.optimizedCSS).toContain('\n');
      expect(result.optimizedCSS).toContain('.button {\n  color: red;\n}');
    });
  });

  describe('Dead Code Elimination', () => {
    it('should remove unused CSS rules when component AST is provided', () => {
      const styleBlock = createStyleBlock([
        createRule('.used-class', [createDeclaration('color', 'red')]),
        createRule('.unused-class', [createDeclaration('color', 'blue')]),
        createRule('div', [createDeclaration('margin', '10px')])
      ]);

      const componentAST = createMockComponentAST(['div'], ['used-class']);
      const result = optimizer.optimize(styleBlock, componentAST);

      expect(result.removedRules).toBe(1);
      expect(result.optimizedCSS).toContain('used-class');
      expect(result.optimizedCSS).toContain('div');
      expect(result.optimizedCSS).not.toContain('unused-class');
    });

    it('should preserve global selectors', () => {
      const styleBlock = createStyleBlock([
        createRule(':root', [createDeclaration('--primary-color', 'blue')]),
        createRule('html', [createDeclaration('font-size', '16px')]),
        createRule('body', [createDeclaration('margin', '0')]),
        createRule('.unused-class', [createDeclaration('color', 'red')])
      ]);

      const componentAST = createMockComponentAST([], []);
      const result = optimizer.optimize(styleBlock, componentAST);

      expect(result.optimizedCSS).toContain(':root');
      expect(result.optimizedCSS).toContain('html');
      expect(result.optimizedCSS).toContain('body');
      expect(result.optimizedCSS).not.toContain('unused-class');
    });

    it('should handle complex selectors correctly', () => {
      const styleBlock = createStyleBlock([
        createRule('.parent .child', [createDeclaration('color', 'red')]),
        createRule('.parent > .child', [createDeclaration('color', 'blue')]),
        createRule('.unused .child', [createDeclaration('color', 'green')])
      ]);

      const componentAST = createMockComponentAST([], ['parent', 'child']);
      const result = optimizer.optimize(styleBlock, componentAST);



      expect(result.optimizedCSS).toContain('.parent .child');
      expect(result.optimizedCSS).toContain('.parent > .child');
      expect(result.optimizedCSS).not.toContain('.unused .child');
    });
  });

  describe('Rule Merging', () => {
    it('should merge duplicate selectors', () => {
      const styleBlock = createStyleBlock([
        createRule('.button', [createDeclaration('color', 'red')]),
        createRule('.button', [createDeclaration('font-size', '16px')]),
        createRule('.button', [createDeclaration('color', 'blue')]) // Should override red
      ]);

      const result = optimizer.optimize(styleBlock);

      expect(result.mergedRules).toBe(2);
      expect(result.optimizedCSS).toContain('color:blue'); // Should use the last value
      expect(result.optimizedCSS).toContain('font-size:16px');
      // Should only have one .button rule
      expect((result.optimizedCSS.match(/\.button/g) || []).length).toBe(1);
    });

    it('should not merge different selectors', () => {
      const styleBlock = createStyleBlock([
        createRule('.button', [createDeclaration('color', 'red')]),
        createRule('.link', [createDeclaration('color', 'blue')])
      ]);

      const result = optimizer.optimize(styleBlock);

      expect(result.mergedRules).toBe(0);
      expect(result.optimizedCSS).toContain('.button');
      expect(result.optimizedCSS).toContain('.link');
    });
  });

  describe('Empty Rule Removal', () => {
    it('should remove rules with no declarations', () => {
      const styleBlock = createStyleBlock([
        createRule('.button', [createDeclaration('color', 'red')]),
        createRule('.empty', []),
        createRule('.link', [createDeclaration('color', 'blue')])
      ]);

      const result = optimizer.optimize(styleBlock);

      expect(result.removedRules).toBe(1);
      expect(result.optimizedCSS).toContain('.button');
      expect(result.optimizedCSS).toContain('.link');
      expect(result.optimizedCSS).not.toContain('.empty');
    });
  });

  describe('Redundant Declaration Removal', () => {
    it('should remove duplicate declarations within the same rule', () => {
      const styleBlock = createStyleBlock([
        createRule('.button', [
          createDeclaration('color', 'red'),
          createDeclaration('font-size', '16px'),
          createDeclaration('color', 'blue'), // Should override red
          createDeclaration('font-size', '18px') // Should override 16px
        ])
      ]);

      const result = optimizer.optimize(styleBlock);

      expect(result.optimizedCSS).toContain('color:blue');
      expect(result.optimizedCSS).toContain('font-size:18px');
      expect(result.optimizedCSS).not.toContain('color:red');
      expect(result.optimizedCSS).not.toContain('font-size:16px');
    });
  });

  describe('Shorthand Optimization', () => {
    it('should optimize margin properties to shorthand', () => {
      const styleBlock = createStyleBlock([
        createRule('.box', [
          createDeclaration('margin-top', '10px'),
          createDeclaration('margin-right', '10px'),
          createDeclaration('margin-bottom', '10px'),
          createDeclaration('margin-left', '10px')
        ])
      ]);

      const result = optimizer.optimize(styleBlock);

      expect(result.optimizedDeclarations).toBe(1);
      expect(result.optimizedCSS).toContain('margin:10px');
      expect(result.optimizedCSS).not.toContain('margin-top');
      expect(result.optimizedCSS).not.toContain('margin-right');
      expect(result.optimizedCSS).not.toContain('margin-bottom');
      expect(result.optimizedCSS).not.toContain('margin-left');
    });

    it('should optimize padding properties to shorthand', () => {
      const styleBlock = createStyleBlock([
        createRule('.box', [
          createDeclaration('padding-top', '5px'),
          createDeclaration('padding-right', '10px'),
          createDeclaration('padding-bottom', '5px'),
          createDeclaration('padding-left', '10px')
        ])
      ]);

      const result = optimizer.optimize(styleBlock);

      expect(result.optimizedDeclarations).toBe(1);
      expect(result.optimizedCSS).toContain('padding:5px 10px');
    });

    it('should create four-value shorthand when all values are different', () => {
      const styleBlock = createStyleBlock([
        createRule('.box', [
          createDeclaration('margin-top', '1px'),
          createDeclaration('margin-right', '2px'),
          createDeclaration('margin-bottom', '3px'),
          createDeclaration('margin-left', '4px')
        ])
      ]);

      const result = optimizer.optimize(styleBlock);

      expect(result.optimizedCSS).toContain('margin:1px 2px 3px 4px');
    });

    it('should not create shorthand when not all properties are present', () => {
      const styleBlock = createStyleBlock([
        createRule('.box', [
          createDeclaration('margin-top', '10px'),
          createDeclaration('margin-right', '10px'),
          createDeclaration('color', 'red')
        ])
      ]);

      const result = optimizer.optimize(styleBlock);

      expect(result.optimizedCSS).toContain('margin-top:10px');
      expect(result.optimizedCSS).toContain('margin-right:10px');
      expect(result.optimizedCSS).not.toContain('margin:');
    });
  });

  describe('Declaration Sorting', () => {
    it('should sort declarations alphabetically', () => {
      const styleBlock = createStyleBlock([
        createRule('.button', [
          createDeclaration('z-index', '1'),
          createDeclaration('color', 'red'),
          createDeclaration('background', 'blue'),
          createDeclaration('font-size', '16px')
        ])
      ]);

      const result = optimizer.optimize(styleBlock);

      const css = result.optimizedCSS;
      const backgroundIndex = css.indexOf('background');
      const colorIndex = css.indexOf('color');
      const fontSizeIndex = css.indexOf('font-size');
      const zIndexIndex = css.indexOf('z-index');

      expect(backgroundIndex).toBeLessThan(colorIndex);
      expect(colorIndex).toBeLessThan(fontSizeIndex);
      expect(fontSizeIndex).toBeLessThan(zIndexIndex);
    });
  });

  describe('Bundle Optimization', () => {
    it('should optimize multiple style blocks into a single bundle', () => {
      const styleBlock1 = createStyleBlock([
        createRule('.button', [createDeclaration('color', 'red')])
      ]);

      const styleBlock2 = createStyleBlock([
        createRule('.link', [createDeclaration('color', 'blue')]),
        createRule('.button', [createDeclaration('font-size', '16px')])
      ]);

      const result = optimizer.optimizeBundle([styleBlock1, styleBlock2]);

      expect(result.optimizedCSS).toContain('.button');
      expect(result.optimizedCSS).toContain('.link');
      expect(result.mergedRules).toBe(1); // .button rules should be merged
    });
  });

  describe('Compression Metrics', () => {
    it('should calculate compression ratio correctly', () => {
      const styleBlock = createStyleBlock([
        createRule('.very-long-class-name-that-will-be-compressed', [
          createDeclaration('background-color', 'rgba(255, 255, 255, 0.5)'),
          createDeclaration('border-radius', '10px'),
          createDeclaration('box-shadow', '0 2px 4px rgba(0, 0, 0, 0.1)')
        ])
      ]);

      const result = optimizer.optimize(styleBlock);

      expect(result.originalSize).toBeGreaterThan(result.optimizedSize);
      expect(result.compressionRatio).toBeGreaterThan(0);
      expect(result.compressionRatio).toBeLessThanOrEqual(1);
    });

    it('should report optimization statistics', () => {
      const styleBlock = createStyleBlock([
        createRule('.button', [createDeclaration('color', 'red')]),
        createRule('.button', [createDeclaration('font-size', '16px')]),
        createRule('.empty', []),
        createRule('.unused', [createDeclaration('color', 'blue')])
      ]);

      const componentAST = createMockComponentAST([], ['button']);
      const result = optimizer.optimize(styleBlock, componentAST);



      expect(result.removedRules).toBe(2); // .empty and .unused
      expect(result.mergedRules).toBe(1); // .button rules merged
      expect(typeof result.originalSize).toBe('number');
      expect(typeof result.optimizedSize).toBe('number');
      expect(typeof result.compressionRatio).toBe('number');
    });
  });

  describe('Edge Cases', () => {
    it('should handle empty style blocks', () => {
      const styleBlock = createStyleBlock([]);

      const result = optimizer.optimize(styleBlock);

      expect(result.optimizedCSS).toBe('');
      expect(result.originalSize).toBe(0);
      expect(result.optimizedSize).toBe(0);
      expect(result.compressionRatio).toBe(0);
    });

    it('should handle style blocks with only empty rules', () => {
      const styleBlock = createStyleBlock([
        createRule('.empty1', []),
        createRule('.empty2', [])
      ]);

      const result = optimizer.optimize(styleBlock);

      expect(result.optimizedCSS).toBe('');
      expect(result.removedRules).toBe(2);
    });

    it('should handle pseudo-classes and pseudo-elements', () => {
      const styleBlock = createStyleBlock([
        createRule('.button:hover', [createDeclaration('color', 'blue')]),
        createRule('.button::before', [createDeclaration('content', '""')])
      ]);

      const componentAST = createMockComponentAST([], ['button']);
      const result = optimizer.optimize(styleBlock, componentAST);

      expect(result.optimizedCSS).toContain('.button:hover');
      expect(result.optimizedCSS).toContain('.button::before');
    });
  });
});
