from collections.abc import Callable
from typing import overload

from remedapy import find_last_index
from remedapy.decorator import make_data_last


@overload
def truncate(s: str, n: int, /, *, omission: str = '...', separator: str = '') -> str: ...


@overload
def truncate(n: int, /, *, omission: str = '...', separator: str = '') -> Callable[[str], str]: ...


@make_data_last
def truncate(string: str, n: int, /, *, omission: str = '...', separator: str = '') -> str:
    """
    Given a string, a number, and optional omission, truncates the string to the specified length.

    If len(string) <= n, returns the string as is.

    The specified length includes the length of the omission,
    so the number of characters retained from the string is n - len(omission).

    Default omission is '...'.

    You can optionally specify a separator to truncate the string at the last occurrence of the separator.

    This can prevent words being cut weirdly.

    Parameters
    ----------
    string: str
        The string to truncate (positional-only).
    n: int
        Desired length of output (positional-only).
    omission: str
        The suffix to put after truncation (keyword-only, optional).
    separator: str
        Separator at whose last occurrence to truncate the string (keyword-only, optional).

    Returns
    -------
    str
        The truncated string.

    Examples
    --------
    Data first:
    >>> R.truncate('Hello, world!', 8)
    'Hello...'
    >>> R.truncate('Hello, world!', 20)
    'Hello, world!'
    >>> R.truncate('Hello, world!', 5, omission='')
    'Hello'
    >>> R.truncate('cat, dog, mouse', 12, omission='__', separator=',')
    'cat, dog__'
    >>> R.truncate('cat, dog, mouse', 12, omission='__', separator='-')
    'cat, dog, __'

    Data last:
    >>> R.pipe('Hello, world!', R.truncate(8))
    'Hello...'
    >>> R.pipe('cat, dog, mouse', R.truncate(12, omission='__', separator=','))
    'cat, dog__'

    """
    if len(string) <= n:
        return string
    cutoff_index = n - len(omission)
    shortened = string[:cutoff_index]
    if not separator or string[cutoff_index] == separator:
        return shortened + omission
    index = find_last_index(shortened, lambda x: x == separator)
    if index > 0:
        return shortened[:index] + omission
    return shortened + omission
