import re, sys
from typing import Callable, Any


def safe_int(string: str) -> int | None:
    """
    Safe way to convert a string to an int.

    :param string: string to convert.
    :return: int if text converted to int, else None.
    """
    try:
        return int(string)
    except ValueError:
        return None

def safe_float(string: str) -> float | None:
    """
    Safe way to convert a string to a float.

    :param string: string to convert.
    :return: float if text converted to float, else None.
    """
    try:
        return float(string)
    except ValueError:
        return None


def split(
        string: str,
        split_by: None | str | re.Pattern = None,
        converter: Callable[[str], Any] | None = None) -> list:

    """
    Splits text string and returns a list of elements.

    :param string: The input string to be split.
    :param split_by: The delimiter used for splitting.
                     If None (default), uses str.split().
                     Can be a string or a compiled re.Pattern.
    :param converter: Callable[[str], Any] to be applied to each element after splitting
                      (e.g., int, float, str.strip). If None, elements remain as strings.
    :raises TypeError: If 'split_by' is of an unsupported type.
    :return: List of parsed and optionally converted elements.
    """

    if split_by is None or isinstance(split_by, str):
        res = string.split(split_by)
    elif isinstance(split_by, re.Pattern):
        res = re.split(split_by, string)
    else:
        raise TypeError('split_by must be None, str or re.Pattern.')

    if converter:
        return [converter(el) for el in res]
    return res


def extract_match(
        string: str,
        pattern: re.Pattern,
        pos: int = 0,
        endpos: int = sys.maxsize,
        converter: Callable[[str], Any] | None = None) -> list:
    """
    Performs a RegEx search and returns a list of captured groups.

    It uses the `re.search()` method and extracts all groups captured
    by the provided pattern.

    Can convert all elements with a converter function.

    :param string: The input text to search within.
    :param pattern: The compiled RegEx pattern (re.Pattern) containing capture groups.
    :param pos: Optional. The starting index for the search (default 0).
    :param endpos: Optional. The ending index for the search (default sys.maxsize).
    :param converter: Optional. A callable function (e.g., int, float, str.strip)
                      to be applied to EACH captured group string (default None).
    :return: List of parsed and optionally converted elements. [] if no matches found.
    """
    match = pattern.search(string, pos=pos, endpos=endpos)
    if not match:
        return []
    res = match.groups()
    if converter:
        return [converter(el) for el in res]
    return list(res)
