/**
 * @fileoverview CSS-in-JS Compiler for OrdoJS Framework
 * Handles compilation of CSS-in-JS expressions to optimized CSS
 */

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

/**
 * CSS-in-JS compilation options
 */
export interface CSSInJSOptions {
  /**
   * Whether to generate scoped CSS
   */
  scoped?: boolean;

  /**
   * Prefix for generated class names
   */
  classPrefix?: string;

  /**
   * Whether to optimize generated CSS
   */
  optimize?: boolean;

  /**
   * Whether to generate source maps
   */
  generateSourceMaps?: boolean;
}

/**
 * Default CSS-in-JS options
 */
const DEFAULT_CSS_IN_JS_OPTIONS: CSSInJSOptions = {
  scoped: true,
  classPrefix: 'ordojs-css',
  optimize: true,
  generateSourceMaps: true
};

/**
 * CSS-in-JS expression types
 */
export type CSSInJSExpression =
  | CSSObjectExpression
  | CSSTemplateExpression
  | CSSFunctionExpression;

/**
 * CSS object expression (e.g., { color: 'red', fontSize: '16px' })
 */
export interface CSSObjectExpression {
  type: 'CSSObject';
  properties: Array<{
    key: string;
    value: string | number | CSSObjectExpression;
    computed?: boolean;
  }>;
}

/**
 * CSS template expression (e.g., css`color: red; font-size: 16px;`)
 */
export interface CSSTemplateExpression {
  type: 'CSSTemplate';
  template: string;
  expressions: ExpressionNode[];
}

/**
 * CSS function expression (e.g., styled.div`...` or css(...))
 */
export interface CSSFunctionExpression {
  type: 'CSSFunction';
  functionName: string;
  arguments: (CSSInJSExpression | string)[];
}

/**
 * CSS-in-JS compilation result
 */
export interface CSSInJSCompilationResult {
  css: string;
  className: string;
  styleBlock: StyleBlockNode;
  dependencies: string[];
}

/**
 * CSS-in-JS Compiler for OrdoJS components
 */
export class OrdoJSCSSInJSCompiler {
  private options: CSSInJSOptions;
  private classCounter: number = 0;

  constructor(options: Partial<CSSInJSOptions> = {}) {
    this.options = { ...DEFAULT_CSS_IN_JS_OPTIONS, ...options };
  }

  /**
   * Compile CSS-in-JS expression to CSS
   */
  compile(expression: CSSInJSExpression, componentName?: string): CSSInJSCompilationResult {
    const className = this.generateClassName(componentName);
    const declarations = this.compileExpression(expression);

    const rule: CSSRuleNode = {
      type: 'CSSRule',
      selector: `.${className}`,
      declarations,
      range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
    };

    const styleBlock: StyleBlockNode = {
      type: 'StyleBlock',
      rules: [rule],
      scoped: this.options.scoped || false,
      range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
    };

    const css = this.generateCSS(styleBlock);
    const dependencies = this.extractDependencies(expression);

    return {
      css,
      className,
      styleBlock,
      dependencies
    };
  }

  /**
   * Compile CSS-in-JS expression to CSS declarations
   */
  private compileExpression(expression: CSSInJSExpression): CSSDeclarationNode[] {
    switch (expression.type) {
      case 'CSSObject':
        return this.compileCSSObject(expression);
      case 'CSSTemplate':
        return this.compileCSSTemplate(expression);
      case 'CSSFunction':
        return this.compileCSSFunction(expression);
      default:
        throw new Error(`Unsupported CSS-in-JS expression type: ${(expression as any).type}`);
    }
  }

  /**
   * Compile CSS object expression
   */
  private compileCSSObject(expression: CSSObjectExpression): CSSDeclarationNode[] {
    const declarations: CSSDeclarationNode[] = [];

    for (const property of expression.properties) {
      if (typeof property.value === 'object' && property.value.type === 'CSSObject') {
        // Nested object - handle pseudo-classes, media queries, etc.
        const nestedDeclarations = this.compileCSSObject(property.value);
        // For now, we'll flatten nested objects - in a real implementation,
        // we'd need to handle nesting properly
        declarations.push(...nestedDeclarations);
      } else {
        const cssProperty = this.camelCaseToKebabCase(property.key);
        const cssValue = this.normalizeCSSValue(property.value);

        declarations.push({
          type: 'CSSDeclaration',
          property: cssProperty,
          value: cssValue,
          important: false,
          range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
        });
      }
    }

    return declarations;
  }

  /**
   * Compile CSS template expression
   */
  private compileCSSTemplate(expression: CSSTemplateExpression): CSSDeclarationNode[] {
    // Parse the template string as CSS
    let cssText = expression.template;

    // Replace template expressions with their values
    // This is a simplified implementation - in reality, we'd need proper template parsing
    expression.expressions.forEach((expr, index) => {
      const placeholder = `\${${index}}`;
      if (cssText.includes(placeholder)) {
        // For now, we'll assume expressions evaluate to strings
        // In a real implementation, we'd need to evaluate the expressions
        cssText = cssText.replace(placeholder, `/* expression ${index} */`);
      }
    });

    return this.parseCSSDeclarations(cssText);
  }

  /**
   * Compile CSS function expression
   */
  private compileCSSFunction(expression: CSSFunctionExpression): CSSDeclarationNode[] {
    const declarations: CSSDeclarationNode[] = [];

    for (const arg of expression.arguments) {
      if (typeof arg === 'string') {
        declarations.push(...this.parseCSSDeclarations(arg));
      } else {
        declarations.push(...this.compileExpression(arg));
      }
    }

    return declarations;
  }

  /**
   * Parse CSS declarations from a string
   */
  private parseCSSDeclarations(cssText: string): CSSDeclarationNode[] {
    const declarations: CSSDeclarationNode[] = [];

    // Split by semicolons and parse each declaration
    const declarationStrings = cssText.split(';').map(s => s.trim()).filter(Boolean);

    for (const declStr of declarationStrings) {
      const colonIndex = declStr.indexOf(':');
      if (colonIndex > 0) {
        const property = declStr.substring(0, colonIndex).trim();
        const value = declStr.substring(colonIndex + 1).trim();

        if (property && value) {
          declarations.push({
            type: 'CSSDeclaration',
            property,
            value,
            important: value.includes('!important'),
            range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
          });
        }
      }
    }

    return declarations;
  }

  /**
   * Convert camelCase to kebab-case for CSS properties
   */
  private camelCaseToKebabCase(str: string): string {
    return str.replace(/([A-Z])/g, '-$1').toLowerCase();
  }

  /**
   * Normalize CSS value (handle numbers, add units, etc.)
   */
  private normalizeCSSValue(value: string | number | CSSObjectExpression): string {
    if (typeof value === 'number') {
      // Add 'px' unit to numeric values for properties that need units
      return `${value}px`;
    }

    if (typeof value === 'string') {
      return value;
    }

    // For nested objects, we'd need to handle them differently
    return 'inherit';
  }

  /**
   * Generate unique class name
   */
  private generateClassName(componentName?: string): string {
    const prefix = this.options.classPrefix || 'ordojs-css';
    const suffix = componentName ? `${componentName.toLowerCase()}-${this.classCounter++}` : `${this.classCounter++}`;
    return `${prefix}-${suffix}`;
  }

  /**
   * Extract dependencies from CSS-in-JS expression
   */
  private extractDependencies(expression: CSSInJSExpression): string[] {
    const dependencies: string[] = [];

    // This is a simplified implementation
    // In a real implementation, we'd analyze the expression for variable references

    return dependencies;
  }

  /**
   * 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');
  }

  /**
   * Compile multiple CSS-in-JS expressions and merge them
   */
  compileMultiple(expressions: CSSInJSExpression[], componentName?: string): CSSInJSCompilationResult {
    const allDeclarations: CSSDeclarationNode[] = [];
    const allDependencies: string[] = [];

    for (const expression of expressions) {
      const declarations = this.compileExpression(expression);
      const dependencies = this.extractDependencies(expression);

      allDeclarations.push(...declarations);
      allDependencies.push(...dependencies);
    }

    const className = this.generateClassName(componentName);

    const rule: CSSRuleNode = {
      type: 'CSSRule',
      selector: `.${className}`,
      declarations: allDeclarations,
      range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
    };

    const styleBlock: StyleBlockNode = {
      type: 'StyleBlock',
      rules: [rule],
      scoped: this.options.scoped || false,
      range: { start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 0, offset: 0 } }
    };

    const css = this.generateCSS(styleBlock);

    return {
      css,
      className,
      styleBlock,
      dependencies: [...new Set(allDependencies)]
    };
  }

  /**
   * Create CSS-in-JS expression from JavaScript object
   */
  static createCSSObject(obj: Record<string, any>): CSSObjectExpression {
    const properties = Object.entries(obj).map(([key, value]) => ({
      key,
      value: typeof value === 'object' && value !== null
        ? OrdoJSCSSInJSCompiler.createCSSObject(value)
        : value,
      computed: false
    }));

    return {
      type: 'CSSObject',
      properties
    };
  }

  /**
   * Create CSS template expression
   */
  static createCSSTemplate(template: string, expressions: ExpressionNode[] = []): CSSTemplateExpression {
    return {
      type: 'CSSTemplate',
      template,
      expressions
    };
  }

  /**
   * Create CSS function expression
   */
  static createCSSFunction(functionName: string, args: (CSSInJSExpression | string)[]): CSSFunctionExpression {
    return {
      type: 'CSSFunction',
      functionName,
      arguments: args
    };
  }
}
