"""Canonical role identities and the duties that execute them."""
from __future__ import annotations

from dataclasses import dataclass
from typing import Mapping, Sequence


class RoleCatalogError(ValueError):
    """Raised when a role or duty is absent from the canonical catalog."""


@dataclass(frozen=True)
class RoleDefinition:
    id: str
    legacy_aliases: tuple[str, ...]
    required_capabilities: frozenset[str]


_READONLY_CAPABILITIES = frozenset({"worker-artifact-io", "source-readonly"})

ROLE_DEFINITIONS = (
    RoleDefinition("leader", ("lead",), frozenset({"lead-session"})),
    RoleDefinition("analyser", (), _READONLY_CAPABILITIES),
    RoleDefinition("critic", (), _READONLY_CAPABILITIES),
    RoleDefinition("designer", (), _READONLY_CAPABILITIES),
    RoleDefinition("planner", (), _READONLY_CAPABILITIES),
    RoleDefinition(
        "implementer",
        ("executor",),
        frozenset({"worker-artifact-io", "project-mutation"}),
    ),
    RoleDefinition("verifier", (), _READONLY_CAPABILITIES),
    RoleDefinition(
        "report-writer",
        (),
        _READONLY_CAPABILITIES | frozenset({"extended-artifact-authoring"}),
    ),
    RoleDefinition(
        "translator",
        (),
        _READONLY_CAPABILITIES | frozenset({"extended-artifact-authoring"}),
    ),
)

DUTY_ROLE_IDS = {
    "lead": "leader",
    "analysis-worker": "analyser",
    "discovery-worker": "analyser",
    "diagnosis-worker": "analyser",
    "scope-critic": "critic",
    "acceptance-critic": "critic",
    "direction-selection-worker": "designer",
    "planning-worker": "planner",
    "implementation-executor": "implementer",
    "implementation-verifier": "verifier",
    "acceptance-verifier": "verifier",
    "reverification-worker": "verifier",
    "code-reviewer": "verifier",
    "schedule-verifier": "verifier",
    "report-writer": "report-writer",
    "translator": "translator",
}


class RoleCatalog:
    def __init__(
        self,
        definitions: Sequence[RoleDefinition],
        duty_role_ids: Mapping[str, str],
    ) -> None:
        definition_list = tuple(definitions)
        self._definition_count = len(definition_list)
        self._definitions = {definition.id: definition for definition in definition_list}
        self._aliases = {
            alias: definition.id
            for definition in definition_list
            for alias in definition.legacy_aliases
        }
        self._duty_role_ids = dict(duty_role_ids)
        self._validate()

    def normalize(self, raw: str) -> str:
        role = raw.strip()
        canonical = self._aliases.get(role, role)
        if canonical not in self._definitions:
            raise RoleCatalogError(f"unknown role: {raw}")
        return canonical

    def required_capabilities(self, role: str) -> frozenset[str]:
        return self._definitions[self.normalize(role)].required_capabilities

    def role_for_duty(self, duty_id: str) -> str:
        try:
            return self._duty_role_ids[duty_id]
        except KeyError as exc:
            raise RoleCatalogError(f"unknown duty: {duty_id}") from exc

    def _validate(self) -> None:
        if len(self._definitions) != self._definition_count:
            raise RoleCatalogError("duplicate canonical role")
        if len(self._aliases) != sum(
            len(definition.legacy_aliases) for definition in self._definitions.values()
        ):
            raise RoleCatalogError("duplicate legacy role alias")
        for role in self._duty_role_ids.values():
            self.normalize(role)


ROLE_CATALOG = RoleCatalog(ROLE_DEFINITIONS, DUTY_ROLE_IDS)

# Ids plus legacy aliases. Bundled adapters declare this set so every
# catalog model can be assigned to every role.
ALL_ROLE_TOKENS = frozenset(
    token
    for definition in ROLE_DEFINITIONS
    for token in (definition.id, *definition.legacy_aliases)
)


def normalize_role(raw: str) -> str:
    return ROLE_CATALOG.normalize(raw)


def role_for_duty(duty_id: str) -> str:
    return ROLE_CATALOG.role_for_duty(duty_id)
