# Shared facade planning against explicit local service and framing exclusions.
from dataclasses import dataclass
from hearth import Box, Frame


@dataclass(frozen=True)
class FacadeDesign:
    """Arrange aligned openings while preserving local reserved volumes.

    Exclusions use room coordinates. They reserve design space for later
    children; they never grant write permission. The enclosing composition still
    validates all actual writes and affected contracts transactionally.
    """
    height: int = 3
    sill: int = 1
    width: int = 2
    planters: bool = True

    def openings(self, length, frame=Frame(), access=False, exclusions=()):
        if self.height not in (2, 3) or self.width not in (1, 2) or self.sill < 1 or self.sill + self.height > 4:
            raise ValueError('Facade openings require width 1..2, height 2..3, sill >=1 and top <=3')
        door = Box((length // 2 - 1, 0, -1), (length // 2 + 1, 1, 0)) if access else None
        candidates = []
        for x in range(1, length - self.width):
            inner = Box((x, self.sill, 0), (x + self.width - 1, self.sill + self.height - 1, 0))
            exterior = Box((x - 1, self.sill - 1, -2 if self.planters else -1), (x + self.width, self.sill + self.height - 1, 0))
            if any(frame.box(inner).intersection(b) for b in exclusions) or (door and inner.intersection(door)):
                continue
            flush = any(frame.box(exterior).intersection(b) for b in exclusions) or bool(door and exterior.intersection(door))
            candidates.append({'at': (x, self.sill, 0), 'width': self.width, 'height': self.height, 'flush': flush})
        desired = 2 if length >= 9 else 1
        targets = (2 if length >= 11 else 1, length - self.width - (2 if length >= 11 else 1)) if desired == 2 else ((length - self.width) // 2,)
        chosen = []
        for target in targets:
            feasible = [c for c in candidates if all(abs(c['at'][0] - q['at'][0]) >= self.width + 1 for q in chosen)]
            if not feasible:
                continue
            c = min(feasible, key=lambda q: (abs(q['at'][0] - target), q['flush'], q['at'][0]))
            chosen.append(c)
        return tuple(sorted(chosen, key=lambda q: q['at'][0]))


def local_exclusions(exclusions, frame):
    """Convert enclosing design reservations into a child's local coordinates."""
    return tuple(Box.enclosing(frame.inverse(p) for p in b.corners()) for b in exclusions)
