/**
 * @fileoverview Tests for CSS-in-JS Compiler
 */

import { describe, expect, it } from 'vitest';
import type { CSSFunctionExpression, CSSObjectExpression, CSSTemplateExpression } from './css-in-js-compiler.js';
import { OrdoJSCSSInJSCompiler } from './css-in-js-compiler.js';

describe('OrdoJSCSSInJSCompiler', () => {
  let compiler: OrdoJSCSSInJSCompiler;

  beforeEach(() => {
    compiler = new OrdoJSCSSInJSCompiler();
  });

  describe('CSS Object Compilation', () => {
    it('should compile simple CSS object', () => {
      const cssObject: CSSObjectExpression = {
        type: 'CSSObject',
        properties: [
          { key: 'color', value: 'red', computed: false },
          { key: 'fontSize', value: '16px', computed: false }
        ]
      };

      const result = compiler.compile(cssObject, 'TestComponent');

      expect(result.css).toContain('color: red');
      expect(result.css).toContain('font-size: 16px');
      expect(result.className).toMatch(/^ordojs-css-testcomponent-\d+$/);
      expect(result.styleBlock.rules).toHaveLength(1);
      expect(result.styleBlock.rules[0].declarations).toHaveLength(2);
    });

    it('should convert camelCase properties to kebab-case', () => {
      const cssObject: CSSObjectExpression = {
        type: 'CSSObject',
        properties: [
          { key: 'backgroundColor', value: 'blue', computed: false },
          { key: 'borderRadius', value: '5px', computed: false },
          { key: 'marginTop', value: '10px', computed: false }
        ]
      };

      const result = compiler.compile(cssObject);

      expect(result.css).toContain('background-color: blue');
      expect(result.css).toContain('border-radius: 5px');
      expect(result.css).toContain('margin-top: 10px');
    });

    it('should handle numeric values by adding px unit', () => {
      const cssObject: CSSObjectExpression = {
        type: 'CSSObject',
        properties: [
          { key: 'width', value: 100, computed: false },
          { key: 'height', value: 200, computed: false },
          { key: 'zIndex', value: 1, computed: false }
        ]
      };

      const result = compiler.compile(cssObject);

      expect(result.css).toContain('width: 100px');
      expect(result.css).toContain('height: 200px');
      expect(result.css).toContain('z-index: 1px');
    });

    it('should handle nested CSS objects', () => {
      const cssObject: CSSObjectExpression = {
        type: 'CSSObject',
        properties: [
          { key: 'color', value: 'red', computed: false },
          {
            key: 'nested',
            value: {
              type: 'CSSObject',
              properties: [
                { key: 'fontSize', value: '14px', computed: false }
              ]
            },
            computed: false
          }
        ]
      };

      const result = compiler.compile(cssObject);

      expect(result.css).toContain('color: red');
      expect(result.css).toContain('font-size: 14px');
    });
  });

  describe('CSS Template Compilation', () => {
    it('should compile CSS template strings', () => {
      const cssTemplate: CSSTemplateExpression = {
        type: 'CSSTemplate',
        template: 'color: red; font-size: 16px; background: blue;',
        expressions: []
      };

      const result = compiler.compile(cssTemplate);

      expect(result.css).toContain('color: red');
      expect(result.css).toContain('font-size: 16px');
      expect(result.css).toContain('background: blue');
      expect(result.styleBlock.rules[0].declarations).toHaveLength(3);
    });

    it('should handle template expressions', () => {
      const cssTemplate: CSSTemplateExpression = {
        type: 'CSSTemplate',
        template: 'color: ${0}; font-size: ${1};',
        expressions: [
          { type: 'Expression', expressionType: 'LITERAL' as any, value: 'red', range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } } },
          { type: 'Expression', expressionType: 'LITERAL' as any, value: '16px', range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } } }
        ]
      };

      const result = compiler.compile(cssTemplate);

      // Template expressions are currently replaced with comments
      expect(result.css).toContain('/* expression 0 */');
      expect(result.css).toContain('/* expression 1 */');
    });

    it('should handle malformed CSS gracefully', () => {
      const cssTemplate: CSSTemplateExpression = {
        type: 'CSSTemplate',
        template: 'color red; font-size; background: blue',
        expressions: []
      };

      const result = compiler.compile(cssTemplate);

      // Should only parse valid declarations
      expect(result.css).toContain('background: blue');
      expect(result.styleBlock.rules[0].declarations).toHaveLength(1);
    });
  });

  describe('CSS Function Compilation', () => {
    it('should compile CSS function with string arguments', () => {
      const cssFunction: CSSFunctionExpression = {
        type: 'CSSFunction',
        functionName: 'css',
        arguments: [
          'color: red; font-size: 16px;',
          'background: blue; margin: 10px;'
        ]
      };

      const result = compiler.compile(cssFunction);

      expect(result.css).toContain('color: red');
      expect(result.css).toContain('font-size: 16px');
      expect(result.css).toContain('background: blue');
      expect(result.css).toContain('margin: 10px');
      expect(result.styleBlock.rules[0].declarations).toHaveLength(4);
    });

    it('should compile CSS function with mixed arguments', () => {
      const cssObject: CSSObjectExpression = {
        type: 'CSSObject',
        properties: [
          { key: 'color', value: 'red', computed: false }
        ]
      };

      const cssFunction: CSSFunctionExpression = {
        type: 'CSSFunction',
        functionName: 'styled',
        arguments: [
          'font-size: 16px;',
          cssObject
        ]
      };

      const result = compiler.compile(cssFunction);

      expect(result.css).toContain('font-size: 16px');
      expect(result.css).toContain('color: red');
    });
  });

  describe('Class Name Generation', () => {
    it('should generate unique class names', () => {
      const cssObject: CSSObjectExpression = {
        type: 'CSSObject',
        properties: [{ key: 'color', value: 'red', computed: false }]
      };

      const result1 = compiler.compile(cssObject);
      const result2 = compiler.compile(cssObject);

      expect(result1.className).not.toBe(result2.className);
      expect(result1.className).toMatch(/^ordojs-css-\d+$/);
      expect(result2.className).toMatch(/^ordojs-css-\d+$/);
    });

    it('should include component name in class name when provided', () => {
      const cssObject: CSSObjectExpression = {
        type: 'CSSObject',
        properties: [{ key: 'color', value: 'red', computed: false }]
      };

      const result = compiler.compile(cssObject, 'MyComponent');

      expect(result.className).toMatch(/^ordojs-css-mycomponent-\d+$/);
    });

    it('should use custom class prefix when configured', () => {
      const customCompiler = new OrdoJSCSSInJSCompiler({ classPrefix: 'custom-prefix' });
      const cssObject: CSSObjectExpression = {
        type: 'CSSObject',
        properties: [{ key: 'color', value: 'red', computed: false }]
      };

      const result = customCompiler.compile(cssObject);

      expect(result.className).toMatch(/^custom-prefix-\d+$/);
    });
  });

  describe('Multiple Expression Compilation', () => {
    it('should compile multiple expressions into a single rule', () => {
      const cssObject: CSSObjectExpression = {
        type: 'CSSObject',
        properties: [{ key: 'color', value: 'red', computed: false }]
      };

      const cssTemplate: CSSTemplateExpression = {
        type: 'CSSTemplate',
        template: 'font-size: 16px; background: blue;',
        expressions: []
      };

      const result = compiler.compileMultiple([cssObject, cssTemplate], 'TestComponent');

      expect(result.css).toContain('color: red');
      expect(result.css).toContain('font-size: 16px');
      expect(result.css).toContain('background: blue');
      expect(result.styleBlock.rules).toHaveLength(1);
      expect(result.styleBlock.rules[0].declarations).toHaveLength(3);
    });
  });

  describe('Static Factory Methods', () => {
    it('should create CSS object from JavaScript object', () => {
      const jsObject = {
        color: 'red',
        fontSize: '16px',
        nested: {
          backgroundColor: 'blue'
        }
      };

      const cssObject = OrdoJSCSSInJSCompiler.createCSSObject(jsObject);

      expect(cssObject.type).toBe('CSSObject');
      expect(cssObject.properties).toHaveLength(3);
      expect(cssObject.properties[0].key).toBe('color');
      expect(cssObject.properties[0].value).toBe('red');
      expect(cssObject.properties[2].key).toBe('nested');
      expect(cssObject.properties[2].value).toEqual({
        type: 'CSSObject',
        properties: [{ key: 'backgroundColor', value: 'blue', computed: false }]
      });
    });

    it('should create CSS template expression', () => {
      const template = OrdoJSCSSInJSCompiler.createCSSTemplate('color: red; font-size: 16px;');

      expect(template.type).toBe('CSSTemplate');
      expect(template.template).toBe('color: red; font-size: 16px;');
      expect(template.expressions).toEqual([]);
    });

    it('should create CSS function expression', () => {
      const cssFunction = OrdoJSCSSInJSCompiler.createCSSFunction('css', ['color: red;']);

      expect(cssFunction.type).toBe('CSSFunction');
      expect(cssFunction.functionName).toBe('css');
      expect(cssFunction.arguments).toEqual(['color: red;']);
    });
  });

  describe('Configuration Options', () => {
    it('should respect scoped option', () => {
      const compiler = new OrdoJSCSSInJSCompiler({ scoped: false });
      const cssObject: CSSObjectExpression = {
        type: 'CSSObject',
        properties: [{ key: 'color', value: 'red', computed: false }]
      };

      const result = compiler.compile(cssObject);

      expect(result.styleBlock.scoped).toBe(false);
    });

    it('should use custom class prefix', () => {
      const compiler = new OrdoJSCSSInJSCompiler({ classPrefix: 'my-app' });
      const cssObject: CSSObjectExpression = {
        type: 'CSSObject',
        properties: [{ key: 'color', value: 'red', computed: false }]
      };

      const result = compiler.compile(cssObject);

      expect(result.className).toMatch(/^my-app-\d+$/);
    });
  });

  describe('Error Handling', () => {
    it('should throw error for unsupported expression types', () => {
      const invalidExpression = {
        type: 'UnsupportedType'
      } as any;

      expect(() => {
        compiler.compile(invalidExpression);
      }).toThrow('Unsupported CSS-in-JS expression type: UnsupportedType');
    });

    it('should handle empty expressions gracefully', () => {
      const emptyObject: CSSObjectExpression = {
        type: 'CSSObject',
        properties: []
      };

      const result = compiler.compile(emptyObject);

      expect(result.css).toContain(`.${result.className} {\n\n}`);
      expect(result.styleBlock.rules[0].declarations).toHaveLength(0);
    });
  });

  describe('CSS Generation', () => {
    it('should generate properly formatted CSS', () => {
      const cssObject: CSSObjectExpression = {
        type: 'CSSObject',
        properties: [
          { key: 'color', value: 'red', computed: false },
          { key: 'fontSize', value: '16px', computed: false }
        ]
      };

      const result = compiler.compile(cssObject);

      expect(result.css).toMatch(/\.[\w-]+ \{\n  color: red;\n  font-size: 16px;\n\}/);
    });

    it('should handle important declarations', () => {
      const cssTemplate: CSSTemplateExpression = {
        type: 'CSSTemplate',
        template: 'color: red !important; font-size: 16px;',
        expressions: []
      };

      const result = compiler.compile(cssTemplate);

      expect(result.css).toContain('color: red !important');
      expect(result.styleBlock.rules[0].declarations[0].important).toBe(true);
      expect(result.styleBlock.rules[0].declarations[1].important).toBe(false);
    });
  });
});
