"""Latex functions for identifying macros and commands (to replace)"""

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

# %% auto 0
__all__ = ['custom_commands', 'regex_pattern_detecting_command']

# %% ../../../nbs/48_helper.latex.macros_and_commands.ipynb 2
import re
from typing import Union

import regex

from .comments import remove_comments

# %% ../../../nbs/48_helper.latex.macros_and_commands.ipynb 6
def _argument_detection(group_num: int) -> str:
    """
    Helper function to `regex_pattern_detecting_command`, and `_commands_from_def`

    This basically helps detect balanced curly braces for invocations of commands.
    """
    return "\{((?>[^{}]+|\{(?1)\})*)\}".replace("1", str(group_num))

# %% ../../../nbs/48_helper.latex.macros_and_commands.ipynb 7
def custom_commands(
        preamble: str, # The preamble of a LaTeX document.
        ) -> list[tuple[str, int, Union[str, None], str]]: # Each tuple consists of 1. the name of the custom command 2. the number of parameters 3. The default argument if specified or `None` otherwise, and 4. the display text of the command.
    """
    Return a dict mapping commands (and math operators) defined in `preamble` to
    the number of arguments display text of the commands.

    Assumes that the newcommands only have at most one default parameter (newcommands with
    multiple default parameters are not valid in LaTeX).

    Ignores all comented newcommands.
    """
    preamble = remove_comments(preamble)
    latex_commands = _commands_from_newcommand_and_declaremathoperator(preamble)
    tex_commands = _commands_from_def(preamble)
    return latex_commands + tex_commands


def _commands_from_newcommand_and_declaremathoperator(
        preamble: str, # The preamble of a LaTeX document
        ) -> list[tuple[str, int, Union[str, None], str]]: # Each tuple consists of 1. the name of the custom command 2. the number of parameters 3. The default argument if specified or `None` otherwise, and 4. the display text of the command.
    """
    Get custom commands from invocations of `\newcommand` and `DeclareMathOperator`
    in the preamble.

    Helper function to `custom_commands`
    """
    # newcommand_regex = regex.compile(
    #     r'(?<!%)\s*\\(?:(?:re)?newcommand|DeclareMathOperator)\s*\{\\\s*(\w+)\s*\}\s*(?:\[(\d+)\]\s*(?:\[(\w+)\])?)?\s*\{((?>[^{}]+|\{(?4)\})*)\}', re.MULTILINE)
    newcommand_regex = regex.compile(
        r'(?<!%)\s*\\(?:(?:re)?newcommand|DeclareMathOperator)\s*(?:\{\\\s*(\w+)\s*\}|\\\s*(\w+))\s*(?:\[(\d+)\]\s*(?:\[(\w+)\])?)?\s*\{((?>[^{}]+|\{(?5)\})*)\}', re.MULTILINE)

    commands = []
    for match in newcommand_regex.finditer(preamble):
        name_surrounded_in_parentheses = match.group(1) # e.g. \newcommand{\A}
        name_without_parentheses = match.group(2) # e.g. \newcommand\A
        num_args = match.group(3)
        optional_default_arg = match.group(4)
        definition = match.group(5)

        if name_surrounded_in_parentheses is not None:
            name = name_surrounded_in_parentheses
        else:
            name = name_without_parentheses

        # Convert the number of arguments to an integer, if it was specified
        if num_args is not None:
            num_args = int(num_args)
        else:
            num_args = 0

        commands.append((name, num_args, optional_default_arg, definition))
    return commands


def _commands_from_def(
        preamble: str
        ) -> list[tuple[str, int, Union[str, None], str]]: # Each tuple consists of 1. the name of the custom command 2. the number of parameters 3. The default argument if specified or `None` otherwise, and 4. the display text of the command.
    """
    """
    def_command_identifying = r'(?<!%)\s*\\def\s*'
    command_name_identifying = r'\\\s*(\w+)\s*'
    command_def = _argument_detection(2)
    def_regex = regex.compile(
        f"{def_command_identifying}{command_name_identifying}{command_def}"
    )
    return [(match.group(1), 0, None, match.group(2))
            for match in def_regex.finditer(preamble)]


# %% ../../../nbs/48_helper.latex.macros_and_commands.ipynb 10
def regex_pattern_detecting_command(
        command_tuple: tuple[str, int, Union[None, str], str], # Consists of 1. the name of the custom command 2. the number of parameters 3. The default argument if specified or `None` otherwise, and 4. the display text of the command.
        ) -> regex.Pattern:
    """Return a `regex.pattern` object (not a `re.pattern` object) detecting
    the command with the specified number of parameters, optional argument,
    and display text.

    Assumes that the curly braces used to write the invocations of the commands
    are balanced and properly nested. Assumes that there are no two commands
    of the same name.
    """
    command_name, num_parameters, optional_arg, _ = command_tuple
    backslash_name = fr"\\{command_name}"
    optional_argument_detection = fr"(?:\[(.*?)\])?" if optional_arg is not None else ""
    if optional_arg is not None:
        trailing_arguments = [_argument_detection(i) for i in range(2, 1+num_parameters)]
        trailing_args_pattern = "\\s*".join(trailing_arguments)
        pattern = (f"{backslash_name}\\s*{optional_argument_detection}\\s*{trailing_args_pattern}")
    elif num_parameters > 0:
        arguments = [_argument_detection(i) for i in range(1, 1+num_parameters)]
        args_pattern = "\\s*".join(arguments)
        pattern = f"{backslash_name}\\s*{args_pattern}"
    else:
        # Match the command name exactly without letters immediately following
        # (but underscores following are okay).
        pattern = f"{backslash_name}(?![^\W_])"
    return regex.compile(pattern)

    
