"""Helper functions with regex capabilities"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/37_helper.regex.ipynb.

# %% auto 0
__all__ = ['find_regex_in_text', 'separate_indices_from_str', 'replace_string_by_indices', 'latex_indices',
           'inline_latex_indices']

# %% ../../nbs/37_helper.regex.ipynb 2
import re
from typing import Pattern, Sequence, Union

# %% ../../nbs/37_helper.regex.ipynb 4
def find_regex_in_text(
        text: str, # Text in which to find regex patter
        pattern: str | Pattern[str] # The regex pattern
        ) -> list[tuple[int, int]]: # Each tuple is of the form `(a,b)` where `text[a:b]` is the regex match.
    # TODO: rename into regex_indices_in_text
    # TODO: swap parameters.
    """Return ranges in `text` where `pattern` occurs.
    """
    matches = re.finditer(pattern, text)
    return [match.span() for match in matches]

# %% ../../nbs/37_helper.regex.ipynb 13
def separate_indices_from_str(
        text: str,
        indices: list[tuple[int, int]] # The indices for substrings in `text` to separate.
        ) -> list[str]: # Each str is a substring of `text`, either a substring of `text` specified by `indices`, or substrings in between the substrings specified by `indices`.
    """Divide `text` into parts along the substrings specified by `indices`. 

    Assumes that the pairs of indices specified by `indices` are in order from
    first to last and the ranges specified by these pairs are all disjoint.

    `''.join(output)` should recover `text`.
    """
    if not indices:
        return [text]
    parts = [text[:indices[0][0]]]
    for index_pair, next_pair in zip(indices, indices[1:]):
        parts.append(text[index_pair[0]:index_pair[1]])
        parts.append(text[index_pair[1]:next_pair[0]])
    last_pair = indices[-1]
    parts.append(text[last_pair[0]:last_pair[1]])
    parts.append(text[last_pair[1]:])
    return parts

# %% ../../nbs/37_helper.regex.ipynb 16
def replace_string_by_indices(
        string: str, # String in which to make replacemenets 
        replace_ranges: Sequence[Union[Sequence[int], int]], # A list of lists/tuples of int's or a single list/tuple of int's. Each 
        replace_with: Sequence[str] | str # The str(s) which will replace the substrings at `replace_ranges` in `string`. `replace_with` must be a str exactly when `replace_ranges` is a Sequence of a single Sequence of int.
        ) -> str:  # The str obtained by replacing the substrings at `replace_range` in `string` by the strs specified by `replace_with`.
    """Replace parts of ``string`` at the specified locations"

    Use this with `find_regex_in_text`.

    **Parameters**

    - ``string`` - `str`
    - ``replace_ranges`` - `Sequence[Sequence[int] | int]`
        - Either a list of lists/tuples of one or two int's. A list/tuple
        ``[a,b]`` or ``(a,b)`` means that ``string[a:b]`` is to be replaced.
        ``[a]`` or ``(a)`` means that ``string[a:]`` is to be replaced. The ranges should
        not overlap and should be arranged in chronological order.
    - ``replace_with`` - `Sequence[str] | str`
        - The str's which will replace the parts represented by 
        ``replace_ranges``. ``replace_ranges`` and ``replace_with`` must be
        both lists or both not lists. If they are lists, they must be of 
        the same length.

    **Returns**

    - str

    """
    if isinstance(replace_with, str):
        replace_ranges = [replace_ranges]
        replace_with = [replace_with]
    if len(replace_ranges) != len(replace_with):
        raise ValueError(
            'The lengths of `replace_ranges` and `replace_with` are different.')
    if len(replace_ranges) == 0:
        return string

    str_parts = _str_parts(string, replace_ranges, replace_with)
    return "".join(str_parts)


def _str_parts(string, replace_ranges, replace_with):
    """Divide `string` into parts divided outside of `replace_ranges`
    and with `replace_with` inserted."""
    str_parts = []
    for i, replace_string in enumerate(replace_ranges):
        replace_string = replace_with[i]
        if i > 0 and len(replace_ranges[i-1]) == 1:
            unreplaced_start_index = len(string)
        elif i > 0 and len(replace_ranges[i-1]) != 1:
            unreplaced_start_index = replace_ranges[i-1][1]
        else:
            unreplaced_start_index = 0
        unreplaced_end_index = replace_ranges[i][0]
        str_parts.append(string[unreplaced_start_index:unreplaced_end_index])
        str_parts.append(replace_string)
    # Add the last (unreplaced) part to str_parts.
    if len(replace_ranges[-1]) == 1:
        unreplaced_start_index = len(string)
    else:
        unreplaced_start_index = replace_ranges[-1][1]
    str_parts.append(string[unreplaced_start_index:])
    return str_parts

# %% ../../nbs/37_helper.regex.ipynb 22
def latex_indices(
        text: str,
        ) -> list[tuple[int, int]]:
    """Returns the indices in the text containing LaTeX str.
    
    This may not work correctly if the text has a LaTeX
    formatting issue.
    
    **Parameters**

    - text - str

    **Returns**

    - tuple[int]
        - Each tuple is of the form `(start, end)` where
        `text[start:end]` is a LaTeX string, including any leading trailing
        dollar signs (`$` or `$$`).
    """
    # r'(?<!\\)\$.*(?<!\\)\$|(?<!\\)\$(?<!\\)\$.*(?<!\\)\$(?<!\\)\$'
    # return find_regex_in_text(text, '\$\$[^\$]*\$\$|\$[^\$]*\$')

    # return find_regex_in_text(text, r'((?<!\\)\$\$?)[^\$]*\1')
    pattern = re.compile(r"(?<!\\)\$\$.*?(?<!\\)\$\$|(?<!\\)\$.*?(?<!\\)\$", re.DOTALL)
    return find_regex_in_text(text, pattern)


def inline_latex_indices(
        text: str,
        ) -> list[tuple[int, int]]:
    """Returns the indices in the text containing inline LaTeX str surrounded by
    `$$`.
    
    This may not work correctly if the text has a LaTeX
    formatting issue or if any LaTeX string has a dollar sign `\$`.
    
    **Parameters**

    - text - str

    **Returns**

    - tuple[int]
        - Each tuple is of the form `(start, end)` where
        `text[start:end]` is a LaTeX string, including any leading trailing
        dollar signs (`$$`).
    """
    # r'(?<!\\)\$.*(?<!\\)\$|(?<!\\)\$(?<!\\)\$.*(?<!\\)\$(?<!\\)\$'
    # return find_regex_in_text(text, '\$\$[^\$]*\$\$|\$[^\$]*\$')

    # return find_regex_in_text(text, r'((?<!\\)\$\$?)[^\$]*\1')
    pattern = re.compile(r"(?<!\\)\$\$.*?(?<!\\)\$\$", re.DOTALL)
    return find_regex_in_text(text, pattern)

# def math_mode_str_in_text(
#         text: str # The str in which to find the latex math mode str.
#         ) -> list[tuple[int, int]]: # Each tuple is of the form `(start,end)`, where `text[start:end]` is a part in `text` with LaTeX math mode text.
#     """
#     Return the indices of the math mode text.
#     """
#     pattern = re.compile(r"(?<!\\)\$\$.*?(?<!\\)\$\$|(?<!\\)\$.*?(?<!\\)\$", re.DOTALL)
#     return find_regex_in_text(text, pattern)

# %% ../../nbs/37_helper.regex.ipynb 29
#| export
