# Depth and joinery fitted to public wall interfaces without replacing openings.
from dataclasses import dataclass
from hearth import Box, ContractError
from hearth.kernel import Capability, Contract, Plan, Rule
from hearth.blocks import name_of, log, stairs, slab


@dataclass(frozen=True)
class FacadeRelief:
    """Fit external beams and brackets around an already installed facade."""
    width: int
    height: int = 5
    access: bool = False
    exclusions: tuple[Box, ...] = ()

    def capability(self):
        return Capability('facade.joinery', ('decoration', 'timber-frame'), assumptions=('wall-installation host',), adaptations=('preserve shutters and other occupied facade cells',), guarantees=('one-cell projection',))

    def negotiate(self, ctx, parameters):
        if len(ctx.bindings) != 1 or ctx.view.port(ctx.bindings[0].host, ctx.bindings[0].port).kind != 'wall-installation':
            raise ContractError('joinery-host', ctx.path)
        points = {}
        for x in range(self.width):
            points[(x, self.height - 1, -1)] = log('spruce', 'x')
            if not (self.access and abs(x - self.width // 2) <= 1):
                points[(x, -1, -1)] = slab('dark_oak', 'top')
        for x in (0, self.width - 1):
            for y in range(self.height - 1):
                points[(x, y, -1)] = log('stripped_spruce')
        for x, direction in ((1, 'east'), (self.width - 2, 'west')):
            points[(x, self.height - 2, -1)] = stairs('spruce', direction, 'top')
        available = [(q, s) for q, s in sorted(points.items()) if name_of(ctx.state(q)) == 'air' and not any(b.contains(q) for b in self.exclusions)]
        return Contract(Box((0, -1, -1), (self.width - 1, self.height - 1, 0)), rules=(Rule('support', ((0, self.height - 1, 0), (self.width - 1, self.height - 1, 0))),), decisions={'members': available, 'preserved_occupied_cells': len(points) - len(available)})

    def realize(self, ctx, contract):
        p = Plan()
        for q, block in contract.decisions['members']:
            p.block(q, block, 'joinery')
        return p
