# Extension-only probes for non-cardinal circulation and curve interfaces.
from dataclasses import replace
import pytest
from hearth import Scene, Box, Frame, Binding, ContractError
from hearth.kernel import validate, Child, Plan
from hearth.components.primitives import Volume
from hearth.environment import Terrain, gradient, wave
from hearth.blocks import state_of
from hearth.persistence import export_scene, load_scene, semantic_digest
from hearth_extensions import Polyline, Endpoint, Link, Boundary, IlluminateBoundary, Platform


def bridge_scene(seed=0, water=3, frame=Frame()):
    domain = Box((-8, -4, -8), (45, 42, 45))
    s = Scene(seed, domain=frame.box(domain))
    s.place('land', Terrain(domain, gradient(.02, .01, 1) + wave(.2, .1, .15, seed), water), frame=frame)
    s.place('a', Platform('/land'), frame=frame)
    s.place('b', Platform('/land'), frame=frame.compose(Frame((18, 0, 27))))
    a, b = Endpoint('/a', 'south'), Endpoint('/b', 'north')
    s.place('link', Link(a, b, '/land', rise=1), frame=frame, bindings=(a.binding(), b.binding()))
    return s.finalize()


def test_diagonal_bridge_actual_route_support_railings_and_reload(work):
    s = bridge_scene()
    assert any(n.type == 'extension.trestles' for n in s.nodes.values())
    assert len(set(x for x, z in s.nodes['/link'].contract.decisions['center'])) > 5
    assert not validate(s, True)
    p = next(iter(s.cells('/link/deck')))
    q = s.inspect(p)
    assert any('connect' in str(r) for r in s.inspect(p)['related']) or ('/link', 'connects', '/a') in s.relations
    export_scene(s, work / 'diagonal bridge.litematic')
    t = load_scene(work / 'diagonal bridge.litematic')
    assert t.inspect(p) == q and semantic_digest(t) == semantic_digest(s)


def test_curve_boundary_interfaces_and_external_attachment():
    s = Scene(3, domain=Box((-5, -4, -5), (45, 35, 35)))
    s.place('land', Terrain(s.domain, gradient(.015, .01, 3)))
    curve = Polyline.bezier(((0, 0), (7, 25), (28, -3), (37, 20)))
    s.place('boundary', Boundary(curve))
    s.place('lamps', IlluminateBoundary('/boundary'))
    s.finalize()
    ports = s.view.offers('/boundary', 'mount')
    assert len(ports) > 3
    p = ports[0].frame.origin
    q = s.inspect(p)
    assert q['chain'][1]['id'] == '/boundary'
    assert p in s.cells('/boundary', descendants=True)


@pytest.mark.parametrize('width,depth,corner,lantern', [(13, 17, 0, False), (17, 21, 3, True), (25, 13, 4, False)])
def test_polygon_hall_multiple_usable_ports(width, depth, corner, lantern, work):
    from hearth_extensions import Pavilion, Polygon
    s = Scene(2, domain=Box((-15, -4, -15), (42, 60, 42)))
    s.place('land', Terrain(s.domain, gradient(.025, -.018, 4)))
    s.place('hall', Pavilion(Polygon.chamfered(width, depth, corner), '/land', entrances=('north', 'east', 'south', 'west'), lantern=lantern))
    s.finalize()
    assert len(s.view.offers('/hall', 'access')) == 4
    glass = next(p for p, c in s.blocks.items() if c.state == 'minecraft:glass' and '/facade/' in c.owner)
    assert [n['type'] for n in s.inspect(glass)['chain']][:3] == ['window.shuttered', 'extension.polygon-shell', 'extension.pavilion']
    export_scene(s, work / 'hall.litematic')
    assert load_scene(work / 'hall.litematic').inspect(glass) == s.inspect(glass)


@pytest.mark.parametrize('turn', [0, 1, 2, 3])
def test_extension_transforms_geometry_interfaces_claims_and_queries(turn, work):
    from hearth.blocks import blockstates_equivalent
    frame = Frame((7, 3, -5), turn)
    base = bridge_scene()
    moved = bridge_scene(frame=frame)
    assert len(base.blocks) == len(moved.blocks)
    for point, cell in base.blocks.items():
        q = frame.point(point)
        assert blockstates_equivalent(frame.state(cell.state), moved.inspect(q)['state'])
        assert moved.inspect(q)['instance'] == cell.owner
    assert moved.view.port('/a', 'south').frame == frame.compose(base.view.port('/a', 'south').frame)
    assert moved.nodes['/link'].contract.rules[0].cells == tuple(frame.point(p) for p in base.nodes['/link'].contract.rules[0].cells)
    export_scene(moved, work / 'rotated bridge.litematic')
    restored = load_scene(work / 'rotated bridge.litematic')
    q = next(iter(moved.cells('/link/deck')))
    assert restored.inspect(q) == moved.inspect(q)


def test_untried_kinked_ribbon_and_asymmetric_polygon():
    from hearth_extensions import Polygon, Pavilion
    # Endpoints share Z; a global endpoint clipping plane would sever this route.
    curve = Polyline(((4, 8), (4, 12), (13, 16), (22, 12), (22, 8)))
    s = Scene(19, domain=Box((-8, -4, -8), (50, 60, 50)))
    s.place('land', Terrain(s.domain, gradient(base=2), water_level=3))
    s.place('a', Platform('/land'))
    s.place('b', Platform('/land'), frame=Frame((18, 0, 0)))
    a, b = Endpoint('/a', 'south'), Endpoint('/b', 'south')
    s.place('link', Link(a, b, '/land', curve=curve, covered=True), bindings=(a.binding(), b.binding()))
    s.finalize()
    # A six-sided asymmetric footprint absent from the shipped clients.
    polygon = Polygon(((2, 0), (18, 0), (22, 4), (22, 16), (0, 16), (0, 3)))
    other = Scene(19, domain=Box((-10, -4, -10), (40, 60, 40)))
    other.place('land', Terrain(other.domain, gradient(.01, .02, 3)))
    other.place('hall', Pavilion(polygon, '/land', entrances=('north', 'south'), height=7, roof_run=3))
    other.finalize()


def test_graph_parent_substitutes_public_access_components():
    from hearth_extensions.graphs import Campus, Vertex, Edge
    from hearth_extensions import Pavilion, Polygon
    domain = Box((-12, -4, -12), (55, 65, 60))
    variants = []
    for component in (Platform('/land', 13, 13), Pavilion(Polygon.chamfered(13, 13, 2), '/land', entrances=('north', 'south'))):
        s = Scene(6, domain=domain)
        s.place('land', Terrain(domain, gradient(base=3), water_level=3))
        campus = Campus((Vertex('a', component, Frame()), Vertex('b', Platform('/land'), Frame((25, 0, 29)))), (Edge('a', 'south', 'b', 'north'),), '/land', domain, landscape=False)
        s.place('group', campus)
        s.finalize()
        assert len(s.view.offers('/group', 'access')) == 1
        assert ('/group/circulation/link-0', 'connects', '/group/a') in s.relations
        variants.append(semantic_digest(s))
    assert variants[0] != variants[1]


@pytest.mark.parametrize('environment_seed,structure_seed', [(0, 0), (1, 5), (2, 3), (3, 2), (4, 1), (5, 4), (6, 0), (7, 5)])
def test_independent_extension_environment_structure_pairs(environment_seed, structure_seed):
    from hearth_extensions.assessment import freeze_probe, connector_scene
    frozen = freeze_probe(environment_seed)
    digest = semantic_digest(frozen)
    scene = connector_scene(frozen, structure_seed)
    assert semantic_digest(frozen) == digest
    assert not validate(scene, True)
