import re
import shlex
from collections.abc import Callable
from typing import TypeVar

from typing_extensions import deprecated

__all__ = [
    "to_snake",
    "to_camel",
    "to_pascal",
    "to_kebab",
    "make_lex_separator",
    "comma_separator",
]

T = TypeVar("T")
OuterCastT = TypeVar("OuterCastT", list, tuple, set)

_to_camel_regexp = re.compile("(_|-)([a-zA-Z])")
_to_snake_regexp = re.compile("([a-z])([A-Z])")


def replace_all(string: str, replacements: dict[str, str]) -> str:
    for old, new in replacements.items():
        string = string.replace(old, new)
    return string


def to_snake(string: str) -> str:
    return replace_all(
        _to_snake_regexp.sub(r"\1_\2", string), {"-": "_", " ": "_"}
    ).lower()


def to_camel(string: str) -> str:
    return _to_camel_regexp.sub(
        lambda match: match[2].upper(), to_snake(string)
    ).rstrip("_-")


def to_pascal(string: str) -> str:
    val = to_camel(string)
    return val[0].upper() + val[1:]


upper_camel = deprecated('Use "to_pascal" instead.')(to_pascal)


def to_kebab(string: str, remove_trailing_underscores: bool = False) -> str:
    result = to_snake(string).replace("_", "-")
    return result if not remove_trailing_underscores else result.rstrip("-")


def make_lex_separator(
    outer_cast: type[OuterCastT], cast: type = str
) -> Callable[[str], OuterCastT]:
    def wrapper(value: str) -> OuterCastT:
        lex = shlex.shlex(value, posix=True)
        lex.whitespace = ","
        lex.whitespace_split = True
        return outer_cast(cast(item.strip()) for item in lex)

    return wrapper


def quote(string: str, quote_char: str = '"') -> str:
    return f"{quote_char}{string}{quote_char}"


comma_separator = make_lex_separator(tuple, str)
