import { BaseNode } from './BaseNode.js';

/**
 * AST node for expressions (variables, literals, attribute access, etc.)
 */
export class ExpressionNode extends BaseNode {
  constructor(expressionType, location = null) {
    super('Expression', location);
    this.expressionType = expressionType; // 'variable', 'literal', 'attribute', 'function', etc.
    this.value = null;
    this.name = null;
    this.attribute = null;
    this.object = null;
    this.arguments = [];
    this.operator = null;
    this.left = null;
    this.right = null;
  }

  /**
   * Set the value for this expression
   * @param {*} value - Value to set
   */
  setValue(value) {
    this.value = value;
  }

  /**
   * Set the name for this expression
   * @param {string} name - Name to set
   */
  setName(name) {
    this.name = name;
  }

  /**
   * Set the attribute for this expression
   * @param {string} attribute - Attribute to set
   */
  setAttribute(attribute) {
    this.attribute = attribute;
  }

  /**
   * Set the object for this expression
   * @param {ExpressionNode} object - Object to set
   */
  setObject(object) {
    this.object = object;
    this.addChild(object);
  }

  /**
   * Add an argument to this expression
   * @param {ExpressionNode} argument - Argument to add
   */
  addArgument(argument) {
    this.arguments.push(argument);
    this.addChild(argument);
  }

  /**
   * Set the operator for this expression
   * @param {string} operator - Operator to set
   */
  setOperator(operator) {
    this.operator = operator;
  }

  /**
   * Set the left operand for this expression
   * @param {ExpressionNode} left - Left operand to set
   */
  setLeft(left) {
    this.left = left;
    this.addChild(left);
  }

  /**
   * Set the right operand for this expression
   * @param {ExpressionNode} right - Right operand to set
   */
  setRight(right) {
    this.right = right;
    this.addChild(right);
  }

  /**
   * Check if this is a variable expression
   * @returns {boolean} True if variable
   */
  isVariable() {
    return this.expressionType === 'variable';
  }

  /**
   * Check if this is a literal expression
   * @returns {boolean} True if literal
   */
  isLiteral() {
    return this.expressionType === 'literal';
  }

  /**
   * Check if this is an attribute access expression
   * @returns {boolean} True if attribute access
   */
  isAttributeAccess() {
    return this.expressionType === 'attribute';
  }

  /**
   * Check if this is a function call expression
   * @returns {boolean} True if function call
   */
  isFunctionCall() {
    return this.expressionType === 'function';
  }

  /**
   * Check if this is a binary operation expression
   * @returns {boolean} True if binary operation
   */
  isBinaryOperation() {
    return this.expressionType === 'binary';
  }

  /**
   * Check if this is a wildcard variable
   * @returns {boolean} True if wildcard
   */
  isWildcard() {
    return this.isVariable() && this.name && this.name.startsWith('*');
  }

  /**
   * Get the variable name (without wildcard prefix)
   * @returns {string|null} Variable name or null
   */
  getVariableName() {
    if (this.isVariable() && this.name) {
      return this.name.startsWith('*') ? this.name.substring(1) : this.name;
    }
    return null;
  }

  /**
   * Get the full attribute path
   * @returns {string|null} Full attribute path or null
   */
  getAttributePath() {
    if (this.isAttributeAccess()) {
      const objStr = this.object ? this.object.toString() : '';
      return `${objStr}.${this.attribute}`;
    }
    return null;
  }

  /**
   * Get the function signature
   * @returns {string|null} Function signature or null
   */
  getFunctionSignature() {
    if (this.isFunctionCall()) {
      const argStr = this.arguments.map(arg => arg.toString()).join(', ');
      return `${this.name}(${argStr})`;
    }
    return null;
  }

  /**
   * Validate the expression
   * @returns {string[]} Array of error messages
   */
  validate() {
    const errors = [];

    // Validate expression type
    const validTypes = ['variable', 'literal', 'attribute', 'function', 'binary'];
    if (!validTypes.includes(this.expressionType)) {
      errors.push(`Invalid expression type: ${this.expressionType}`);
    }

    // Validate variable expressions
    if (this.isVariable() && !this.name) {
      errors.push('Variable expression must have a name');
    }

    // Validate literal expressions
    if (this.isLiteral() && this.value === null) {
      errors.push('Literal expression must have a value');
    }

    // Validate attribute access expressions
    if (this.isAttributeAccess()) {
      if (!this.attribute) {
        errors.push('Attribute access expression must have an attribute');
      }
      if (this.object) {
        const objErrors = this.object.validate ? this.object.validate() : [];
        errors.push(...objErrors);
      }
    }

    // Validate function call expressions
    if (this.isFunctionCall()) {
      if (!this.name) {
        errors.push('Function call expression must have a name');
      }
      this.arguments.forEach((arg, index) => {
        const argErrors = arg.validate ? arg.validate() : [];
        errors.push(...argErrors.map(err => `Argument ${index + 1}: ${err}`));
      });
    }

    // Validate binary operation expressions
    if (this.isBinaryOperation()) {
      if (!this.operator) {
        errors.push('Binary operation expression must have an operator');
      }
      if (!this.left) {
        errors.push('Binary operation expression must have a left operand');
      }
      if (!this.right) {
        errors.push('Binary operation expression must have a right operand');
      }
      if (this.left) {
        const leftErrors = this.left.validate ? this.left.validate() : [];
        errors.push(...leftErrors);
      }
      if (this.right) {
        const rightErrors = this.right.validate ? this.right.validate() : [];
        errors.push(...rightErrors);
      }
    }

    return errors;
  }

  toString() {
    switch (this.expressionType) {
      case 'variable':
        return `Variable(${this.name})`;
      case 'literal':
        return `Literal(${this.value})`;
      case 'attribute':
        const objStr = this.object ? this.object.toString() : '';
        return `Attribute(${objStr}.${this.attribute})`;
      case 'function':
        const argStr = this.arguments.map(arg => arg.toString()).join(', ');
        return `Function(${this.name}(${argStr}))`;
      case 'binary':
        const leftStr = this.left ? this.left.toString() : 'null';
        const rightStr = this.right ? this.right.toString() : 'null';
        return `Binary(${leftStr} ${this.operator} ${rightStr})`;
      default:
        return `Expression(${this.expressionType})`;
    }
  }
}
