# Reusable geometric checks registered through the public validator extension point.
from hearth.kernel.validation import validator, state, fail
from hearth.blocks import solid, name_of
from collections import deque


@validator('daylight-path')
def daylight_path(scene, node, rule):
    """Check a real transmissive path from a well to each declared glazing cell.

    This bounded six-neighbor model permits air, glass and thin light hangers.
    It does not predict Minecraft sky-light levels or replace room light checks.
    """

    def transmits(p):
        name = name_of(state(scene, p))
        return name in ('air', 'cave_air', 'chain', 'lantern') or name == 'glass' or name.endswith('_stained_glass')

    if not rule.cells or rule.box is None:
        yield fail('daylight-contract', node, None, 'A bounded well and glazing targets are required')
        return
    reached = {rule.cells[0]} if transmits(rule.cells[0]) else set()
    queue = deque(reached)
    while queue:
        x, y, z = queue.popleft()
        for dx, dy, dz in ((1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)):
            q = (x + dx, y + dy, z + dz)
            if rule.box.contains(q) and q not in reached and transmits(q):
                reached.add(q)
                queue.append(q)
    for q in rule.cells:
        if q not in reached:
            yield fail('daylight-path', node, q, 'Glazing and loft well are separated by opaque geometry')


@validator('vertical-cover')
def vertical_cover(scene, node, rule):
    """Check actual weather cover along bounded world-vertical columns.

    Horizontal frame rotations preserve world Y. This is a voxel coverage check,
    not a fluid simulator or a structural-mechanics proof.
    """
    for x, y, z in rule.cells:
        if not any(solid(state(scene, (x, yy, z))) or 'glass' in name_of(state(scene, (x, yy, z))) for yy in range(y, rule.box.hi[1] + 1)):
            yield fail('roof-cover', node, (x, y, z), 'Weather skin has an uncovered column')


@validator('enclosed-volume')
def enclosed_volume(scene, node, rule):
    """Flood actual air to detect exterior leaks, exempting explicit floor apertures.

    This is a six-neighbor voxel enclosure check, not fluid or collision simulation.
    Apertures are node-local cells on the bounded volume's floor boundary.
    """
    allowed = {node.frame.point(tuple(q)) for q in rule.data.get('apertures', ())}
    if any(p[1] != rule.box.lo[1] or not rule.box.contains(p) for p in allowed):
        yield fail('enclosure-aperture', node, None, 'Apertures must be bounded floor cells')
        return

    def permeable(p):
        name = name_of(state(scene, p))
        return not solid(state(scene, p)) and 'glass' not in name and not name.endswith('_door')

    reached = {q for q in rule.cells if permeable(q)}
    if not reached:
        yield fail('enclosure-seed', node, rule.cells[0] if rule.cells else None, 'The room needs an actual interior air cell')
        return
    queue = deque(sorted(reached))
    while queue:
        p = queue.popleft()
        if p in allowed:
            continue
        if any(p[k] in (rule.box.lo[k], rule.box.hi[k]) for k in range(3)):
            yield fail('enclosure-leak', node, p, 'Actual interior air reaches the exterior boundary')
            return
        for dx, dy, dz in ((1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)):
            q = (p[0] + dx, p[1] + dy, p[2] + dz)
            if q not in reached and rule.box.contains(q) and permeable(q):
                reached.add(q)
                queue.append(q)
