/**
 * @fileoverview OrdoJS Parser Fixes - Improvements for type safety and error handling
 */

import type {
  ASTNode,
  ComponentNode,
  ExpressionNode,
  HTMLElementNode,
  ReactiveVariableNode,
  SourcePosition,
  SourceRange,
  Token,
  TokenStream,
  TypeAnnotation
} from '../types/index.js';
import {
  DirectiveType,
  ExpressionType,
  SyntaxError,
  TokenType
} from '../types/index.js';

/**
 * Improved parser error handling and type safety
 */
export class ParserErrorHandler {
  /**
   * Create a properly typed syntax error with improved suggestions
   */
  static createError(
    message: string,
    expected: string[],
    actual: string,
    position: SourcePosition,
    filename?: string
  ): SyntaxError {
    // Enhance error message with context
    let enhancedMessage = message;

    // Add specific suggestions based on error context
    const suggestions = ParserErrorHandler.generateSuggestions(expected, actual, message);

    return new SyntaxError(
      enhancedMessage,
      position,
      expected,
      actual,
      filename
    );
  }

  /**
   * Generate helpful suggestions based on error context
   */
  private static generateSuggestions(expected: string[], actual: string, message: string): string[] {
    const suggestions: string[] = [];

    // Component structure suggestions
    if (message.includes("component")) {
      suggestions.push("Make sure to start with 'component ComponentName {'");
      suggestions.push("Component must have at least a markup block");
    }

    // Block-specific suggestions
    if (message.includes("client") || message.includes("server") || message.includes("markup")) {
      suggestions.push("Each block type (client, server, markup) can only appear once");
      suggestions.push("Blocks must be properly closed with '}'");
    }

    // HTML-specific suggestions
    if (message.includes("tag")) {
      suggestions.push("Check for matching opening and closing tags");
      suggestions.push("Self-closing tags should end with '/>'");
    }

    // Expression suggestions
    if (message.includes("expression")) {
      suggestions.push("Check for balanced parentheses and operators");
      suggestions.push("Verify variable names and property access syntax");
    }

    return suggestions;
  }

  /**
   * Validate component structure
   */
  static validateComponentStructure(component: ComponentNode): SyntaxError[] {
    const errors: SyntaxError[] = [];

    // Validate required markup block
    if (!component.markupBlock) {
      errors.push(new SyntaxError(
        "Component must have a markup block",
        component.range.start,
        ["markup"],
        "missing",
        ""
      ));
    }

    // Validate component name (must start with uppercase letter)
    if (!/^[A-Z][a-zA-Z0-9]*$/.test(component.name)) {
      errors.push(new SyntaxError(
        "Component name must start with uppercase letter and contain only alphanumeric characters",
        component.range.start,
        ["ValidComponentName"],
        component.name
      ));
    }

    // Validate no duplicate blocks
    const blockCounts = {
      client: 0,
      server: 0,
      markup: 0
    };

    component.children?.forEach(child => {
      if (child.type === 'ClientBlock') blockCounts.client++;
      if (child.type === 'ServerBlock') blockCounts.server++;
      if (child.type === 'MarkupBlock') blockCounts.markup++;
    });

    if (blockCounts.client > 1) {
      errors.push(new SyntaxError(
        "Multiple client blocks found",
        component.range.start,
        ["single client block"],
        `${blockCounts.client} client blocks`
      ));
    }

    if (blockCounts.server > 1) {
      errors.push(new SyntaxError(
        "Multiple server blocks found",
        component.range.start,
        ["single server block"],
        `${blockCounts.server} server blocks`
      ));
    }

    if (blockCounts.markup > 1) {
      errors.push(new SyntaxError(
        "Multiple markup blocks found",
        component.range.start,
        ["single markup block"],
        `${blockCounts.markup} markup blocks`
      ));
    }

    return errors;
  }

  /**
   * Validate HTML structure
   */
  static validateHTMLStructure(element: HTMLElementNode): SyntaxError[] {
    const errors: SyntaxError[] = [];

    // Check for void elements with children
    if (element.isVoidElement && element.children.length > 0) {
      errors.push(new SyntaxError(
        `Void element <${element.tagName}> cannot have children`,
        element.range.start,
        ["self-closing tag"],
        "tag with children"
      ));
    }

    // Check for duplicate ID attributes
    const idAttributes = element.attributes.filter(attr => attr.name === 'id');
    if (idAttributes.length > 1) {
      errors.push(new SyntaxError(
        "Element cannot have multiple 'id' attributes",
        element.range.start,
        ["single id attribute"],
        `${idAttributes.length} id attributes`
      ));
    }

    // Recursively validate children
    element.children.forEach(child => {
      if (child.type === 'HTMLElement') {
        errors.push(...ParserErrorHandler.validateHTMLStructure(child as HTMLElementNode));
      }
    });

    return errors;
  }
}

/**
 * Null/undefined safety utilities for parser
 */
export class ParserSafetyUtils {
  /**
   * Safely access token value with null checking
   */
  static safeTokenValue(token: Token | null | undefined): string {
    return token?.value || '';
  }

  /**
   * Safely create a source range with null checking
   */
  static safeCreateRange(
    start: SourcePosition | null | undefined,
    end: SourcePosition | null | undefined
  ): SourceRange {
    const defaultPos: SourcePosition = { line: 0, column: 0, offset: 0 };
    return {
      start: start || defaultPos,
      end: end || defaultPos
    };
  }

  /**
   * Safely handle optional children
   */
  static safeChildren(nodes: (ASTNode | null | undefined)[]): ASTNode[] {
    return nodes.filter((node): node is ASTNode => node !== null && node !== undefined);
  }

  /**
   * Safely handle optional expressions
   */
  static safeExpression(expr: ExpressionNode | null | undefined): ExpressionNode {
    if (expr) return expr;

    // Create a safe default expression
    const defaultPos: SourcePosition = { line: 0, column: 0, offset: 0 };
    const defaultRange: SourceRange = { start: defaultPos, end: defaultPos };

    return {
      type: 'Expression',
      expressionType: ExpressionType.LITERAL,
      value: null,
      range: defaultRange
    };
  }

  /**
   * Safely handle optional type annotations
   */
  static safeTypeAnnotation(type: TypeAnnotation | null | undefined): TypeAnnotation {
    return type || {
      name: 'any',
      isArray: false,
      isOptional: false,
      genericTypes: []
    };
  }
}

/**
 * Enhanced error recovery for parser
 */
export class ParserRecovery {
  /**
   * Synchronize parser state after error
   * @param tokens Token stream
   * @param synchronizationPoints Token types to synchronize on
   */
  static synchronize(
    tokens: TokenStream,
    synchronizationPoints: TokenType[] = [
      TokenType.SEMICOLON,
      TokenType.RIGHT_BRACE,
      TokenType.RIGHT_PAREN,
      TokenType.HTML_TAG_CLOSE
    ]
  ): void {
    // Skip tokens until we reach a synchronization point
    while (!tokens.isAtEnd()) {
      if (synchronizationPoints.includes(tokens.peek().type)) {
        tokens.advance(); // Consume the synchronization token
        return;
      }

      // Skip to next token
      tokens.advance();
    }
  }

  /**
   * Attempt to recover from HTML parsing errors
   */
  static recoverFromHTMLError(
    tokens: TokenStream,
    tagName: string
  ): void {
    // Skip until we find a matching closing tag or another opening tag
    while (!tokens.isAtEnd()) {
      const current = tokens.peek();

      if (current.type === TokenType.HTML_TAG_OPEN) {
        // Found another opening tag, stop here
        return;
      }

      if (current.type === TokenType.HTML_TAG_CLOSE) {
        // Found a closing tag, consume it and return
        tokens.advance();
        return;
      }

      // Skip to next token
      tokens.advance();
    }
  }

  /**
   * Attempt to recover from block parsing errors
   */
  static recoverFromBlockError(tokens: TokenStream): void {
    let braceCount = 0;

    // Skip until we find a matching closing brace
    while (!tokens.isAtEnd()) {
      const current = tokens.peek();

      if (current.type === TokenType.LEFT_BRACE) {
        braceCount++;
      } else if (current.type === TokenType.RIGHT_BRACE) {
        braceCount--;
        if (braceCount <= 0) {
          tokens.advance(); // Consume the closing brace
          return;
        }
      }

      // Skip to next token
      tokens.advance();
    }
  }
}

/**
 * Enhanced validation for parser
 */
export class ParserValidation {
  /**
   * Validate HTML element structure
   */
  static validateHTMLElement(element: HTMLElementNode): SyntaxError[] {
    const errors: SyntaxError[] = [];

    // Check for void elements with closing tags
    const voidElements = [
      'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
      'link', 'meta', 'param', 'source', 'track', 'wbr'
    ];

    if (voidElements.includes(element.tagName.toLowerCase()) && !element.isSelfClosing) {
      errors.push(new SyntaxError(
        `Void element <${element.tagName}> should be self-closing`,
        element.range.start,
        ["self-closing tag"],
        "non-self-closing tag"
      ));
    }

    // Check for invalid nesting
    const invalidNesting: Record<string, string[]> = {
      'a': ['a'],
      'button': ['button', 'input', 'select', 'textarea', 'a'],
      'form': ['form'],
      'label': ['label'],
      'td': ['td', 'th', 'tr', 'thead', 'tfoot', 'tbody'],
      'th': ['td', 'th', 'tr', 'thead', 'tfoot', 'tbody']
    };

    const tagName = element.tagName.toLowerCase();
    if (invalidNesting[tagName]) {
      for (const child of element.children) {
        if (child.type === 'HTMLElement') {
          const childTag = (child as HTMLElementNode).tagName.toLowerCase();
          if (invalidNesting[tagName].includes(childTag)) {
            errors.push(new SyntaxError(
              `Invalid nesting: <${childTag}> cannot be nested inside <${tagName}>`,
              child.range.start,
              ["valid nesting"],
              `<${tagName}> containing <${childTag}>`
            ));
          }
        }
      }
    }

    // Recursively validate children
    for (const child of element.children) {
      if (child.type === 'HTMLElement') {
        errors.push(...ParserValidation.validateHTMLElement(child as HTMLElementNode));
      }
    }

    return errors;
  }

  /**
   * Validate directive usage
   */
  static validateDirectives(element: HTMLElementNode): SyntaxError[] {
    const errors: SyntaxError[] = [];

    // Check for bind:value on non-input elements
    const bindValueAttrs = element.attributes.filter(
      attr => attr.isDirective && attr.directiveType === DirectiveType.BIND && attr.name === 'bind:value'
    );

    if (bindValueAttrs.length > 0 && !['input', 'textarea', 'select'].includes(element.tagName.toLowerCase())) {
      errors.push(new SyntaxError(
        `bind:value can only be used on input, textarea, or select elements`,
        element.range.start,
        ["input", "textarea", "select"],
        element.tagName
      ));
    }

    // Check for duplicate event handlers
    const eventHandlers = element.attributes.filter(
      attr => attr.isDirective && attr.directiveType === DirectiveType.ON
    );

    const eventNames = new Set<string>();
    for (const handler of eventHandlers) {
      const eventName = handler.name.split(':')[1] || '';
      if (eventName && eventNames.has(eventName)) {
        errors.push(new SyntaxError(
          `Duplicate event handler for '${eventName}'`,
          handler.range.start,
          ["single event handler"],
          "multiple handlers"
        ));
      }
      if (eventName) {
        eventNames.add(eventName);
      }
    }

    // Recursively validate children
    for (const child of element.children) {
      if (child.type === 'HTMLElement') {
        errors.push(...ParserValidation.validateDirectives(child as HTMLElementNode));
      }
    }

    return errors;
  }

  /**
   * Validate reactive variable declarations
   */
  static validateReactiveVariables(variables: ReactiveVariableNode[]): SyntaxError[] {
    const errors: SyntaxError[] = [];
    const declaredNames = new Set<string>();

    for (const variable of variables) {
      // Check for duplicate declarations
      if (declaredNames.has(variable.name)) {
        errors.push(new SyntaxError(
          `Duplicate reactive variable declaration: '${variable.name}'`,
          variable.range.start,
          ["unique variable name"],
          variable.name
        ));
      }
      declaredNames.add(variable.name);

      // Check for const variables without initialization
      if (variable.isConst && !variable.initialValue) {
        errors.push(new SyntaxError(
          `Const variable '${variable.name}' must be initialized`,
          variable.range.start,
          ["initialization"],
          "missing initialization"
        ));
      }
    }

    return errors;
  }
}
