# High-level selection, arrangement, repetition, scattering and encapsulation.
from dataclasses import dataclass
from hearth.kernel import Capability, Contract, Plan, Child, ContractError, Rule
from hearth.space import Box, Frame


@dataclass(frozen=True)
class Network:
    name: str
    envelope: Box
    recipe: object
    tags: tuple = ('composite',)
    rules: tuple[Rule, ...] = ()

    def capability(self):
        return Capability(self.name, self.tags)

    def negotiate(self, ctx, parameters):
        return Contract(self.envelope, rules=self.rules)

    def realize(self, ctx, contract):
        return self.recipe(ctx)


def encapsulate(name, envelope, recipe, tags=('composite',), rules=()):
    return Network(name, envelope, recipe, tags, rules)


def attach(key, component, host, port, frame, verb='install', region=None):
    from hearth.kernel import Binding
    return Child(key, component, frame=frame, bindings=(Binding(host, port, verb, region),))


def arrange(components, origins, prefix='item'):
    components, origins = tuple(components), tuple(origins)
    if len(components) != len(origins):
        raise ValueError('One placement per component is required')
    return tuple(Child(f'{prefix}-{i}', c, frame=Frame(tuple(p))) for i, (c, p) in enumerate(zip(components, origins)))


def repeat(component, origins, prefix='repeat'):
    return arrange([component for _ in origins], origins, prefix)


def scatter(ctx, candidates, predicate, minimum, maximum, purpose='scatter'):
    candidates = sorted(set(candidates))
    ctx.rng(purpose).shuffle(candidates)
    accepted = []
    for p in candidates:
        if predicate(p, accepted):
            accepted.append(p)
            if len(accepted) >= maximum:
                break
    if len(accepted) < minimum:
        raise ContractError('scatter-minimum', ctx.path, conditions=f'Accepted {len(accepted)}, required {minimum}; finite candidate set exhausted')
    return tuple(accepted)


def constrain(condition, rule, context, position=None, reason=''):
    if not condition:
        raise ContractError(rule, context.path, position, reason)
