"""Deterministic Telegram command menus for nutrition customer and staff chats."""

from __future__ import annotations

from collections.abc import Sequence
from dataclasses import dataclass
from enum import StrEnum
from typing import Protocol, assert_never


class NutritionChatRole(StrEnum):
    """The two production command scopes exposed by nutrition coaching."""

    CUSTOMER = "customer"
    STAFF = "staff"


class TelegramCommand(Protocol):
    command: str


class TelegramCommandScope(Protocol):
    @property
    def chat_id(self) -> int | str: ...


class CommandBot(Protocol):
    async def set_my_commands(
        self,
        commands: Sequence[TelegramCommand],
        *,
        scope: TelegramCommandScope,
    ) -> None: ...


@dataclass(frozen=True, slots=True)
class NutritionMenuError(ValueError):
    reason: str

    def __str__(self) -> str:
        return self.reason


_CUSTOMER_COMMANDS = (
    ("start", "Start nutrition coaching"),
    ("help", "Show nutrition coaching help"),
)
_STAFF_COMMANDS = (
    ("help", "Show staff help"),
    ("status", "Show coaching status"),
    ("approve", "Approve the pending item"),
    ("deny", "Deny the pending item"),
)


def nutrition_chat_role(chat_id: int) -> NutritionChatRole:
    """Map private positive chat IDs to customers and group IDs to staff."""
    if isinstance(chat_id, bool) or chat_id == 0:
        raise NutritionMenuError("nutrition command chat ID is invalid")
    return NutritionChatRole.CUSTOMER if chat_id > 0 else NutritionChatRole.STAFF


def nutrition_menu_commands(role: NutritionChatRole) -> tuple[tuple[str, str], ...]:
    """Return the closed command set for one production role."""
    match role:
        case NutritionChatRole.CUSTOMER:
            return _CUSTOMER_COMMANDS
        case NutritionChatRole.STAFF:
            return _STAFF_COMMANDS
        case unreachable:
            assert_never(unreachable)


def validate_nutrition_command_routes(
    customer_chat_ids: tuple[int, ...],
    staff_chat_ids: tuple[int, ...],
) -> tuple[tuple[int, ...], tuple[int, ...]]:
    """Return unique, disjoint customer and staff command routes."""
    customer = tuple(dict.fromkeys(customer_chat_ids))
    staff = tuple(dict.fromkeys(staff_chat_ids))
    if (
        any(isinstance(chat_id, bool) or chat_id == 0 for chat_id in (*customer, *staff))
        or len(customer) != len(customer_chat_ids)
        or len(staff) != len(staff_chat_ids)
        or set(customer) & set(staff)
    ):
        raise NutritionMenuError("nutrition command routes must be unique and disjoint")
    return customer, staff


async def register_nutrition_chat_commands(
    bot: CommandBot,
    chat_id: int,
    *,
    role: NutritionChatRole | None = None,
) -> None:
    """Register exactly one role menu at Telegram's chat-specific scope."""
    from telegram import BotCommand, BotCommandScopeChat

    commands = [
        BotCommand(command, description)
        for command, description in nutrition_menu_commands(
            role if role is not None else nutrition_chat_role(chat_id)
        )
    ]
    await bot.set_my_commands(commands, scope=BotCommandScopeChat(chat_id=chat_id))


__all__ = [
    "NutritionChatRole",
    "NutritionMenuError",
    "nutrition_chat_role",
    "nutrition_menu_commands",
    "register_nutrition_chat_commands",
    "validate_nutrition_command_routes",
]
