"""
Expression parsing utilities.

This module provides functions for parsing logical expressions from infix
notation to the prefix notation used internally by the framework.
"""

from typing import List, Tuple, Any, Union


def parse_expression(tokens: List[str]) -> Tuple[List[Any], int]:
    """Parses a list of tokens representing a propositional expression and returns
    the expression in prefix notation.
    """
    if not tokens:  # Check if tokens are empty before processing
        raise ValueError("Empty token list")
    token = tokens.pop(0)  # Get the next token
    if token == "(":  # Handle binary operator case (indicated by parentheses)
        if not tokens:  # Check if tokens are empty after removing first token
            raise ValueError(f"Empty token list after opening parenthesis")
        closing_parentheses = tokens.pop()  # Remove the closing parenthesis
        if closing_parentheses != ")":
            raise SyntaxError(
                f"The sentence {tokens} is missing a closing parenthesis."
            )
        if not tokens:  # Check if tokens are empty after removing parentheses
            raise ValueError(f"Empty token list after removing parentheses")
        operator, left, right = op_left_right(tokens)
        left_arg, left_comp = parse_expression(left)  # Parse the left argument
        right_arg, right_comp = parse_expression(right)  # Parse the right argument
        complexity = left_comp + right_comp + 1
        return [operator, left_arg, right_arg], complexity 
    if token.isalnum():  # Handle atomic sentences
        return [token], 0
    elif token.startswith("\\"):  # Handle all LaTeX operators 
        # This includes: \top, \bot, \Future, \Past, \future, \past, etc.
        # Special case for nullary operators like \top and \bot
        if token in {"\\top", "\\bot"}:
            return [token], 0
        if not tokens:  # Check if tokens are empty after operator
            raise ValueError(f"Empty token list after operator {token}")
        arg, comp = parse_expression(tokens)
        return [token, arg], comp + 1
    arg, comp = parse_expression(tokens)
    return [token, arg], comp + 1 


def op_left_right(tokens: List[str]) -> Tuple[str, List[str], List[str]]:
    """Divides whatever is inside a pair of parentheses into the left argument,
    right argument, and the operator."""

    def balanced_parentheses(tokens: List[str]) -> bool:
        """Check if parentheses are balanced in the argument."""
        stack = []
        for token in tokens:
            if token == "(":
                stack.append(token)
            elif token == ")":
                if not stack:
                    return False
                stack.pop()
        return len(stack) == 0

    def check_right(tokens: List[str], operator: str) -> List[str]:
        if not tokens:
            raise ValueError(f"Expected an argument after the operator {operator}")
        if not balanced_parentheses(tokens):
            raise ValueError(
                f"Unbalanced parentheses for the tokens {tokens} " + 
                f"with the operator {operator}."
            )
        return tokens  # Remaining tokens are the right argument

    def cut_parentheses(left: List[str], tokens: List[str]) -> Tuple[str, List[str], List[str]]:
        count = 1  # To track nested parentheses
        while tokens:
            token = tokens.pop(0)
            if token == "(":
                count += 1
                left.append(token)
            elif token == ")":
                count -= 1
                left.append(token)
            elif count > 0:
                left.append(token)
            elif not tokens:
                raise ValueError(f"Extra parentheses in {tokens}.")
            else:
                operator = token
                right = check_right(tokens, operator)
                return operator, left, right
        raise ValueError(f"The expression {tokens} is incomplete.")

    def process_operator(tokens: List[str]) -> str:
        if tokens:
            return tokens.pop(0)
        raise ValueError("Operator missing after operand")

    def extract_arguments(tokens: List[str]) -> Tuple[str, List[str], List[str]]:
        """Extracts the left argument and right argument from tokens."""
        left = []
        while tokens:
            token = tokens.pop(0)
            if token == "(":
                left.append(token)
                return cut_parentheses(left, tokens)
            elif token.isalnum() or token in {"\\top", "\\bot"}:
                left.append(token)
                operator = process_operator(tokens)
                right = check_right(tokens, operator)
                return operator, left, right
            else:
                left.append(token)
        raise ValueError("Invalid expression or unmatched parentheses")

    result = extract_arguments(tokens)
    if result is None:
        raise ValueError("Failed to extract arguments")
    return result