"""Process a subcommand CLI manifest."""

from dataclasses import dataclass


@dataclass
class ManifestSubcommand:
    """Processed subcommand CLI manifest subcommand info."""

    name: str
    short_help: str
    visible_aliases: list[str]
    hidden_aliases: list[str]


@dataclass
class Manifest:
    """Processed subcommand CLI manifest."""

    subcommands: list[ManifestSubcommand]
    tab_completion_supported: bool
    tab_completion_install_option: str | list[str] | None
    tab_completion_show_option: str | list[str] | None


MANIFEST_TYPE_SINGLE = "slices-cli:subcommand-metadata"
MANIFEST_TYPE_MULTIPLE = "slices-cli:multi-subcommand-metadata"
SUPPORTED_VERSIONS = ("1.0",)

SUPPORTED_FIELDS_SINGLE = (
    "version",
    "tab_completion_supported",
    "tab_completion_install_option",
    "tab_completion_show_option",
    "short_help",
    "visible_aliases",
    "hidden_aliases",
)
SUPPORTED_FIELDS_MULTI = (
    "version",
    "tab_completion_supported",
    "tab_completion_install_option",
    "tab_completion_show_option",
    "subcommands",
)
SUPPORTED_SUBCOMMAND_FIELDS = ("name", "short_help", "visible_aliases", "hidden_aliases")
SUPPORTED_FIELDS_EXTRA = ("cached_cli_version",)

REQUIRED_FIELDS_SINGLE = ("short_help",)
REQUIRED_FIELDS_MULTI = ("subcommands",)
REQUIRED_SUBCOMMAND_FIELDS = ("name", "short_help")


class ManifestException(Exception):
    """An exception while processing a subcommand CLI manifest."""


def process_manifest(
    executable_name: str, manifest_dict: dict, *, require_subcommand_with_exe_name: bool = True
) -> Manifest:
    """Process the manifest dictionary into a Manifest object."""
    assert executable_name.startswith("slices-")
    subcommand_basename = str(executable_name[len("slices-") :])

    manifest_dict = dict(manifest_dict)

    # First handle the @type and version fields
    if "@type" not in manifest_dict or manifest_dict["@type"] not in (MANIFEST_TYPE_SINGLE, MANIFEST_TYPE_MULTIPLE):
        raise ManifestException("Invalid or missing manifest @type")
    is_single = manifest_dict["@type"] == MANIFEST_TYPE_SINGLE
    del manifest_dict["@type"]

    if "version" not in manifest_dict:
        raise ManifestException("Missing manifest version")
    version = manifest_dict["version"]
    del manifest_dict["version"]
    if version not in SUPPORTED_VERSIONS:
        raise ManifestException(f"Unsupported manifest version ({version!r} is not in {', '.join(SUPPORTED_VERSIONS)})")

    # Handle the regular fields
    for field in manifest_dict:
        if field not in ((SUPPORTED_FIELDS_SINGLE if is_single else SUPPORTED_FIELDS_MULTI) + SUPPORTED_FIELDS_EXTRA):
            raise ManifestException(f"Unsupported manifest field ({field!r})")
    for field in REQUIRED_FIELDS_SINGLE if is_single else REQUIRED_FIELDS_MULTI:
        if field not in manifest_dict:
            raise ManifestException(f"Missing required manifest field ({field!r})")

    subcommands = []
    if is_single:
        subcommands.append(
            ManifestSubcommand(
                name=subcommand_basename,
                short_help=manifest_dict["short_help"],
                visible_aliases=manifest_dict.get("visible_aliases", []),
                hidden_aliases=manifest_dict.get("hidden_aliases", []),
            )
        )
    else:
        for index, subcommand in enumerate(manifest_dict["subcommands"]):
            for field in subcommand:
                if field not in SUPPORTED_SUBCOMMAND_FIELDS:
                    raise ManifestException(f"Unsupported subcommand field ({field!r}) for subcommand {index}")
            for field in REQUIRED_SUBCOMMAND_FIELDS:
                if field not in subcommand:
                    raise ManifestException(f"Missing required subcommand field ({field!r}) for subcommand {index}")
            if subcommand["name"] in [s.name for s in subcommands]:
                raise ManifestException(f"Duplicate subcommand name {subcommand['name']!r}")
            subcommands.append(
                ManifestSubcommand(
                    name=subcommand["name"],
                    short_help=subcommand["short_help"],
                    visible_aliases=subcommand.get("visible_aliases", []),
                    hidden_aliases=subcommand.get("hidden_aliases", []),
                )
            )
        if require_subcommand_with_exe_name and subcommand_basename not in [s.name for s in subcommands]:
            raise ManifestException(f"At least one subcommand must have the name {subcommand_basename!r}")

    return Manifest(
        subcommands=subcommands,
        tab_completion_supported=manifest_dict.get("tab_completion_supported", False),
        tab_completion_install_option=manifest_dict.get("tab_completion_install_option"),
        tab_completion_show_option=manifest_dict.get("tab_completion_show_option"),
    )
