"""For parsing notation notes"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../../../../../nbs/51_markdown.obsidian.personal.notation.parse.ipynb.

# %% auto 0
__all__ = ['parse_notation_note', 'notation_in_note', 'main_of_notation']

# %% ../../../../../nbs/51_markdown.obsidian.personal.notation.parse.ipynb 2
from os import PathLike
import re
from typing import Optional, Union

from ....markdown.file import MarkdownFile, MarkdownLineEnum 
from ...links import link_ranges_in_text, ObsidianLink, MARKDOWNLINK_PATTERN, MARKDOWNLINK_CAPTURE_PATTERN, WIKILINK_PATTERN
from ...vault import VaultNote
from ..note_type import assert_note_is_of_type, PersonalNoteTypeEnum

# %% ../../../../../nbs/51_markdown.obsidian.personal.notation.parse.ipynb 6
def _main_of_notation_from_text(
        file_text: str # Text of notation note
        ) -> Union[str, VaultNote, None]: # The name main information note that `notation_note` comes from. Returns `None` if `notation_note` does not come from such a note.
    """Return the name of the note from which the notation comes from.

    Helper function for `parse_notation_string`.
    """
    if '%%' in file_text and 'main: ' in file_text:
        return None

    # First, get rid of the notation, i.e. the first latex math mode string
    match = re.search(fr'\$.+?\$\s+', file_text) 
    if match is None:
        raise ValueError(
            'There seems to be a formatting error in a notation note'
            ' and the notation has not been identified. The following is the'
            f' content of the notation note:\n{file_text}')
    start, end = match.span()
    file_text = file_text[end:]
    
    link_locations = link_ranges_in_text(file_text)
    if not link_locations:
        return None
    start, end = link_locations[0]
    link_str = file_text[start:end]
    link_parse = ObsidianLink.from_text(link_str)
    main_note_name = link_parse.file_name
    return main_note_name

# %% ../../../../../nbs/51_markdown.obsidian.personal.notation.parse.ipynb 11
def _divide_bulleted_list_mf_at_end(
        mf: MarkdownFile
        ) -> tuple[MarkdownFile, list[tuple[str, str]]]: # The first MarkdownFile contains the main content. The second MarkdonwFile contains the bulleted list at the end; if no such bulleted list exists, then this is None.
    """Return a `MarkdownFile` consisting of just the main content
    of a notation note along with the list of tuples capturing the
    information of the trailing bulleted list of links
    for notations used in the notation note.

    Assumes that the bulleted list is formatted correctly
    (i.e. each line is of the form `- [<notation>](<link>)`)

    Helper function for `parse_notation_note`.
    """
    main_parts = mf.parts.copy()
    trailing_parts = []
    for part in reversed(mf.parts):
        if part['type'] == MarkdownLineEnum.BLANK_LINE:
            main_parts.pop()
            continue
        if not _part_is_unordered_list_and_is_of_markdownstyle_link(part):
            break
        last_part = main_parts.pop() # Should be the same as `part`
        trailing_parts.insert(0, last_part)
    
    bulleted_list = [
        _notat_str_and_linked_notat_note_name_from_bullet_point_part(part)
        for part in trailing_parts]
    return MarkdownFile(main_parts), bulleted_list
    

def _part_is_unordered_list_and_is_of_markdownstyle_link(
        part: dict[str, Union[str, MarkdownLineEnum]]
        ) -> bool:
    """
    
    Helper function for `_divide_bulleted_list_mf_at_end`
    """
    if part['type'] != MarkdownLineEnum.UNORDERED_LIST:
        return False
    if not part['line'].startswith('- '):
        return False
    if not re.match(MARKDOWNLINK_PATTERN, part['line'][2:]):
        return False
    return True
    

def _notat_str_and_linked_notat_note_name_from_bullet_point_part(
        part: dict[str, Union[str, MarkdownLineEnum]]
        ) -> tuple[str, str]:
    match = re.match(MARKDOWNLINK_CAPTURE_PATTERN, part['line'][2:])
    return match[1], match[2]

# %% ../../../../../nbs/51_markdown.obsidian.personal.notation.parse.ipynb 14
def parse_notation_note(
        notation_note: Union[str, VaultNote],
        vault: Optional[PathLike] = None # The vault If `None`, then uses `notation_note.vault`
        ) -> tuple[Union[dict, None], Union[str, None], str, MarkdownFile,
                   list[tuple[str, str]]]:
    """Parse information from the notation note.

    **Returns**

    - tuple[Union[dict, None], str, ObsidianLink, MarkdownFile, list[tuple[str, str]]]
        - The first entry is the YAML frontmatter meta, if available.
        - The second entry is the notation string
        - The third entry is the name of the "main note" of the notation note. This is usual
          the linked note in the link `[[<linked_note>|denotes]]`. If no such main note
          exists, then this is `None`.
        - The fourth entry is the MarkdownFile consisting of the "main" content of the note,
          which excludes the information given by all of the other entries.
        - The fifth entry is a list of tuples of two `str`'s representing a
          bulleted list of notation notes to which `notation_note` links to.
          Each tuple is of the form `(latex_str, notation_note_name)` and
          the corresponding entry in the bulleted list is of the form
          `- [<latex_str>](<notation_note_name)`.

    **Raises**

    - UserWarning
        - If the (non-YAML frontmatter meta) contents of the note do not start
        inn the form `<Notation> [[<link_to_note>|denotes]]`; the name of the
        notation note is included in the warning message.
    - ValueError
        - If the notation note is not formatted correctly by starting
        with the notation with dollar signs `$`.
    - AssertionError
        - If `notation_note` is not determined to be a notation note.
    """
    if isinstance(notation_note, str):
        notation_note = VaultNote(vault, name=notation_note)
    if not vault:
        vault = notation_note.vault
    assert_note_is_of_type(notation_note, PersonalNoteTypeEnum.NOTATION_NOTE)

    mf = MarkdownFile.from_vault_note(notation_note)
    metadata = mf.metadata()
    mf_without_metadata = MarkdownFile(
        [part for part in mf.parts if part['type'] != MarkdownLineEnum.META])

    file_text = str(mf_without_metadata)

    main_mf, linked_notations_list = _divide_bulleted_list_mf_at_end(mf_without_metadata)
    _remove_the_notation_str_and_denotes_in_main_mf(main_mf, notation_note)

    return (metadata, _get_notation_string(file_text, notation_note),
            _main_of_notation_from_text(file_text), main_mf,
            linked_notations_list)


def _get_notation_string(
        file_text: str,
        notation_note: VaultNote
        ) -> str:
    """Return the notation string from the text of the notation note..

    Assumes that the notation string exists and is well formatted.

    Helper function for `parse_notation_note`.
    """
    try:
        return re.search(r'\$.+?\$', file_text).group()
    except AttributeError as e:
        raise ValueError(
            'There seems to be a formatting error in a notation note'
            ' and the notation has not been identified. The following is the'
            f' name of the notation note: {notation_note.name}')
    

def _remove_the_notation_str_and_denotes_in_main_mf(
        main_mf: MarkdownFile,
        notation_note: VaultNote):
    """Remove the text `<notation> denotes ` which starts the
    notation description.

    Helper function of `parse_notation_note`.
    """
    for part in main_mf.parts:
        if part['type'] == MarkdownLineEnum.BLANK_LINE:
            continue
        match = re.match(fr'^\$.+?\$ ({WIKILINK_PATTERN}|denotes)\s*', part['line']) 
        if match is None:
            raise ValueError(
                'There seems to be a formatting error in a notation note'
                ' and the notation has not been identified. The following is the'
                f' name of the notation note: {notation_note.name}')
        else:
            start, end = match.span()
            part['line'] = part['line'][end:]
            break
    

# %% ../../../../../nbs/51_markdown.obsidian.personal.notation.parse.ipynb 21
def notation_in_note(
        notation_note: Union[str, VaultNote],
        vault: Optional[PathLike] = None 
        ) -> str:
    """Return the name of the note from which the notation comes from.
    
    **Parameters**

    - `notation_note` - Union[str, VaultNote]
        - Either
            
            - The name of the notation note or
            - The `VaultNote` object of the notation note. 
            
        The note name is expected to be unique
        inside the vault specified by `vault`. 
        This is expected to contain `'notation'` as a substring. 
        Usually, this is expected to be formatted in one of
        the following forms:
            - `'<reference_name>_notation_<rest_of_note_name>'`
            - `'notation.<rest_of_note_name>'
    - `vault` - Pathlike or `None`
        - Defaults to `None`
        
    **Returns**

    - str
        - The notation in LaTeX, including the dollar signs `$`.

    **Raises**

    """
    _, notation_in_note, _, _, _ = parse_notation_note(notation_note, vault)
    return notation_in_note

# %% ../../../../../nbs/51_markdown.obsidian.personal.notation.parse.ipynb 24
def main_of_notation(
        notation_note: VaultNote, # The VaultNote object representing the notation note.
        as_note: bool = False # If `False`, then returns the name of the note, and returns a VaultNote object with the same vault as `notation_note` otherwise. The vault used to get the `VaultNote` is the vault of `notation_note`.
        ) -> Union[str, VaultNote, None]: # The (name of the) main information note that `notation_note` comes from. Returns `None` if `notation_note` does not come from such a note.
    """Return the name of the note from which the notation comes from.
            
    **Raises**

    - ValueError
        - If the notation note is not formatted correctly by starting
        with the notation with dollar signs `$`.
    """
    vault = notation_note.vault
    _, _, main_note_name, _, _ = parse_notation_note(notation_note, vault)
    if main_note_name is None:
        return None
    if as_note:
        return VaultNote(notation_note.vault, name=main_note_name)
    else:
        return main_note_name
