/**
 * @fileoverview CSS Parser for OrdoJS Framework
 * Parses CSS content into an AST structure for further processing
 */

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

/**
 * CSS Parser options
 */
export interface CSSParserOptions {
  /**
   * Whether to generate source maps
   */
  generateSourceMaps?: boolean;

  /**
   * Whether to validate CSS syntax
   */
  validateSyntax?: boolean;
}

/**
 * Default CSS Parser options
 */
const DEFAULT_CSS_PARSER_OPTIONS: CSSParserOptions = {
  generateSourceMaps: true,
  validateSyntax: true
};

/**
 * CSS Parser for OrdoJS components
 */
export class OrdoJSCSSParser {
  private options: CSSParserOptions;
  private source: string = '';
  private currentPosition: number = 0;
  private currentLine: number = 1;
  private currentColumn: number = 0;
  private errors: string[] = [];

  constructor(options: Partial<CSSParserOptions> = {}) {
    this.options = { ...DEFAULT_CSS_PARSER_OPTIONS, ...options };
  }

  /**
   * Parse CSS content into a StyleBlockNode
   */
  parse(source: string, scoped: boolean = true): StyleBlockNode {
    this.source = source;
    this.currentPosition = 0;
    this.currentLine = 1;
    this.currentColumn = 0;
    this.errors = [];

    const rules = this.parseRules();

    if (this.options.validateSyntax && this.errors.length > 0) {
      throw new Error(`CSS parsing errors: ${this.errors.join(', ')}`);
    }

    return {
      type: 'StyleBlock',
      rules,
      scoped,
      range: this.createSourceRange(0, this.currentPosition)
    };
  }

  /**
   * Parse CSS rules
   */
  private parseRules(): CSSRuleNode[] {
    const rules: CSSRuleNode[] = [];

    this.skipWhitespace();

    while (this.currentPosition < this.source.length) {
      const rule = this.parseRule();
      if (rule) {
        rules.push(rule);
      }
      this.skipWhitespace();
    }

    return rules;
  }

  /**
   * Parse a single CSS rule
   */
  private parseRule(): CSSRuleNode | null {
    const startPos = this.currentPosition;

    // Parse selector
    const selector = this.parseSelector();
    if (!selector) {
      return null;
    }

    this.skipWhitespace();

    // Expect opening brace
    if (!this.consume('{')) {
      this.errors.push(`Expected '{' after selector '${selector}' at line ${this.currentLine}, column ${this.currentColumn}`);
      this.recoverToNextRule();
      return null;
    }

    this.skipWhitespace();

    // Parse declarations
    const declarations = this.parseDeclarations();

    // Expect closing brace
    if (!this.consume('}')) {
      this.errors.push(`Expected '}' after declarations for selector '${selector}' at line ${this.currentLine}, column ${this.currentColumn}`);
      this.recoverToNextRule();
      return null;
    }

    return {
      type: 'CSSRule',
      selector,
      declarations,
      range: this.createSourceRange(startPos, this.currentPosition)
    };
  }

  /**
   * Parse a CSS selector
   */
  private parseSelector(): string | null {
    const startPos = this.currentPosition;

    // Skip any leading whitespace
    this.skipWhitespace();

    // Read until we find an opening brace or end of input
    let selector = '';
    while (
      this.currentPosition < this.source.length &&
      this.source[this.currentPosition] !== '{' &&
      this.source[this.currentPosition] !== '}'
    ) {
      selector += this.source[this.currentPosition];
      this.advance();
    }

    selector = selector.trim();

    if (!selector) {
      this.errors.push(`Empty selector at line ${this.currentLine}, column ${this.currentColumn}`);
      return null;
    }

    return selector;
  }

  /**
   * Parse CSS declarations
   */
  private parseDeclarations(): CSSDeclarationNode[] {
    const declarations: CSSDeclarationNode[] = [];

    this.skipWhitespace();

    while (
      this.currentPosition < this.source.length &&
      this.source[this.currentPosition] !== '}'
    ) {
      const declaration = this.parseDeclaration();
      if (declaration) {
        declarations.push(declaration);
      }

      // Skip semicolon and whitespace
      this.consume(';');
      this.skipWhitespace();
    }

    return declarations;
  }

  /**
   * Parse a single CSS declaration
   */
  private parseDeclaration(): CSSDeclarationNode | null {
    const startPos = this.currentPosition;

    // Parse property
    const property = this.parseIdentifier();
    if (!property) {
      this.recoverToNextDeclaration();
      return null;
    }

    this.skipWhitespace();

    // Expect colon
    if (!this.consume(':')) {
      this.errors.push(`Expected ':' after property '${property}' at line ${this.currentLine}, column ${this.currentColumn}`);
      this.recoverToNextDeclaration();
      return null;
    }

    this.skipWhitespace();

    // Parse value
    const value = this.parseValue();
    if (!value) {
      this.recoverToNextDeclaration();
      return null;
    }

    return {
      type: 'CSSDeclaration',
      property,
      value,
      important: false,
      range: this.createSourceRange(startPos, this.currentPosition)
    };
  }

  /**
   * Parse a CSS property name
   */
  private parseIdentifier(): string | null {
    const startPos = this.currentPosition;

    // CSS identifiers can start with a letter, underscore, or hyphen
    if (
      this.currentPosition < this.source.length &&
      !this.isIdentifierStart(this.source[this.currentPosition])
    ) {
      this.errors.push(`Invalid identifier start at line ${this.currentLine}, column ${this.currentColumn}`);
      return null;
    }

    let identifier = '';

    while (
      this.currentPosition < this.source.length &&
      this.isIdentifierPart(this.source[this.currentPosition])
    ) {
      identifier += this.source[this.currentPosition];
      this.advance();
    }

    if (!identifier) {
      this.errors.push(`Empty identifier at line ${this.currentLine}, column ${this.currentColumn}`);
      return null;
    }

    return identifier;
  }

  /**
   * Parse a CSS property value
   */
  private parseValue(): string | null {
    let value = '';
    let parenDepth = 0;

    while (this.currentPosition < this.source.length) {
      const char = this.source[this.currentPosition];

      // Handle nested parentheses in values like url(), calc(), etc.
      if (char === '(') {
        parenDepth++;
      } else if (char === ')') {
        parenDepth--;
      }

      // End of value when we hit a semicolon or closing brace (if not in parentheses)
      if ((char === ';' || char === '}') && parenDepth <= 0) {
        break;
      }

      value += char;
      this.advance();
    }

    value = value.trim();

    if (!value) {
      this.errors.push(`Empty value at line ${this.currentLine}, column ${this.currentColumn}`);
      return null;
    }

    return value;
  }

  /**
   * Check if a character is valid as the start of an identifier
   */
  private isIdentifierStart(char: string): boolean {
    return /[a-zA-Z_\-]/.test(char);
  }

  /**
   * Check if a character is valid as part of an identifier
   */
  private isIdentifierPart(char: string): boolean {
    return /[a-zA-Z0-9_\-]/.test(char);
  }

  /**
   * Advance the current position
   */
  private advance(): void {
    if (this.source[this.currentPosition] === '\n') {
      this.currentLine++;
      this.currentColumn = 0;
    } else {
      this.currentColumn++;
    }

    this.currentPosition++;
  }

  /**
   * Skip whitespace characters
   */
  private skipWhitespace(): void {
    while (
      this.currentPosition < this.source.length &&
      /\s/.test(this.source[this.currentPosition])
    ) {
      this.advance();
    }
  }

  /**
   * Consume an expected character
   */
  private consume(expected: string): boolean {
    if (
      this.currentPosition < this.source.length &&
      this.source[this.currentPosition] === expected
    ) {
      this.advance();
      return true;
    }

    return false;
  }

  /**
   * Recover from an error by advancing to the next rule
   */
  private recoverToNextRule(): void {
    while (
      this.currentPosition < this.source.length &&
      this.source[this.currentPosition] !== '}'
    ) {
      this.advance();
    }

    // Skip the closing brace if found
    if (this.currentPosition < this.source.length) {
      this.advance();
    }
  }

  /**
   * Recover from an error by advancing to the next declaration
   */
  private recoverToNextDeclaration(): void {
    while (
      this.currentPosition < this.source.length &&
      this.source[this.currentPosition] !== ';' &&
      this.source[this.currentPosition] !== '}'
    ) {
      this.advance();
    }

    // Skip the semicolon if found
    if (this.currentPosition < this.source.length && this.source[this.currentPosition] === ';') {
      this.advance();
    }
  }

  /**
   * Create a source location object
   */
  private createSourceLocation(start: number, end: number): SourceLocation {
    return {
      start,
      end,
      line: this.currentLine,
      column: this.currentColumn
    };
  }

  /**
   * Create a source range object
   */
  private createSourceRange(start: number, end: number): import('../types/index.js').SourceRange {
    return {
      start: {
        line: this.currentLine,
        column: this.currentColumn,
        offset: start
      },
      end: {
        line: this.currentLine,
        column: this.currentColumn,
        offset: end
      }
    };
  }
}
