from __future__ import annotations

from typing import TYPE_CHECKING, Any, Tuple, cast

from hatchling.version.scheme.plugin.interface import VersionSchemeInterface

if TYPE_CHECKING:
    from packaging.version import Version


class StandardScheme(VersionSchemeInterface):
    """
    See https://peps.python.org/pep-0440/
    """

    PLUGIN_NAME = 'standard'

    def update(
        self,
        desired_version: str,
        original_version: str,
        version_data: dict,  # noqa: ARG002
    ) -> str:
        from packaging.version import Version

        original = Version(original_version)
        versions = desired_version.split(',')

        for version in versions:
            if version == 'release':
                reset_version_parts(original, release=original.release)
            elif version == 'major':
                reset_version_parts(original, release=update_release(original, [original.major + 1]))
            elif version == 'minor':
                reset_version_parts(original, release=update_release(original, [original.major, original.minor + 1]))
            elif version in {'micro', 'patch', 'fix'}:
                reset_version_parts(
                    original, release=update_release(original, [original.major, original.minor, original.micro + 1])
                )
            elif version in {'a', 'b', 'c', 'rc', 'alpha', 'beta', 'pre', 'preview'}:
                phase, number = parse_letter_version(version, 0)
                if original.pre:
                    current_phase, current_number = parse_letter_version(*original.pre)
                    if phase == current_phase:
                        number = current_number + 1

                reset_version_parts(original, pre=(phase, number))
            elif version in {'post', 'rev', 'r'}:
                number = 0 if original.post is None else original.post + 1
                reset_version_parts(original, post=parse_letter_version(version, number))
            elif version == 'dev':
                number = 0 if original.dev is None else original.dev + 1
                reset_version_parts(original, dev=(version, number))
            else:
                if len(versions) > 1:
                    message = 'Cannot specify multiple update operations with an explicit version'
                    raise ValueError(message)

                next_version = Version(version)
                if self.config.get('validate-bump', True) and next_version <= original:
                    message = f'Version `{version}` is not higher than the original version `{original_version}`'
                    raise ValueError(message)

                return str(next_version)

        return str(original)


def reset_version_parts(version: Version, **kwargs: Any) -> None:
    # https://github.com/pypa/packaging/blob/20.9/packaging/version.py#L301-L310
    internal_version = version._version  # noqa: SLF001
    parts: dict[str, Any] = {}
    ordered_part_names = ('epoch', 'release', 'pre', 'post', 'dev', 'local')

    reset = False
    for part_name in ordered_part_names:
        if reset:
            parts[part_name] = kwargs.get(part_name)
        elif part_name in kwargs:
            parts[part_name] = kwargs[part_name]
            reset = True
        else:
            parts[part_name] = getattr(internal_version, part_name)

    version._version = type(internal_version)(**parts)  # noqa: SLF001


def update_release(original_version: Version, new_release_parts: list[int]) -> tuple[int, ...]:
    # Retain release length
    new_release_parts.extend(0 for _ in range(len(original_version.release) - len(new_release_parts)))

    return tuple(new_release_parts)


def parse_letter_version(*args: Any, **kwargs: Any) -> tuple[str, int]:
    from packaging.version import _parse_letter_version

    return cast(Tuple[str, int], _parse_letter_version(*args, **kwargs))
