from dataclasses import dataclass, replace

from controllers.types import CONTROLLERS
from resources.base import Refused, SYSTEM
from resources.fields import Loaded

MOST = 5
LABEL = 120


def runs(record, type_: str, action: str) -> bool:
    if type_ not in CONTROLLERS:
        return False
    try:
        return callable(CONTROLLERS[type_](record, actor=SYSTEM).action(action))
    except (AttributeError, Refused):
        return False


def whole(value) -> int | None:
    try:
        return int(value)
    except (TypeError, ValueError):
        return None


@dataclass(frozen=True)
class Button(Loaded):
    aliases = {"action": ("action", "method")}
    label: str = ""
    type: str = ""
    action: str = ""
    n: object = None
    body: dict | None = None
    again: bool = False
    say: str = ""
    choice: str = ""
    ask: str = ""
    outcome: str = ""

    @classmethod
    def from_payload(cls, given: dict) -> "Button":
        button = cls.from_json(given)
        return replace(button, label=button.label.strip()[:LABEL], n=whole(button.n))

    def to_json(self) -> dict:
        kept = {"label": self.label, "type": self.type, "action": self.action, "n": self.n, "body": self.body, "again": self.again,
                "say": self.say, "choice": self.choice.strip(), "ask": self.ask.strip(), "outcome": self.outcome.strip()}
        return {key: value for key, value in kept.items() if value not in (None, False, "")}


def spent(button: Button, buttons: list[Button], pressed: list[str]) -> bool:
    chosen = {one.choice for one in buttons if one.choice and one.label in pressed}
    return (not button.again and button.label in pressed) or bool(button.choice and button.choice in chosen)


def one(record, given) -> dict:
    if not isinstance(given, dict):
        return {}
    button = Button.from_payload(given)
    if button.label and button.say.strip():
        return Button(label=button.label, say=button.say.strip(), again=button.again, choice=button.choice, ask=button.ask).to_json()
    if not button.label or not button.action or not runs(record, button.type, button.action):
        return {}
    return button.to_json()


def shaped(record, given) -> list[dict]:
    if not isinstance(given, list):
        return []
    return [kept for kept in (one(record, b) for b in given[:MOST]) if kept]
