/**
 * @fileoverview OrdoJS Parser - Full implementation for syntax analysis
 */

import {
  ExpressionType,
  LifecycleType,
  StatementType,
  SyntaxError,
  TokenType,
  type AttributeNode,
  type ClientBlockNode,
  type ComponentAST,
  type ComponentNode,
  type ExpressionNode,
  type FunctionNode,
  type HTMLElementNode,
  type InterpolationNode,
  type LifecycleHookNode,
  type MarkupBlockNode,
  type ParameterNode,
  type PropDefinition,
  type ReactiveVariableNode,
  type ServerBlockNode,
  type ServerFunctionNode,
  type SourcePosition,
  type StatementNode,
  type TextNode,
  type Token,
  type TokenStream,
  type TypeAnnotation
} from '../types/index.js';

export interface ParserOptions {
  allowRecovery?: boolean;
  maxErrors?: number;
}

export class OrdoJSParser {
  private tokenStream: TokenStream;
  private current: number = 0;
  private errors: SyntaxError[] = [];
  private options: ParserOptions;
  private filename: string;

  constructor(tokenStream: TokenStream, options: ParserOptions = {}, filename: string = 'unknown') {
    this.tokenStream = tokenStream;
    this.options = {
      allowRecovery: false,
      maxErrors: 1,
      ...options
    };
    this.filename = filename;
  }

  parse(): ComponentAST {
    try {
      const component = this.parseComponent();

      if (this.errors.length > 0) {
        throw this.errors[0];
      }

      return {
        component,
        dependencies: [],
        exports: [],
        sourceMap: {
          version: 3,
          sources: [this.filename],
          names: [],
          mappings: '',
          sourcesContent: []
        }
      };
    } catch (error) {
      if (error instanceof SyntaxError) {
        throw error;
      }
      throw new SyntaxError(
        `Parser error: ${error instanceof Error ? error.message : 'Unknown error'}`,
        this.getCurrentPosition(),
        ['valid component syntax'],
        this.peek().value,
        this.filename
      );
    }
  }

  getErrors(): SyntaxError[] {
    return this.errors;
  }

  private parseComponent(): ComponentNode {
    const start = this.getCurrentPosition();

    // Expect 'component' keyword
    if (!this.match(TokenType.COMPONENT)) {
      this.throwError("Expected 'component' keyword", ['component']);
    }

    // Parse component name
    const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected component name');
    const name = nameToken.value;

    // Validate component name (must start with uppercase)
    if (!/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
      this.throwError('Component name must start with uppercase letter and contain only alphanumeric characters', []);
    }

    // Parse optional props
    const props = this.parseProps();

    // Expect component body
    this.consume(TokenType.LEFT_BRACE, "Expected '{' after component declaration");

    // Parse component blocks
    let clientBlock: ClientBlockNode | undefined;
    let serverBlock: ServerBlockNode | undefined;
    let markupBlock: MarkupBlockNode | undefined;

    while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
      if (this.match(TokenType.CLIENT)) {
        if (clientBlock) {
          this.throwError('Duplicate client block', []);
        }
        clientBlock = this.parseClientBlock();
      } else if (this.match(TokenType.SERVER)) {
        if (serverBlock) {
          this.throwError('Duplicate server block', []);
        }
        serverBlock = this.parseServerBlock();
      } else if (this.match(TokenType.MARKUP)) {
        if (markupBlock) {
          this.throwError('Duplicate markup block', []);
        }
        markupBlock = this.parseMarkupBlock();
      } else {
        this.throwError('Expected client, server, or markup block', ['client', 'server', 'markup']);
      }
    }

    this.consume(TokenType.RIGHT_BRACE, "Expected '}' after component body");

    // Markup block is required
    if (!markupBlock) {
      this.throwError('Component must have a markup block', ['markup']);
    }

    const end = this.getCurrentPosition();

    return {
      type: 'Component',
      name,
      props,
      clientBlock,
      serverBlock,
      markupBlock: markupBlock!,
      range: { start, end }
    } as ComponentNode;
  }

  private parseProps(): PropDefinition[] {
    const props: PropDefinition[] = [];

    if (!this.check(TokenType.LEFT_PAREN)) {
      return props;
    }

    this.advance(); // consume '('

    while (!this.check(TokenType.RIGHT_PAREN) && !this.isAtEnd()) {
      props.push(this.parseProp());

      if (!this.match(TokenType.COMMA)) {
        break;
      }
    }

    this.consume(TokenType.RIGHT_PAREN, "Expected ')' after props");
    return props;
  }

  private parseProp(): PropDefinition {
    const start = this.getCurrentPosition();

    const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected prop name');
    this.consume(TokenType.COLON, "Expected ':' after prop name");

    const dataType = this.parseTypeAnnotation();

    let defaultValue: ExpressionNode | undefined;
    let isRequired = true;

    if (this.match(TokenType.ASSIGN)) {
      isRequired = false;
      defaultValue = this.parseExpression();
    }

    const end = this.getCurrentPosition();

    return {
      type: 'PropDefinition',
      name: nameToken.value,
      dataType,
      defaultValue,
      isRequired,
      range: { start, end }
    } as PropDefinition;
  }

  private parseTypeAnnotation(): TypeAnnotation {
    const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected type name');
    let isArray = false;

    if (this.match(TokenType.LEFT_BRACKET)) {
      this.consume(TokenType.RIGHT_BRACKET, "Expected ']' after '['");
      isArray = true;
    }

    return {
      name: nameToken.value,
      isArray,
      isOptional: false,
      genericTypes: []
    };
  }

  private parseClientBlock(): ClientBlockNode {
    const start = this.getCurrentPosition();

    this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'client'");

    const reactiveVariables: ReactiveVariableNode[] = [];
    const computedValues: any[] = [];
    const eventHandlers: any[] = [];
    const functions: FunctionNode[] = [];
    const lifecycle: LifecycleHookNode[] = [];

    while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
      if (this.check(TokenType.LET) || this.check(TokenType.CONST)) {
        reactiveVariables.push(this.parseReactiveVariable());
      } else if (this.checkLifecycleHook()) {
        lifecycle.push(this.parseLifecycleHook());
      } else if (this.check(TokenType.IDENTIFIER)) {
        functions.push(this.parseFunction());
      } else {
        this.throwError('Expected variable declaration, function, or lifecycle hook', ['let', 'const', 'function']);
      }
    }

    this.consume(TokenType.RIGHT_BRACE, "Expected '}' after client block");

    const end = this.getCurrentPosition();

    return {
      type: 'ClientBlock',
      reactiveVariables,
      computedValues,
      eventHandlers,
      functions,
      lifecycle,
      range: { start, end }
    };
  }

  private parseServerBlock(): ServerBlockNode {
    const start = this.getCurrentPosition();

    this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'server'");

    const functions: ServerFunctionNode[] = [];
    const middleware: any[] = [];
    const dataFetchers: any[] = [];
    const imports: any[] = [];

    while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
      if (this.check(TokenType.PUBLIC) || this.check(TokenType.IDENTIFIER)) {
        functions.push(this.parseServerFunction());
      } else {
        this.throwError('Expected function declaration', ['public', 'function']);
      }
    }

    this.consume(TokenType.RIGHT_BRACE, "Expected '}' after server block");

    const end = this.getCurrentPosition();

    return {
      type: 'ServerBlock',
      functions,
      middleware,
      dataFetchers,
      imports,
      range: { start, end }
    };
  }

  private parseMarkupBlock(): MarkupBlockNode {
    const start = this.getCurrentPosition();

    this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'markup'");

    const elements: HTMLElementNode[] = [];
    const textNodes: TextNode[] = [];
    const interpolations: InterpolationNode[] = [];

    while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
      if (this.check(TokenType.HTML_TAG_OPEN)) {
        elements.push(this.parseHTMLElement());
      } else if (this.check(TokenType.INTERPOLATION_START)) {
        interpolations.push(this.parseInterpolation());
      } else if (this.check(TokenType.HTML_TEXT)) {
        textNodes.push(this.parseTextNode());
      } else {
        // Skip whitespace and try to parse HTML
        if (this.peek().value.trim()) {
          this.advance();
        } else {
          this.advance();
        }
      }
    }

    this.consume(TokenType.RIGHT_BRACE, "Expected '}' after markup block");

    const end = this.getCurrentPosition();

    return {
      type: 'MarkupBlock',
      elements,
      textNodes,
      interpolations,
      range: { start, end }
    };
  }

  private parseReactiveVariable(): ReactiveVariableNode {
    const start = this.getCurrentPosition();

    const isConst = this.match(TokenType.CONST);
    if (!isConst) {
      this.consume(TokenType.LET, "Expected 'let' or 'const'");
    }

    const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected variable name');

    let dataType: TypeAnnotation = { name: 'any', isArray: false, isOptional: false, genericTypes: [] };

    if (this.match(TokenType.COLON)) {
      dataType = this.parseTypeAnnotation();
    }

    this.consume(TokenType.ASSIGN, "Expected '=' after variable declaration");

    const initialValue = this.parseExpression();

    this.match(TokenType.SEMICOLON); // Optional semicolon

    const end = this.getCurrentPosition();

    return {
      type: 'ReactiveVariable',
      name: nameToken.value,
      initialValue,
      dataType,
      isConst,
      range: { start, end }
    };
  }

  private parseFunction(): FunctionNode {
    const start = this.getCurrentPosition();

    const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected function name');

    this.consume(TokenType.LEFT_PAREN, "Expected '(' after function name");

    const parameters = this.parseParameters();

    this.consume(TokenType.RIGHT_PAREN, "Expected ')' after parameters");

    this.consume(TokenType.COLON, "Expected ':' after parameters");

    const returnType = this.parseTypeAnnotation();

    this.consume(TokenType.LEFT_BRACE, "Expected '{' after function signature");

    const body = this.parseStatements();

    this.consume(TokenType.RIGHT_BRACE, "Expected '}' after function body");

    const end = this.getCurrentPosition();

    return {
      type: 'Function',
      name: nameToken.value,
      parameters,
      body,
      returnType,
      isAsync: false,
      range: { start, end }
    };
  }

  private parseServerFunction(): ServerFunctionNode {
    const start = this.getCurrentPosition();

    const isPublic = this.match(TokenType.PUBLIC);

    const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected function name');

    this.consume(TokenType.LEFT_PAREN, "Expected '(' after function name");

    const parameters = this.parseParameters();

    this.consume(TokenType.RIGHT_PAREN, "Expected ')' after parameters");

    this.consume(TokenType.COLON, "Expected ':' after parameters");

    const returnType = this.parseTypeAnnotation();

    this.consume(TokenType.LEFT_BRACE, "Expected '{' after function signature");

    const body = this.parseStatements();

    this.consume(TokenType.RIGHT_BRACE, "Expected '}' after function body");

    const end = this.getCurrentPosition();

    return {
      type: 'ServerFunction',
      name: nameToken.value,
      parameters,
      body,
      returnType,
      isAsync: false,
      isPublic,
      middleware: [],
      permissions: [],
      range: { start, end }
    };
  }

  private parseLifecycleHook(): LifecycleHookNode {
    const start = this.getCurrentPosition();

    const hookName = this.advance().value;

    let hookType: LifecycleType;
    switch (hookName) {
      case 'onMount':
        hookType = LifecycleType.ON_MOUNT;
        break;
      case 'onUnmount':
        hookType = LifecycleType.ON_UNMOUNT;
        break;
      case 'onUpdate':
        hookType = LifecycleType.ON_UPDATE;
        break;
      default:
        this.throwError(`Unknown lifecycle hook: ${hookName}`, ['onMount', 'onUnmount', 'onUpdate']);
        hookType = LifecycleType.ON_MOUNT;
    }

    this.consume(TokenType.LEFT_PAREN, "Expected '(' after lifecycle hook");
    this.consume(TokenType.RIGHT_PAREN, "Expected ')' after lifecycle hook");
    this.consume(TokenType.LEFT_BRACE, "Expected '{' after lifecycle hook");

    const handler = this.parseStatements();

    this.consume(TokenType.RIGHT_BRACE, "Expected '}' after lifecycle hook body");

    const end = this.getCurrentPosition();

    return {
      type: 'LifecycleHook',
      hookType,
      handler,
      range: { start, end }
    };
  }

  private parseParameters(): ParameterNode[] {
    const parameters: ParameterNode[] = [];

    while (!this.check(TokenType.RIGHT_PAREN) && !this.isAtEnd()) {
      parameters.push(this.parseParameter());

      if (!this.match(TokenType.COMMA)) {
        break;
      }
    }

    return parameters;
  }

  private parseParameter(): ParameterNode {
    const start = this.getCurrentPosition();

    const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected parameter name');

    this.consume(TokenType.COLON, "Expected ':' after parameter name");

    const dataType = this.parseTypeAnnotation();

    let defaultValue: ExpressionNode | undefined;
    let isOptional = false;

    if (this.match(TokenType.ASSIGN)) {
      isOptional = true;
      defaultValue = this.parseExpression();
    }

    const end = this.getCurrentPosition();

    return {
      type: 'Parameter',
      name: nameToken.value,
      dataType,
      defaultValue,
      isOptional,
      range: { start, end }
    } as ParameterNode;
  }

  private parseStatements(): StatementNode[] {
    const statements: StatementNode[] = [];

    while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
      statements.push(this.parseStatement());
    }

    return statements;
  }

  private parseStatement(): StatementNode {
    const start = this.getCurrentPosition();

    // For now, treat everything as expression statements
    const expression = this.parseExpression();

    this.match(TokenType.SEMICOLON); // Optional semicolon

    const end = this.getCurrentPosition();

    return {
      type: 'Statement',
      statementType: StatementType.EXPRESSION,
      expression,
      range: { start, end }
    };
  }

  private parseExpression(): ExpressionNode {
    return this.parseAssignment();
  }

  private parseAssignment(): ExpressionNode {
    const expr = this.parseLogicalOr();

    if (this.match(TokenType.ASSIGN)) {
      const operator = this.previous().value;
      const right = this.parseAssignment();

      return {
        type: 'Expression',
        expressionType: ExpressionType.ASSIGNMENT,
        operator,
        left: expr,
        right,
        range: { start: expr.range.start, end: right.range.end }
      };
    }

    return expr;
  }

  private parseLogicalOr(): ExpressionNode {
    let expr = this.parseLogicalAnd();

    while (this.match(TokenType.LOGICAL_OR)) {
      const operator = this.previous().value;
      const right = this.parseLogicalAnd();
      expr = {
        type: 'Expression',
        expressionType: ExpressionType.BINARY,
        operator,
        left: expr,
        right,
        range: { start: expr.range.start, end: right.range.end }
      };
    }

    return expr;
  }

  private parseLogicalAnd(): ExpressionNode {
    let expr = this.parseEquality();

    while (this.match(TokenType.LOGICAL_AND)) {
      const operator = this.previous().value;
      const right = this.parseEquality();
      expr = {
        type: 'Expression',
        expressionType: ExpressionType.BINARY,
        operator,
        left: expr,
        right,
        range: { start: expr.range.start, end: right.range.end }
      };
    }

    return expr;
  }

  private parseEquality(): ExpressionNode {
    let expr = this.parseComparison();

    while (this.match(TokenType.EQUALS, TokenType.NOT_EQUALS)) {
      const operator = this.previous().value;
      const right = this.parseComparison();
      expr = {
        type: 'Expression',
        expressionType: ExpressionType.BINARY,
        operator,
        left: expr,
        right,
        range: { start: expr.range.start, end: right.range.end }
      };
    }

    return expr;
  }

  private parseComparison(): ExpressionNode {
    let expr = this.parseTerm();

    while (this.match(TokenType.GREATER_THAN, TokenType.GREATER_EQUAL, TokenType.LESS_THAN, TokenType.LESS_EQUAL)) {
      const operator = this.previous().value;
      const right = this.parseTerm();
      expr = {
        type: 'Expression',
        expressionType: ExpressionType.BINARY,
        operator,
        left: expr,
        right,
        range: { start: expr.range.start, end: right.range.end }
      };
    }

    return expr;
  }

  private parseTerm(): ExpressionNode {
    let expr = this.parseFactor();

    while (this.match(TokenType.MINUS, TokenType.PLUS)) {
      const operator = this.previous().value;
      const right = this.parseFactor();
      expr = {
        type: 'Expression',
        expressionType: ExpressionType.BINARY,
        operator,
        left: expr,
        right,
        range: { start: expr.range.start, end: right.range.end }
      };
    }

    return expr;
  }

  private parseFactor(): ExpressionNode {
    let expr = this.parseUnary();

    while (this.match(TokenType.DIVIDE, TokenType.MULTIPLY, TokenType.MODULO)) {
      const operator = this.previous().value;
      const right = this.parseUnary();
      expr = {
        type: 'Expression',
        expressionType: ExpressionType.BINARY,
        operator,
        left: expr,
        right,
        range: { start: expr.range.start, end: right.range.end }
      };
    }

    return expr;
  }

  private parseUnary(): ExpressionNode {
    if (this.match(TokenType.LOGICAL_NOT, TokenType.MINUS)) {
      const operator = this.previous().value;
      const right = this.parseUnary();
      return {
        type: 'Expression',
        expressionType: ExpressionType.UNARY,
        operator,
        right,
        range: { start: this.previous().range.start, end: right.range.end }
      };
    }

    return this.parseCall();
  }

  private parseCall(): ExpressionNode {
    let expr = this.parsePrimary();

    while (true) {
      if (this.match(TokenType.LEFT_PAREN)) {
        expr = this.finishCall(expr);
      } else if (this.match(TokenType.DOT)) {
        const name = this.consume(TokenType.IDENTIFIER, "Expected property name after '.'");
        expr = {
          type: 'Expression',
          expressionType: ExpressionType.MEMBER,
          object: expr,
          property: {
            type: 'Expression',
            expressionType: ExpressionType.IDENTIFIER,
            identifier: name.value,
            range: name.range
          },
          range: { start: expr.range.start, end: name.range.end }
        };
      } else {
        break;
      }
    }

    return expr;
  }

  private finishCall(callee: ExpressionNode): ExpressionNode {
    const args: ExpressionNode[] = [];

    if (!this.check(TokenType.RIGHT_PAREN)) {
      do {
        args.push(this.parseExpression());
      } while (this.match(TokenType.COMMA));
    }

    const paren = this.consume(TokenType.RIGHT_PAREN, "Expected ')' after arguments");

    return {
      type: 'Expression',
      expressionType: ExpressionType.CALL,
      callee,
      arguments: args,
      range: { start: callee.range.start, end: paren.range.end }
    };
  }

  private parsePrimary(): ExpressionNode {
    const start = this.getCurrentPosition();

    if (this.match(TokenType.BOOLEAN)) {
      const value = this.previous().value === 'true';
      return {
        type: 'Expression',
        expressionType: ExpressionType.LITERAL,
        value,
        range: { start, end: this.getCurrentPosition() }
      };
    }

    if (this.match(TokenType.NUMBER)) {
      const value = parseFloat(this.previous().value);
      return {
        type: 'Expression',
        expressionType: ExpressionType.LITERAL,
        value,
        range: { start, end: this.getCurrentPosition() }
      };
    }

    if (this.match(TokenType.STRING)) {
      const value = this.previous().value;
      return {
        type: 'Expression',
        expressionType: ExpressionType.LITERAL,
        value,
        range: { start, end: this.getCurrentPosition() }
      };
    }

    if (this.match(TokenType.IDENTIFIER)) {
      const identifier = this.previous().value;
      return {
        type: 'Expression',
        expressionType: ExpressionType.IDENTIFIER,
        identifier,
        range: { start, end: this.getCurrentPosition() }
      };
    }

    if (this.match(TokenType.LEFT_PAREN)) {
      const expr = this.parseExpression();
      this.consume(TokenType.RIGHT_PAREN, "Expected ')' after expression");
      return expr;
    }

    this.throwError('Expected expression', ['identifier', 'number', 'string', 'boolean', '(']);

    // Return dummy expression for error recovery
    return {
      type: 'Expression',
      expressionType: ExpressionType.LITERAL,
      value: null,
      range: { start, end: this.getCurrentPosition() }
    };
  }

  private parseHTMLElement(): HTMLElementNode {
    const start = this.getCurrentPosition();

    // This is a simplified HTML parser - in a real implementation,
    // you'd need much more sophisticated HTML parsing
    const tagName = 'div'; // Placeholder
    const attributes: AttributeNode[] = [];
    const children: (HTMLElementNode | TextNode | InterpolationNode)[] = [];

    const end = this.getCurrentPosition();

    return {
      type: 'HTMLElement',
      tagName,
      attributes,
      children,
      isSelfClosing: false,
      isVoidElement: false,
      range: { start, end }
    };
  }

  private parseInterpolation(): InterpolationNode {
    const start = this.getCurrentPosition();

    this.consume(TokenType.INTERPOLATION_START, "Expected '{' for interpolation");

    const expression = this.parseExpression();

    this.consume(TokenType.INTERPOLATION_END, "Expected '}' after interpolation");

    const end = this.getCurrentPosition();

    return {
      type: 'Interpolation',
      expression,
      range: { start, end }
    };
  }

  private parseTextNode(): TextNode {
    const start = this.getCurrentPosition();

    const content = this.consume(TokenType.HTML_TEXT, 'Expected text content').value;

    const end = this.getCurrentPosition();

    return {
      type: 'Text',
      content,
      range: { start, end }
    };
  }

  private checkLifecycleHook(): boolean {
    const value = this.peek().value;
    return value === 'onMount' || value === 'onUnmount' || value === 'onUpdate';
  }

  // Utility methods
  private match(...types: TokenType[]): boolean {
    for (const type of types) {
      if (this.check(type)) {
        this.advance();
        return true;
      }
    }
    return false;
  }

  private check(type: TokenType): boolean {
    if (this.isAtEnd()) return false;
    return this.peek().type === type;
  }

  private advance(): Token {
    if (!this.isAtEnd()) this.current++;
    return this.previous();
  }

  private isAtEnd(): boolean {
    return this.peek().type === TokenType.EOF;
  }

  private peek(): Token {
    return this.tokenStream.tokens[this.current]!;
  }

  private previous(): Token {
    return this.tokenStream.tokens[this.current - 1]!;
  }

  private consume(type: TokenType, message: string): Token {
    if (this.check(type)) return this.advance();

    this.throwError(message, [type.toString()]);

    // Return dummy token for error recovery
    return {
      type: TokenType.EOF,
      value: '',
      position: this.getCurrentPosition(),
      range: { start: this.getCurrentPosition(), end: this.getCurrentPosition() }
    };
  }

  private throwError(message: string, expected: string[]): never {
    const error = new SyntaxError(
      message,
      this.getCurrentPosition(),
      expected,
      this.peek().value,
      this.filename
    );

    if (this.options.allowRecovery && this.errors.length < (this.options.maxErrors || 1)) {
      this.errors.push(error);
      this.synchronize();
      throw error; // Still throw after synchronize for proper error handling
    }

    throw error;
  }

  private synchronize(): void {
    this.advance();

    while (!this.isAtEnd()) {
      if (this.previous().type === TokenType.SEMICOLON) return;

      switch (this.peek().type) {
        case TokenType.COMPONENT:
        case TokenType.CLIENT:
        case TokenType.SERVER:
        case TokenType.MARKUP:
        case TokenType.LET:
        case TokenType.CONST:
          return;
      }

      this.advance();
    }
  }

  private getCurrentPosition(): SourcePosition {
    return this.peek().position;
  }
}
