from lexer import Token

class AST:
    pass

class Number(AST):
    def __init__(self, token):
        self.token = token
        self.value = token.value

class String(AST):
    def __init__(self, token):
        self.token = token
        self.value = token.value

class Boolean(AST):
    def __init__(self, token):
        self.token = token
        self.value = token.value

class LogicalOp(AST):
    def __init__(self, left, op, right=None):
        self.left = left
        self.op = op
        self.right = right

class UnaryOp(AST):
    def __init__(self, op, right):
        self.op = op
        self.right = right

class BinOp(AST):
    def __init__(self, left, op, right):
        self.left = left
        self.token = self.op = op
        self.right = right

class Comparison(AST):
    def __init__(self, left, op, right):
        self.left = left
        self.op = op
        self.right = right

class Print(AST):
    def __init__(self, value):
        self.value = value

class Assign(AST):
    def __init__(self, left, op, right):
        self.left = left
        self.op = op
        self.right = right

class Variable(AST):
    def __init__(self, token):
        self.token = token
        self.value = token.value

class Block(AST):
    def __init__(self, statements):
        self.statements = statements

class IfStatement(AST):
    def __init__(self, condition, if_block, else_block=None):
        self.condition = condition
        self.if_block = if_block
        self.else_block = else_block

class WhileStatement(AST):
    def __init__(self, condition, body):
        self.condition = condition
        self.body = body

class ForStatement(AST):
    def __init__(self, var_name, iterable, body):
        self.var_name = var_name
        self.iterable = iterable
        self.body = body

class BreakStatement(AST):
    pass

class ContinueStatement(AST):
    pass

class TryExceptStatement(AST):
    def __init__(self, try_block, except_block):
        self.try_block = try_block
        self.except_block = except_block

class Increment(AST):
    def __init__(self, var_name):
        self.var_name = var_name

class Decrement(AST):
    def __init__(self, var_name):
        self.var_name = var_name

class FunctionDefinition(AST):
    def __init__(self, name, parameters, body):
        self.name = name
        self.parameters = parameters
        self.body = body

class FunctionCall(AST):
    def __init__(self, name, arguments):
        self.name = name
        self.arguments = arguments

class ReturnStatement(AST):
    def __init__(self, value):
        self.value = value

class List(AST):
    def __init__(self, elements):
        self.elements = elements

class IndexAccess(AST):
    def __init__(self, target, index):
        self.target = target
        self.index = index

class Dictionary(AST):
    def __init__(self, pairs):
        self.pairs = pairs # List of (key_expr, value_expr) tuples

class PropertyAccess(AST):
    def __init__(self, target, property_name):
        self.target = target
        self.property_name = property_name

class Parser:
    def __init__(self, tokens):
        self.tokens = tokens
        self.pos = 0

    def get_current_token(self):
        if self.pos < len(self.tokens):
            return self.tokens[self.pos]
        return Token('EOF', None)

    def consume(self, token_type):
        if self.get_current_token().type == token_type:
            self.pos += 1
        else:
            raise Exception(f"Expected {token_type}, got {self.get_current_token().type}")

    def parse(self):
        statements = []
        while self.get_current_token().type != 'EOF':
            statements.append(self.parse_statement())
        return statements

    def parse_statement(self):
        token = self.get_current_token()

        if token.type == 'PRINT':
            return self.parse_print_statement()

        if token.type == 'ID':
            # Check for assignment
            if self.pos + 1 < len(self.tokens) and self.tokens[self.pos + 1].type in ('ASSIGN', 'PLUS_ASSIGN', 'MINUS_ASSIGN', 'MUL_ASSIGN', 'DIV_ASSIGN'):
                return self.parse_assignment_statement()
            # Check for increment/decrement as a statement
            if self.pos + 1 < len(self.tokens) and self.tokens[self.pos + 1].type in ('INCREMENT', 'DECREMENT'):
                var_token = self.get_current_token()
                self.consume('ID')
                op_token = self.get_current_token()
                self.consume(op_token.type)
                if op_token.type == 'INCREMENT':
                    return Increment(Variable(var_token))
                else:
                    return Decrement(Variable(var_token))
            # Check for function call as a statement
            if self.pos + 1 < len(self.tokens) and self.tokens[self.pos + 1].type == 'LPAREN':
                # Consume the ID token first, then parse the function call
                name_token = self.get_current_token()
                self.consume('ID') # Consume the ID token
                return self.parse_function_call(name_token)

        if token.type == 'FEMBOY_FEMININE':
            return self.parse_if_statement()

        if token.type == 'OTOKONOKO':
            return self.parse_while_statement()

        if token.type == 'FOR':
            return self.parse_for_statement()

        if token.type == 'FUNCTION_DEF':
            return self.parse_function_definition()

        if token.type == 'RETURN':
            return self.parse_return_statement()

        if token.type == 'PASS':
            self.consume('PASS')
            return Block([]) # A pass statement is an empty block

        if token.type == 'BREAK':
            self.consume('BREAK')
            return BreakStatement()

        if token.type == 'CONTINUE':
            self.consume('CONTINUE')
            return ContinueStatement()

        if token.type == 'TRY':
            return self.parse_try_except_statement()

        raise Exception(f"Invalid statement starting with token {token.type}")

    def parse_print_statement(self):
        self.consume('PRINT')
        expr = self.expression()
        return Print(expr)

    def parse_assignment_statement(self):
        var_token = self.get_current_token()
        self.consume('ID')
        var_node = Variable(var_token)
        
        assign_op_token = self.get_current_token()
        if assign_op_token.type in ('ASSIGN', 'PLUS_ASSIGN', 'MINUS_ASSIGN', 'MUL_ASSIGN', 'DIV_ASSIGN'):
            self.consume(assign_op_token.type)
        else:
            raise Exception(f"Expected assignment operator, got {assign_op_token.type}")

        right_expr = self.expression()

        if assign_op_token.type == 'ASSIGN':
            return Assign(left=var_node, op=assign_op_token, right=right_expr)
        else:
            # For compound assignments, create a BinOp as the right-hand side of the Assign
            # The operation is derived from the compound assignment token type
            op_type = assign_op_token.type.replace('_ASSIGN', '') # e.g., PLUS_ASSIGN -> PLUS
            op_token = Token(op_type, assign_op_token.value[0]) # e.g., Token(PLUS, '+')
            bin_op_node = BinOp(left=var_node, op=op_token, right=right_expr)
            return Assign(left=var_node, op=Token('ASSIGN', 'is'), right=bin_op_node)

    def parse_if_statement(self):
        self.consume('FEMBOY_FEMININE')

        condition = self.expression()

        self.consume('FEMBOYCORE')

        if_block_statements = []
        while self.get_current_token().type != 'PERIODT':
            if self.get_current_token().type == 'EOF':
                raise Exception("Unterminated if block: Expected 'Periodt'")
            if_block_statements.append(self.parse_statement())
        self.consume('PERIODT')
        if_block = Block(if_block_statements)

        else_block = None
        if self.get_current_token().type == 'ANDROGYNY':
            self.consume('ANDROGYNY')
            self.consume('FEMBOYCORE')

            else_block_statements = []
            while self.get_current_token().type != 'PERIODT':
                if self.get_current_token().type == 'EOF':
                    raise Exception("Unterminated else block: Expected 'Periodt'")
                else_block_statements.append(self.parse_statement())
            self.consume('PERIODT')
            else_block = Block(else_block_statements)

        return IfStatement(condition, if_block, else_block)

    def parse_while_statement(self):
        self.consume('OTOKONOKO')

        condition = self.expression()

        self.consume('FEMBOYCORE')

        body_statements = []
        while self.get_current_token().type != 'PERIODT':
            if self.get_current_token().type == 'EOF':
                raise Exception("Unterminated while loop: Expected 'Periodt'")
            body_statements.append(self.parse_statement())
        self.consume('PERIODT')
        body = Block(body_statements)

        return WhileStatement(condition, body)

    def parse_for_statement(self):
        self.consume('FOR')
        var_name_token = self.get_current_token()
        self.consume('ID')
        self.consume('ASSIGN') # Using ASSIGN for 'in' for now, will refine later
        iterable_expr = self.expression()

        self.consume('FEMBOYCORE')

        body_statements = []
        while self.get_current_token().type != 'PERIODT':
            if self.get_current_token().type == 'EOF':
                raise Exception("Unterminated for loop: Expected 'Periodt'")
            body_statements.append(self.parse_statement())
        self.consume('PERIODT')
        body = Block(body_statements)

        return ForStatement(var_name_token.value, iterable_expr, body)

    def parse_function_definition(self):
        self.consume('FUNCTION_DEF')
        name_token = self.get_current_token()
        self.consume('ID')
        
        # Parse parameters
        parameters = []
        if self.get_current_token().type == 'LPAREN':
            self.consume('LPAREN')
            while self.get_current_token().type != 'RPAREN':
                param_token = self.get_current_token()
                self.consume('ID')
                parameters.append(param_token.value)
                if self.get_current_token().type == 'COMMA':
                    self.consume('COMMA')
            self.consume('RPAREN')
            

        self.consume('FEMBOYCORE')

        body_statements = []
        while self.get_current_token().type != 'PERIODT':
            if self.get_current_token().type == 'EOF':
                raise Exception("Unterminated function definition: Expected 'Periodt'")
            body_statements.append(self.parse_statement())
        self.consume('PERIODT')
        body = Block(body_statements)

        return FunctionDefinition(name_token.value, parameters, body)

    def parse_return_statement(self):
        self.consume('RETURN')
        value = self.expression()
        return ReturnStatement(value)

    def factor(self):
        token = self.get_current_token()
        if token.type == 'INTEGER':
            self.consume('INTEGER')
            return Number(token)
        elif token.type == 'FLOAT':
            self.consume('FLOAT')
            return Number(token) # Using Number AST for both int and float for now
        elif token.type == 'STRING':
            self.consume('STRING')
            return String(token)
        elif token.type == 'KAWAII' or token.type == 'CRINGE':
            self.consume(token.type)
            return Boolean(token)
        elif token.type == 'NULL':
            self.consume('NULL')
            return Null()
        elif token.type == 'ID':
            # Consume the ID token first
            id_token = token
            self.consume('ID')
            # Now check what follows the ID
            next_token = self.get_current_token()
            if next_token.type == 'LPAREN':
                # It's a function call
                return self.parse_function_call(id_token)
            elif next_token.type == 'DOT':
                # It's a property access
                return self.parse_property_access(Variable(id_token)) # Pass Variable node as target
            elif next_token.type == 'LBRACKET':
                # It's an index access
                return self.parse_index_access(Variable(id_token))
            elif next_token.type == 'INCREMENT':
                self.consume('INCREMENT')
                return Increment(Variable(id_token))
            elif next_token.type == 'DECREMENT':
                self.consume('DECREMENT')
                return Decrement(Variable(id_token))
            else:
                # It's a simple variable
                return Variable(id_token)
        elif token.type == 'LPAREN': # Handle parenthesized expressions
            self.consume('LPAREN')
            node = self.expression()
            self.consume('RPAREN')
            return node
        elif token.type == 'LBRACKET': # Handle list literals
            return self.parse_list_literal()
        elif token.type == 'LBRACE': # Handle dictionary literals
            return self.parse_dictionary_literal()
        else:
            raise Exception(f"Expected integer, string, boolean, identifier, or literal, got {token.type}")

    def unary_expression(self):
        token = self.get_current_token()
        if token.type == 'NOT':
            self.consume('NOT')
            right_node = self.unary_expression() # NOT applies to the next unary_expression
            return UnaryOp(token, right_node)
        return self.factor()

    def term(self):
        node = self.unary_expression()
        while self.get_current_token().type in ('MUL', 'DIV'):
            token = self.get_current_token()
            self.consume(token.type)
            node = BinOp(left=node, op=token, right=self.unary_expression())
        return node

    def comparison_expression(self):
        node = self.term()

        # Handle addition/subtraction
        while self.get_current_token().type in ('PLUS', 'MINUS'):
            token = self.get_current_token()
            self.consume(token.type)
            node = BinOp(left=node, op=token, right=self.term())

        # Handle comparisons
        if self.get_current_token().type in ('EQ', 'NEQ', 'GT', 'GTE', 'LT', 'LTE'):
            op_token = self.get_current_token()
            self.consume(op_token.type)
            right_node = self.comparison_expression() # Recursively parse right side of comparison
            node = Comparison(left=node, op=op_token, right=right_node)

        return node

    def expression(self):
        node = self.comparison_expression()
        while self.get_current_token().type in ('AND', 'OR'):
            op_token = self.get_current_token()
            self.consume(op_token.type)
            right_node = self.comparison_expression()
            node = LogicalOp(left=node, op=op_token, right=right_node)
        return node

    def parse_function_call(self, name_token):
        self.consume('LPAREN')
        arguments = []
        if self.get_current_token().type != 'RPAREN':
            while True:
                arguments.append(self.expression())
                if self.get_current_token().type == 'COMMA':
                    self.consume('COMMA')
                else:
                    break
        self.consume('RPAREN')
        return FunctionCall(name_token.value, arguments)

    def parse_list_literal(self):
        self.consume('LBRACKET')
        elements = []
        # Check if the list is empty
        if self.get_current_token().type == 'RBRACKET':
            self.consume('RBRACKET')
            return List(elements)

        # Parse first element
        elements.append(self.expression())

        # Parse subsequent elements
        while self.get_current_token().type == 'COMMA':
            self.consume('COMMA')
            elements.append(self.expression())

        # Expect closing bracket
        self.consume('RBRACKET')

        return List(elements)

    def parse_dictionary_literal(self):
        self.consume('LBRACE')
        pairs = []
        # Check if the dictionary is empty
        if self.get_current_token().type == 'RBRACE':
            self.consume('RBRACE')
            return Dictionary(pairs)

        # Parse first key-value pair
        key = self.expression()
        self.consume('COLON')
        value = self.expression()
        pairs.append((key, value))

        # Parse subsequent key-value pairs
        while self.get_current_token().type == 'COMMA':
            self.consume('COMMA')
            key = self.expression()
            self.consume('COLON')
            value = self.expression()
            pairs.append((key, value))

        # Expect closing brace
        self.consume('RBRACE')
        return Dictionary(pairs)

    def parse_index_access(self, target_node):
        self.consume('LBRACKET')
        index_node = self.expression()
        self.consume('RBRACKET')
        return IndexAccess(target_node, index_node)

    def parse_property_access(self, target_node):
        self.consume('DOT') # Assuming DOT token for property access
        property_name_token = self.get_current_token()
        self.consume('ID')
        return PropertyAccess(target_node, property_name_token.value)

    def parse_try_except_statement(self):
        self.consume('TRY')
        self.consume('FEMBOYCORE')
        try_block_statements = []
        while self.get_current_token().type != 'PERIODT':
            if self.get_current_token().type == 'EOF':
                raise Exception("Unterminated try block: Expected 'Periodt'")
            try_block_statements.append(self.parse_statement())
        self.consume('PERIODT')
        try_block = Block(try_block_statements)

        self.consume('EXCEPT')
        self.consume('FEMBOYCORE')
        except_block_statements = []
        while self.get_current_token().type != 'PERIODT':
            if self.get_current_token().type == 'EOF':
                raise Exception("Unterminated except block: Expected 'Periodt'")
            except_block_statements.append(self.parse_statement())
        self.consume('PERIODT')
        except_block = Block(except_block_statements)

        return TryExceptStatement(try_block, except_block)