import shlex
from typing import Any, Callable
from typing_extensions import Self


def _default_to_str(v: Any) -> str:
    return shlex.quote(str(v))


class CliFormatter:
    """Build CLI argument strings for subprocess invocation."""

    def __init__(
        self,
        cmd: str = "",
        key_prefix: str = "",
        key_value_delimiter: str = " ",
        multi_value_delimiter: str = " ",
        to_str: Callable[[Any], str] = _default_to_str,
    ) -> None:
        self._cmd = cmd
        self._key_prefix = key_prefix
        self._key_value_delimiter = key_value_delimiter
        self._multi_value_delimiter = multi_value_delimiter
        self._to_str = to_str
        self._pairs: list[tuple[str, str]] = []

    def set_cmd(self, cmd: str) -> Self:
        self._cmd = cmd
        return self

    def clear(self) -> Self:
        self._pairs.clear()
        return self

    def add(self, key: str, value: Any = None) -> Self:
        if value is None:
            return self
        if isinstance(value, str):
            joined = self._to_str(value) if value else ""
        else:
            try:
                joined = self._multi_value_delimiter.join(self._to_str(v) for v in value)
            except TypeError:
                joined = self._to_str(value)
        self._pairs.append((self._key_prefix + key, joined))
        return self

    def __str__(self) -> str:
        parts: list[str] = [self._cmd] if self._cmd else []
        for prefixed_key, quoted_value in self._pairs:
            if not quoted_value:
                parts.append(prefixed_key)
            elif self._key_value_delimiter == " ":
                parts += [prefixed_key, quoted_value]
            else:
                parts.append(prefixed_key + self._key_value_delimiter + quoted_value)
        return " ".join(parts)

    def as_cmd_list(self) -> list[str]:
        return shlex.split(str(self))
