# Serializable contracts, binding requests and pure candidate plans.
from dataclasses import dataclass, field
from typing import Any, Protocol
from hearth.space import Box, Frame, Pos


@dataclass(frozen=True)
class Domain:
    minimum: float | None = None
    maximum: float | None = None
    choices: tuple = ()

    def accepts(self, value):
        return (not self.choices or value in self.choices) and (self.minimum is None or value >= self.minimum) and (self.maximum is None or value <= self.maximum)


@dataclass(frozen=True)
class Capability:
    name: str
    tags: tuple[str, ...]
    parameters: dict[str, Domain] = field(default_factory=dict)
    offers: tuple[str, ...] = ()
    assumptions: tuple[str, ...] = ()
    adaptations: tuple[str, ...] = ()
    guarantees: tuple[str, ...] = ()
    version: str = '1'
    inputs: dict = field(default_factory=dict)
    locked: tuple[str, ...] = ()


@dataclass(frozen=True)
class Rule:
    kind: str
    cells: tuple[Pos, ...] = ()
    box: Box | None = None
    data: dict = field(default_factory=dict)
    phase: str = 'invariant'

    def transformed(self, frame):
        return Rule(self.kind, tuple(frame.point(p) for p in self.cells), frame.box(self.box) if self.box else None, self.data, self.phase)


@dataclass(frozen=True)
class Grant:
    key: str
    box: Box
    verbs: tuple[str, ...]
    tags: tuple[str, ...] = ()
    limit: int = 100000
    whole: bool = False


@dataclass(frozen=True)
class Port:
    key: str
    kind: str
    frame: Frame
    region: Box
    capacity: int = 1
    grant: str | None = None
    alignment: int = 1
    facts: dict = field(default_factory=dict)
    delegate: tuple[str, str] | None = None


@dataclass(frozen=True)
class Contract:
    envelope: Box
    ports: tuple[Port, ...] = ()
    grants: tuple[Grant, ...] = ()
    rules: tuple[Rule, ...] = ()
    decisions: dict = field(default_factory=dict)
    reads: tuple[Box, ...] = ()
    atomic_object: bool = False

    def transformed(self, frame):
        return Contract(frame.box(self.envelope), tuple(Port(p.key, p.kind, frame.compose(p.frame), frame.box(p.region), p.capacity, p.grant, p.alignment, p.facts, p.delegate) for p in self.ports), tuple(Grant(g.key, frame.box(g.box), g.verbs, g.tags, g.limit, g.whole) for g in self.grants), tuple(r.transformed(frame) for r in self.rules), self.decisions, tuple(frame.box(b) for b in self.reads), self.atomic_object)


@dataclass(frozen=True)
class Binding:
    host: str
    port: str
    verb: str = 'install'
    region: Box | None = None


@dataclass(frozen=True)
class Write:
    point: Pos
    state: str
    operation: str = 'realize'
    preserve_owner: bool = False
    nbt: dict | None = None


@dataclass(frozen=True)
class Child:
    key: str
    component: Any
    params: dict = field(default_factory=dict)
    frame: Frame = Frame()
    bindings: tuple[Binding, ...] = ()
    seed: int | None = None


@dataclass(frozen=True)
class Choice:
    key: str
    candidates: tuple[Child, ...]
    score: Any
    tolerance: float = 0.5


@dataclass
class Plan:
    writes: list[Write] = field(default_factory=list)
    children: list[Child] = field(default_factory=list)
    relations: list[tuple[str, str, str]] = field(default_factory=list)

    def block(self, point, state, operation='realize', **kwargs):
        self.writes.append(Write(tuple(point), state, operation, **kwargs))

    def fill(self, box, state, operation='fill'):
        self.writes.extend(Write(p, state, operation) for p in box.cells())


class Component(Protocol):

    def capability(self) -> Capability:
        ...

    def negotiate(self, context, parameters: dict) -> Contract:
        ...

    def realize(self, context, contract: Contract) -> Plan:
        ...


class Registry:

    def __init__(self):
        self.entries = {}

    def register(self, component):
        self.entries[component.capability().name] = component
        return component

    def select(self, *tags):
        return tuple(self.entries[k] for k in sorted(self.entries) if set(tags) <= set(self.entries[k].capability().tags))
