import dataclasses
import shlex
import typing
from collections.abc import Sequence
from typing import Literal, Self, get_args

from cibuildwheel.logger import log
from cibuildwheel.util.helpers import parse_key_value_string

BuildFrontendName = Literal["pip", "build", "build[uv]", "uv"]


@dataclasses.dataclass(frozen=True)
class BuildFrontendConfig:
    name: BuildFrontendName
    args: Sequence[str] = ()

    @classmethod
    def from_config_string(cls, config_string: str) -> Self:
        config_dict = parse_key_value_string(config_string, ["name"], ["args"])
        name = " ".join(config_dict["name"])
        if name not in get_args(BuildFrontendName):
            names = ", ".join(repr(n) for n in get_args(BuildFrontendName))
            msg = f"Unrecognised build frontend {name!r}, must be one of {names}"
            raise ValueError(msg)

        name = typing.cast("BuildFrontendName", name)

        args = config_dict.get("args") or []
        return cls(name=name, args=args)

    def options_summary(self) -> str | dict[str, str]:
        if not self.args:
            return self.name
        else:
            return {"name": self.name, "args": repr(self.args)}


def _get_verbosity_flags(level: int, frontend: BuildFrontendName, *, py38: bool) -> list[str]:
    if level < 0:
        if frontend.startswith("build") and py38:
            msg = f"build_verbosity {level} is not supported for {frontend} frontend. Ignoring."
            log.warning(msg)
            return []
        return ["-" + -level * "q"]

    if level > 0:
        if frontend == "pip":
            return ["-" + level * "v"]
        if level > 1:
            return ["-" + (level - 1) * "v"]

    return []


def _split_config_settings(config_settings: str) -> list[str]:
    config_settings_list = shlex.split(config_settings)
    return [f"-C{setting}" for setting in config_settings_list]


# Based on build.__main__.main.
def parse_config_settings(config_settings_str: str) -> dict[str, str | list[str]]:
    config_settings: dict[str, str | list[str]] = {}
    for arg in shlex.split(config_settings_str):
        setting, _, value = arg.partition("=")
        existing_value = config_settings.get(setting)
        if existing_value is None:
            config_settings[setting] = value
        elif isinstance(existing_value, str):
            config_settings[setting] = [existing_value, value]
        else:
            existing_value.append(value)

    return config_settings


def get_build_frontend_extra_flags(
    build_frontend: BuildFrontendConfig, verbosity_level: int, config_settings: str, *, py38: bool
) -> list[str]:
    return [
        *_split_config_settings(config_settings),
        *build_frontend.args,
        *_get_verbosity_flags(verbosity_level, build_frontend.name, py38=py38),
    ]
