"""Helper functions for latex functionalities"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/47_helper.latex.ipynb.

# %% auto 0
__all__ = ['math_mode_string_is_syntactically_valid', 'math_mode_string_is_syntactically_clean', 'reduce_unnecessary_spaces']

# %% ../../nbs/47_helper.latex.ipynb 2
import re

from .regex import latex_indices

# %% ../../nbs/47_helper.latex.ipynb 6
def _is_balanced_braces(s):
    """
    This is a helper function to `math_mode_string_is_syntactically_valid`.

    Note that curly braces (`{`, `}`) that have 
    """
    stack = []
    escaped = False
    
    for _, char in enumerate(s):
        if char == '\\':
            escaped = True
        elif char == '{' and not escaped:
            stack.append(char)
        elif char == '}' and not escaped:
            if not stack:
                return False
            stack.pop()
        else:
            escaped = False
    
    return len(stack) == 0


# %% ../../nbs/47_helper.latex.ipynb 8
def _detect_backslash_space_curly(
        text: str
        ) -> bool:
    """
    Return `True` if there is some backslash `\` followed
    by spaces and then followed by curly brackets `{`

    Note that the presence of such a combination of text
    will induce a syntax error in LaTeX math mode string.

    This is a helper function of `math_mode_string_is_syntactically_valid`
    """
    pattern = r'\\\s+[{}]'
    match = re.search(pattern, text)
    return bool(match)

# %% ../../nbs/47_helper.latex.ipynb 10
def _is_left_right_balanced(
        latex_string: str
        ) -> bool:
    """
    Return `True` if occurrences of `\left` and `\right` are balanced. 

    This is a helper function of `math_mode_string_is_syntactically_valid`

    This function does not test whether occurrences of the
    appropriately corresponding braces are balanced. For instance,
    the function would return `True` on the input `\left . \right)`.
    Compare against `_is_semantically_left_right_balanced`, which
    is a similar function that tests whether left-right braces
    are "semantically" balanced.
    """
    # Remove all whitespace from the string
    latex_string = ''.join(latex_string.split())
    
    # Find all \left and \right commands
    left_commands = re.findall(r'\\left', latex_string)
    right_commands = re.findall(r'\\right', latex_string)
    
    # Check if the number of \left and \right commands are equal
    if len(left_commands) != len(right_commands):
        return False
    
    # Check if \left always comes before \right
    left_indices = [m.start() for m in re.finditer(r'\\left', latex_string)]
    right_indices = [m.start() for m in re.finditer(r'\\right', latex_string)]
    
    for left, right in zip(left_indices, right_indices):
        if left > right:
            return False
    
    return True



    # # Dictionary to store counts of left and right commands for each brace type
    # brace_counts = {
    #     '(': 0, ')': 0,
    #     '[': 0, ']': 0,
    #     '{': 0, '}': 0,
    #     '|': 0,
    #     '\\|': 0,
    #     '\\{': 0, '\\}': 0,
    #     '.': 0  # For \left. and \right.
    # }
    
    # # Regular expression to match \left and \right commands
    # pattern = r'\\(left|right)(\(|\)|\[|\]|{|}|\||\\\||\\{|\\}|\.)'
    
    # # Find all matches in the latex_string
    # matches = re.finditer(pattern, latex_string)
    
    # for match in matches:
    #     command, brace = match.groups()
    #     if command == 'left':
    #         brace_counts[brace] += 1
    #     elif command == 'right':
    #         brace_counts[brace] -= 1
    
    # # Check if all counts are zero (balanced)
    # return all(count == 0 for count in brace_counts.values()) 

# %% ../../nbs/47_helper.latex.ipynb 12
def _is_semantically_left_right_balanced(
        latex_string: str
        ) -> bool:
    """
    Return `True` if occurrences of `\left` and `\right` macros
    preceding various braces are balanced.

    This is a helper function of `math_mode_string_is_syntactically_clean`

    Compare against `_is_left_right_balanced`, which
    is a similar function that only tests whether left-right
    macros are "syntactically" balanced, without regard to
    the types of braces actually used.
    """
    # Remove all whitespace from the string
    latex_string = ''.join(latex_string.split())
    
    # Define a stack to keep track of opening delimiters
    stack = []
    
    # Define a dictionary to match opening and closing delimiters
    delimiters = {
        '(': ')', '[': ']', '{': '}', '<': '>',
        r'\left(': r'\right)', r'\left[': r'\right]', 
        r'\left{': r'\right}', r'\left<': r'\right>',
        r'\left\{': r'\right\}', r'\left|': r'\right|',
        r'\left\|': r'\right\|', r'\left.': r'\right.',
        r'\left\langle': r'\right\rangle'
    }
    
    # Regular expression to match \left and \right commands with their delimiters
    pattern = r'(\\left[\(\[\{\<\|\.\|]|\\right[\)\]\}\>\|\.\|]|\(|\)|\[|\]|\{|\}|\<|\>)'
    
    # Find all delimiters and \left/\right commands
    tokens = re.findall(pattern, latex_string)
    
    for token in tokens:
        if token.startswith(r'\left') or token in '([{<':
            stack.append(token)
        elif token.startswith(r'\right') or token in ')]}>':
            if not stack:
                return False
            last_open = stack.pop()
            if token != delimiters.get(last_open):
                return False
    
    # If the stack is empty, all delimiters are balanced
    return len(stack) == 0


# %% ../../nbs/47_helper.latex.ipynb 14
def _has_invalid_left_right_bracket(
        latex_string: str
        ) -> bool:
    """
    Return `True` is there is at least one invalid use of
    a `\left` or `\right` command.

    This is a helper function of `math_mode_string_is_syntactically_valid`
    """
    # Remove all whitespace from the string
    latex_string = ''.join(latex_string.split())
    
    # Define valid brackets for \left and \right
    valid_brackets = [
        r'(', r')',
        r'[', r']',
        r'\(', r'\)', r'\[', r'\]',  # Parentheses and square brackets
        r'\{', r'\}',                # Curly braces (escaped)
        r'<', r'>',                  # Angle brackets
        r'\|',                       # Vertical bar
        r'|',                       # Vertical bar
        r'\\\|',                     # Double vertical bar (escaped)
        r'\.',                       # Dot
        r'.',                       # Dot
        r'\\lfloor', r'\\rfloor',    # Floor brackets
        r'\\lceil', r'\\rceil',      # Ceiling brackets
        r'\\langle', r'\\rangle'     # Angle brackets (commands)
    ]
    
    # Escape special regex characters and join with |
    valid_brackets_pattern = '|'.join(re.escape(b) for b in valid_brackets)
    
    # Pattern to match \left or \right followed by a valid bracket
    valid_pattern = rf'\\(left|right)({valid_brackets_pattern})'
    
    # Find all \left and \right commands
    commands = list(re.finditer(r'\\(left|right)', latex_string))
    
    for command in commands:
        # Check if the command is followed by a valid bracket
        if not re.match(valid_pattern, latex_string[command.start():]):
            # If not, return True and the invalid command
            invalid_part = latex_string[command.start():command.start()+6]  # Adjust slice as needed
            # return True, invalid_part
            return True
    
    # return False, None
    return False

# %% ../../nbs/47_helper.latex.ipynb 16
import re

def _has_double_script(
        latex_string: str
        ) -> bool:
    """
    Return `True` is there is at least one double superscript
    or double subscript in `latex_string` 

    This function fails to give correct outputs for more
    nuanced texts, such as `r"x^{2}_{3}^{4}"`; while in
    principle, the function should return `True` on this
    input, the actual return value is `False`.

    This is a helper function of `math_mode_string_is_syntactically_valid`
    """
    # Remove all whitespace from the string
    latex_string = ''.join(latex_string.split())
    
    # Function to match balanced braces
    def match_braces(s, start):
        count = 0
        for i, char in enumerate(s[start:], start):
            if char == '{':
                count += 1
            elif char == '}':
                count -= 1
                if count == 0:
                    return i
        return len(s) - 1

    # Find all subscripts and superscripts
    scripts = []
    i = 0
    while i < len(latex_string):
        if latex_string[i] in '^_' and latex_string[i-1] != '\\':
            scripts.append((latex_string[i], i))
            i += 1
            if i < len(latex_string) and latex_string[i] == '{':
                i = match_braces(latex_string, i)
        i += 1

    # Check for double scripts
    for j in range(len(scripts) - 1):
        if scripts[j][0] == scripts[j+1][0]:
            # return True, latex_string[scripts[j][1]:scripts[j+1][1]+1]
            return True

    # return False, None
    return False    

# %% ../../nbs/47_helper.latex.ipynb 18
def math_mode_string_is_syntactically_valid(
        text: str,
        ) -> bool:
    """
    Return `True` if `text` is determined to be syntactically valid
    as a latex str.

    There may be TeX syntax rules beyond the scope of this function.

    Some caveats:

    `text` is allowed to have dollar signs `$` and is also allowed to not have
    dollar signs. Even if `text` does not have dollar signs, this function
    may return `True`. Even if `text` has dollar signs, this function may return
    `False` if the entire string is not a singular math mode string or if the
    dollar signs are not used in a math-mode-valid way.
    """
    # 
    if len(latex_indices(text)) > 1:
        return False
    if _detect_backslash_space_curly(text):
        return False
    if not _is_balanced_braces(text):
        return False
    if _has_invalid_left_right_bracket(text):
        return False
    if not _is_left_right_balanced(text):
        return False
    if _has_double_script(text):
        return False
    return True



# %% ../../nbs/47_helper.latex.ipynb 34
def math_mode_string_is_syntactically_clean(
        text: str,
        ) -> bool:
    """
    Return `True` if `text` is syntactically "clean" as a LaTeX math mode str.
    
    While the precise meaning of this may be subjective, here we will
    consider `text` to be clean, assuming that it is syntactically valid, if

    - It does not have double blackslashes
    """
    if r'\\' in text:
        return False

# %% ../../nbs/47_helper.latex.ipynb 37
def reduce_unnecessary_spaces(
        text: str,

        ) -> str:
    """
    Return a string modifying `text` by removing spaces which are
    unnecessary for the purposes of considering the string as a 
    LaTeX string.
    """
    pattern = r'([{_^\\()])\s+'
    text = re.sub(pattern, r'\1', text)
    pattern = r'\s+([}_^()])'
    text = re.sub(pattern, r'\1', text)
    return text
    # for char in ['{', '_', '^', '}', '\\']:
    #     text = re.sub(fr'\s*{chr}\s*', chr, text)
