# Generate Amberpine Inn: a furnished timber inn and tower on a planted stone terrace.
# Bounding box XYZ: 59 x 59 x 54 blocks, inclusive coordinates (0, 0, 0)..(58, 58, 53).
# Reference interpretation: two-storey plaster-and-timber wings, charcoal shingle gables,
# a five-level tapering tower, deep porches, amber glazing, raised stone courtyard,
# broad frontal steps, spruce silhouettes, ivy, casks, outdoor tables and warm lanterns.
# Hidden rooms continue the same frame and roof system. Ground floor is an inn,
# upper wings contain guest chambers, tower levels hold pantry, maps, library and study.
from __future__ import annotations

import argparse
import random
import sys
from pathlib import Path
from typing import Dict, Iterable, Optional, Tuple

sys.path.insert(0, "/work/generator/MCIO")

from mcio.schematic import load_schematic # noqa: E402
from mcio.sketch import LitematicCanvas, Material, parse_blockstate # noqa: E402

CONFIG = {
    "seed": 20260923,
    "size_xyz": (59, 59, 54), # width X, height Y, depth Z
    "minecraft_version": "1.21.1",
    "data_version": 3955,
    "output": "output.litematic",
    "name": "Amberpine Inn",
    "author": "generator",
    "description": "Furnished timber inn, five-level shingled tower, lantern-lit stone terrace and conifer garden",
}

# Material families repeat dominant tones and reserve rougher variants for wear.
STONE = ['stone_bricks'] * 6 + ['cracked_stone_bricks', 'mossy_stone_bricks', 'cobblestone', 'andesite']
PAVING = ['stone_bricks'] * 5 + ['andesite'] * 3 + ['cobblestone', 'mossy_stone_bricks']
PLASTER = ['smooth_sandstone'] * 9 + ['sandstone'] * 2 + ['cut_sandstone']
WOOD = ['spruce_planks'] * 6 + ['oak_planks'] * 2 + ['dark_oak_planks']
ROOF = ['deepslate_tiles'] * 7 + ['deepslate_bricks'] * 2 + ['cobbled_deepslate']

Position = Tuple[int, int, int]


class Build:
    """Hold a block volume and write it out as a Litematica schematic."""

    def __init__(self, size_xyz: Tuple[int, int, int] = CONFIG["size_xyz"], seed: int = CONFIG["seed"]) -> None:
        """Create an empty volume with one seeded random source.

        Args:
            size_xyz (Tuple[int, int, int]): Volume size as width X, height Y, depth Z.
            seed (int): Seed for every random decision in this build.
        """
        self.size_x, self.size_y, self.size_z = size_xyz
        self.rng = random.Random(seed)
        self.canvas = LitematicCanvas((self.size_y, self.size_z, self.size_x))
        self.placed: Dict[Position, str] = {}

    def inside(self, x: int, y: int, z: int) -> bool:
        """Report whether a coordinate is inside the volume.

        Args:
            x (int): Width coordinate.
            y (int): Height coordinate.
            z (int): Depth coordinate.

        Returns:
            bool: True when every axis is within range.
        """
        return 0 <= x < self.size_x and 0 <= y < self.size_y and 0 <= z < self.size_z

    def put(self, x: int, y: int, z: int, block: str | Material, **properties: str) -> None:
        """Place one block, in XYZ order, with an immediate bounds check.

        Args:
            x (int): Width coordinate.
            y (int): Height coordinate.
            z (int): Depth coordinate.
            block (str | Material): Namespaced block name, a full blockstate string, or a Material.
            **properties: Blockstate properties such as facing or half.

        Raises:
            AssertionError: When the coordinate is outside the volume, naming the coordinate.
        """
        assert self.inside(x, y, z), f"out of bounds: (x={x}, y={y}, z={z}) in {self.size_x}x{self.size_y}x{self.size_z}"
        material = self.material(block, **properties) if isinstance(block, str) else block
        self.canvas.block((y, z, x), material)
        self.placed[(x, y, z)] = material.blockstate

    def material(self, name: str, **properties: str) -> Material:
        """Build a Material from a block name and its properties.

        Args:
            name (str): Namespaced block name, or a full blockstate string.
            **properties: Blockstate properties.

        Returns:
            Material: Material ready for placement.
        """
        if "[" in name:
            return parse_blockstate(name)
        return self.canvas.material(name, **dict(sorted(properties.items())))

    def box(self, bounds: Tuple[int, int, int, int, int, int], block: str | Material, **properties: str) -> None:
        """Fill an inclusive box.

        Args:
            bounds (Tuple[int, int, int, int, int, int]): x0, y0, z0, x1, y1, z1, inclusive on both ends.
            block (str | Material): Block to place.
            **properties: Blockstate properties.
        """
        x0, y0, z0, x1, y1, z1 = bounds
        for x in range(min(x0, x1), max(x0, x1) + 1):
            for y in range(min(y0, y1), max(y0, y1) + 1):
                for z in range(min(z0, z1), max(z0, z1) + 1):
                    self.put(x, y, z, block, **properties)

    def shell(self, bounds: Tuple[int, int, int, int, int, int], block: str | Material, **properties: str) -> None:
        """Fill only the faces of a box, leaving the inside empty.

        Args:
            bounds (Tuple[int, int, int, int, int, int]): x0, y0, z0, x1, y1, z1, inclusive.
            block (str | Material): Block to place.
            **properties: Blockstate properties.
        """
        x0, y0, z0, x1, y1, z1 = bounds
        for x in range(min(x0, x1), max(x0, x1) + 1):
            for y in range(min(y0, y1), max(y0, y1) + 1):
                for z in range(min(z0, z1), max(z0, z1) + 1):
                    on_face = x in (x0, x1) or y in (y0, y1) or z in (z0, z1)
                    if on_face:
                        self.put(x, y, z, block, **properties)

    def texture(self, bounds: Tuple[int, int, int, int, int, int], palette: Iterable[str]) -> None:
        """Fill a box with a weighted mix of related blocks, so the surface is not flat.

        Args:
            bounds (Tuple[int, int, int, int, int, int]): x0, y0, z0, x1, y1, z1, inclusive.
            palette (Iterable[str]): Block names. Repeat a name to make it more common.
        """
        choices = list(palette)
        x0, y0, z0, x1, y1, z1 = bounds
        for x in range(min(x0, x1), max(x0, x1) + 1):
            for y in range(min(y0, y1), max(y0, y1) + 1):
                for z in range(min(z0, z1), max(z0, z1) + 1):
                    self.put(x, y, z, self.rng.choice(choices))

    def get(self, x: int, y: int, z: int) -> Optional[str]:
        """Read back what was placed at a coordinate.

        Args:
            x (int): Width coordinate.
            y (int): Height coordinate.
            z (int): Depth coordinate.

        Returns:
            Optional[str]: Blockstate string, or None when the cell is empty.
        """
        return self.placed.get((x, y, z))

    def block(self, x, y, z, name, **props):
        """Place a vanilla block using a short name."""
        self.put(x, y, z, 'minecraft:' + name, **props)

    def fill(self, bounds, name, **props):
        """Fill a box using a short vanilla name."""
        self.box(bounds, 'minecraft:' + name, **props)

    def mix(self, bounds, names):
        """Texture a box from short vanilla names."""
        self.texture(bounds, ['minecraft:' + n for n in names])

    def air(self, bounds):
        """Carve a rectangular clearance."""
        self.fill(bounds, 'air')

    def log(self, bounds, axis='y'):
        """Place the exposed oak structural frame."""
        self.fill(bounds, 'stripped_spruce_log', axis=axis)

    def stair(self, x, y, z, facing, material='spruce', walking=False):
        """Place a stair, recording the treads used for circulation."""
        self.block(x, y, z, material + '_stairs', facing=facing, half='bottom', shape='straight', waterlogged='false')
        if walking:
            self.walking_stairs.append((x, y, z, facing))

    def lantern(self, x, y, z, hanging=False):
        """Place a supported warm light."""
        self.block(x, y, z, 'lantern', hanging=str(hanging).lower(), waterlogged='false')

    def door(self, x, y, z, facing='south', hinge='left'):
        """Place both matching halves of a closed spruce door."""
        for dy, half in enumerate(('lower', 'upper')):
            self.block(x, y + dy, z, 'spruce_door', facing=facing, half=half, hinge=hinge, open='false', powered='false')
        self.doors.append((x, y, z))

    def bed(self, x, y, z, facing='north', color='red'):
        """Place a complete bed on the floor."""
        dx, dz = {'north': (0, -1), 'south': (0, 1), 'east': (1, 0), 'west': (-1, 0)}[facing]
        for px, pz, part in ((x, z, 'foot'), (x + dx, z + dz, 'head')):
            self.block(px, y, pz, color + '_bed', facing=facing, part=part, occupied='false')

    def window(self, axis, fixed, lo, hi, y0, y1, exterior=1):
        """Set amber glazing, heavy mullions, projecting sills and wooden shutters."""
        panes = []
        for u in range(lo, hi + 1):
            for y in range(y0, y1 + 1):
                p = (u, y, fixed) if axis == 'z' else (fixed, y, u)
                self.block(*p, 'yellow_stained_glass')
                panes.append(p)
        self.windows.append(panes)
        # Full sill and lintel seal the opening and hold external shutters.
        for u in range(lo - 1, hi + 2):
            p = (u, y0 - 1, fixed) if axis == 'z' else (fixed, y0 - 1, u)
            self.block(*p, 'spruce_planks')
            p = (u, y1 + 1, fixed) if axis == 'z' else (fixed, y1 + 1, u)
            self.block(*p, 'stripped_spruce_log', axis='x' if axis == 'z' else 'z')
        for u in (lo - 1, hi + 1):
            for y in range(y0, y1 + 1):
                p = (u, y, fixed) if axis == 'z' else (fixed, y, u)
                self.block(*p, 'stripped_spruce_log', axis='y')
                p = (u, y, fixed + exterior) if axis == 'z' else (fixed + exterior, y, u)
                self.block(*p, 'spruce_trapdoor', facing=('south' if exterior > 0 else 'north') if axis == 'z' else ('east' if exterior > 0 else 'west'), half='bottom', open='true', powered='false', waterlogged='false')
        # Nearby interior lantern supplies the warm window glow.
        mid = (lo + hi) // 2
        p = (mid, y1, fixed - exterior) if axis == 'z' else (fixed - exterior, y1, mid)
        above = (p[0], p[1] + 1, p[2])
        self.block(*above, 'spruce_planks')
        self.lantern(*p, hanging=True)

    def room_box(self, x0, z0, x1, z1, floor, ceiling):
        """Make a floored and framed plaster volume, open at its roof."""
        self.mix((x0, floor, z0, x1, floor, z1), WOOD)
        for y in range(floor + 1, ceiling + 1):
            for x in range(x0, x1 + 1):
                for z in range(z0, z1 + 1):
                    if x in (x0, x1) or z in (z0, z1):
                        self.block(x, y, z, self.rng.choice(PLASTER))
        for x in (x0, x1):
            for z in (z0, z1):
                self.log((x, floor, z, x, ceiling, z))
        for y in (floor, ceiling):
            self.log((x0, y, z0, x1, y, z0), 'x')
            self.log((x0, y, z1, x1, y, z1), 'x')
            self.log((x0, y, z0, x0, y, z1), 'z')
            self.log((x1, y, z0, x1, y, z1), 'z')

    def occupied_tower(self, x, y, z):
        """Keep intersecting house roofs out of the tower's occupied envelope."""
        return (32 <= x <= 42 and 8 <= z <= 19 and y <= 26) or (33 <= x <= 41 and 9 <= z <= 18 and 27 <= y <= 33) or (34 <= x <= 40 and 10 <= z <= 17 and 34 <= y <= 39)

    def gable_roof(self, x0, z0, x1, z1, eave, axis='z', skip=None):
        """Build thick, stair-edged shingle slopes with stone ridge caps."""
        lo, hi = (x0, x1) if axis == 'z' else (z0, z1)
        for x in range(x0, x1 + 1):
            for z in range(z0, z1 + 1):
                u = x if axis == 'z' else z
                d = min(u - lo, hi - u)
                y = eave + d
                if skip and skip(x, y, z):
                    continue
                self.block(x, y - 1, z, self.rng.choice(ROOF))
                if u in ((lo + hi) // 2, (lo + hi + 1) // 2):
                    self.block(x, y, z, self.rng.choice(ROOF))
                    self.block(x, y + 1, z, 'deepslate_tile_slab', type='bottom', waterlogged='false')
                else:
                    facing = ('east' if u < (lo + hi) / 2 else 'west') if axis == 'z' else ('south' if u < (lo + hi) / 2 else 'north')
                    edge = z in (z0, z1) if axis == 'z' else x in (x0, x1)
                    material = 'cobbled_deepslate' if edge else self.rng.choice(['deepslate_tile'] * 5 + ['deepslate_brick'])
                    self.stair(x, y, z, facing, material)
        # Carved finials at the two ends of each ridge.
        mid = (lo + hi) // 2
        peak = eave + (hi - lo) // 2
        for end in (z0, z1) if axis == 'z' else (x0, x1):
            x, z = (mid, end) if axis == 'z' else (end, mid)
            if skip and skip(x, peak + 1, z):
                continue
            self.block(x, peak + 1, z, 'cobbled_deepslate_wall', up='true', north='none', east='none', south='none', west='none', waterlogged='false')
            self.block(x, peak + 2, z, 'deepslate_tile_slab', type='bottom', waterlogged='false')

    def hip_apron(self, x0, z0, x1, z1, y0, steps):
        """Add the flared, tiered skirt of the tower roof."""
        for step in range(steps):
            a, b, c, d = x0 + step, z0 + step, x1 - step, z1 - step
            y = y0 + step
            for x in range(a, c + 1):
                for z in range(b, d + 1):
                    if x not in (a, c) and z not in (b, d):
                        continue
                    self.block(x, y - 1, z, self.rng.choice(ROOF))
                    facing = 'east' if x == a else 'west' if x == c else 'south' if z == b else 'north'
                    self.stair(x, y, z, facing, 'deepslate_tile')

    def table(self, x, y, z, long=1):
        """Build a solid-legged dining table and supported tableware."""
        for dx in range(long):
            self.block(x + dx, y, z, 'spruce_planks')
            self.block(x + dx, y + 1, z, 'brown_carpet')
        self.block(x, y + 1, z, 'potted_fern')

    def planter(self, x, y, z, flower='fern'):
        """Make a timber planter with greenery rooted in soil."""
        self.block(x, y, z, 'rooted_dirt')
        self.block(x, y + 1, z, flower)
        for dx, dz, face in ((0, 1, 'south'), (0, -1, 'north'), (1, 0, 'east'), (-1, 0, 'west')):
            if self.get(x + dx, y, z + dz) in (None, 'minecraft:air'):
                self.block(x + dx, y, z + dz, 'spruce_trapdoor', facing=face, half='bottom', open='true', powered='false', waterlogged='false')

    def terrain(self):
        """Create an irregular grassy island, stone terrace and five-step approach."""
        for x in range(1, 58):
            for z in range(2, 53):
                corner = (max(5 - x, 0) + max(x - 53, 0) + max(7 - z, 0) + max(z - 47, 0))
                if corner > 5 + self.rng.randrange(2):
                    continue
                self.mix((x, 0, z, x, 0, z), ['dirt', 'coarse_dirt', 'stone'])
                self.block(x, 1, z, self.rng.choice(['grass_block'] * 7 + ['moss_block', 'coarse_dirt']))
                if 4 <= x <= 54 and 6 <= z <= 45 and not ((x < 7 or x > 51) and (z < 9 or z > 42)):
                    self.fill((x, 2, z, x, 5, z), 'stone')
                    self.block(x, 6, z, self.rng.choice(PAVING))
        # Retaining faces use weathered blockwork and deep buttresses.
        for x in range(4, 55):
            for z in range(6, 46):
                if self.get(x, 6, z) is None:
                    continue
                if any(self.get(x + dx, 6, z + dz) is None for dx, dz in ((1, 0), (-1, 0), (0, 1), (0, -1))):
                    self.mix((x, 2, z, x, 5, z), STONE)
                    self.block(x, 6, z, 'stone_bricks')
        for x in (7, 14, 21, 36, 43, 51):
            self.mix((x, 2, 45, x + 1, 7, 45), STONE)
            self.fill((x, 8, 45, x + 1, 8, 45), 'stone_brick_slab', type='bottom', waterlogged='false')
        for i in range(1, 6):
            y, z = 1 + i, 51 - i
            self.mix((26, 1, z, 32, y - 1, z), STONE)
            for x in range(26, 33):
                self.stair(x, y, z, 'north', 'stone_brick', walking=True)
        for x in range(24, 36):
            for z in range(49, 53):
                if not (26 <= x <= 32 and z <= 50):
                    self.block(x, 1, z, self.rng.choice(PAVING))
        # Coping and broken parapet leave the stair mouth open.
        for x0, x1 in ((8, 24), (34, 50)):
            for x in range(x0, x1 + 1):
                self.block(x, 7, 44, 'stone_brick_slab', type='bottom', waterlogged='false')
        for x in (25, 33):
            for z in range(46, 51):
                h = 52 - z
                self.mix((x, 1, z, x, h, z), STONE)
                self.block(x, h + 1, z, 'stone_brick_slab', type='bottom', waterlogged='false')
        for x in (24, 34):
            self.mix((x, 7, 43, x, 8, 43), STONE)
            self.lantern(x, 9, 43)

    def architecture(self):
        """Construct the two-storey wings and five connected tower levels."""
        # Solid stone plinth joins every bearing wall to the terrain.
        for x0, z0, x1, z1 in ((8, 12, 23, 29), (23, 16, 41, 29), (32, 8, 42, 19), (40, 25, 50, 36)):
            self.mix((x0, 2, z0, x1, 6, z1), STONE)
        self.room_box(8, 12, 23, 29, 6, 12)
        self.room_box(8, 12, 23, 29, 13, 20)
        self.room_box(23, 16, 41, 29, 6, 12)
        self.room_box(23, 16, 41, 29, 13, 15)
        # Plaster end walls of the cross roof follow its triangular section.
        for x in (23, 41):
            for z in range(16, 30):
                h = 22 - abs(z - 22)
                self.mix((x, 14, z, x, h, z), PLASTER)
        self.room_box(40, 25, 50, 36, 6, 12)
        self.room_box(40, 25, 50, 36, 13, 18)
        for f in (6, 13, 20):
            self.room_box(32, 8, 42, 19, f, f + 6)
        self.room_box(33, 9, 41, 18, 27, 33)
        self.room_box(34, 10, 40, 17, 34, 39)
        self.fill((34, 39, 10, 40, 39, 17), 'spruce_planks')
        # Exposed vertical and horizontal timbers, golden plaster infill.
        for x in (8, 13, 18, 23):
            self.log((x, 7, 29, x, 20, 29))
        for z in (12, 17, 23, 29):
            self.log((8, 7, z, 8, 20, z))
            self.log((23, 7, z, 23, 20, z))
        for x in (23, 27, 32, 37, 41):
            self.log((x, 7, 29, x, 15, 29))
        for z in (25, 30, 36):
            self.log((50, 7, z, 50, 18, z))
        for x in (40, 45, 50):
            self.log((x, 7, 36, x, 18, 36))
        for f in (6, 13, 20):
            self.log((37, f + 1, 19, 37, f + 6, 19))
            self.log((42, f + 1, 13, 42, f + 6, 13))
            self.log((37, f + 1, 8, 37, f + 6, 8))
        # Closed gable ends under both longitudinal roofs.
        for x in range(8, 24):
            roof_y = 20 + min(x - 6, 25 - x) - 1
            for z in (12, 29):
                self.mix((x, 21, z, x, roof_y, z), PLASTER)
                if x in (11, 15, 19, 22):
                    self.log((x, 20, z, x, roof_y, z))
        for x in range(40, 51):
            roof_y = 18 + min(x - 38, 52 - x) - 1
            for z in (25, 36):
                self.mix((x, 19, z, x, roof_y, z), PLASTER)
                if x in (42, 45, 48):
                    self.log((x, 18, z, x, roof_y, z))
        self.gable_roof(6, 10, 25, 31, 20, 'z', self.occupied_tower)

        def cross_skip(x, y, z):
            return self.occupied_tower(x, y, z) or (x <= 23 and 12 <= z <= 29 and y <= 20) or (40 <= x <= 50 and 25 <= z <= 36 and y <= 18)

        self.gable_roof(22, 13, 43, 31, 14, 'x', cross_skip)
        self.gable_roof(38, 23, 52, 38, 18, 'z', self.occupied_tower)
        # Tower's two flaring roof skirts and tall tapering cap.
        self.hip_apron(30, 6, 44, 21, 27, 4)
        self.hip_apron(32, 8, 42, 19, 34, 3)
        for y in range(40, 52):
            inset = (y - 40) // 2
            x0, x1, z0, z1 = 32 + inset, 42 - inset, 8 + inset, 19 - inset
            self.mix((x0, y, z0, x1, y, z1), ROOF)
            if y % 2 == 0 and x0 < x1:
                for x in range(x0, x1 + 1):
                    self.stair(x, y, z0, 'south', 'deepslate_tile')
                    self.stair(x, y, z1, 'north', 'deepslate_tile')
                for z in range(z0 + 1, z1):
                    self.stair(x0, y, z, 'east', 'deepslate_tile')
                    self.stair(x1, y, z, 'west', 'deepslate_tile')
        self.log((35, 52, 13, 39, 52, 13), 'x')
        for x, height in ((35, 54), (37, 56), (39, 54)):
            self.fill((x, 53, 13, x, height - 1, 13), 'spruce_fence', north='false', east='false', south='false', west='false', waterlogged='false')
            self.block(x, height, 13, 'lightning_rod', facing='up', powered='false', waterlogged='false')
        # Useful dormer bays continue the rooms into the front roof planes.
        self.room_box(27, 27, 33, 31, 13, 19)
        self.air((28, 14, 27, 32, 18, 30))
        self.gable_roof(26, 26, 34, 32, 20, 'z')
        for x in range(27, 34):
            h = 19 + min(x - 26, 34 - x)
            self.mix((x, 20, 31, x, h, 31), PLASTER)
            self.mix((x, 20, 27, x, h, 27), PLASTER)
        self.window('z', 31, 29, 31, 16, 18)
        self.log((30, 20, 31, 30, 23, 31))
        self.room_box(35, 17, 39, 21, 27, 32)
        self.air((36, 28, 17, 38, 31, 20))
        self.gable_roof(34, 16, 40, 22, 33, 'z')
        for x in range(35, 40):
            self.mix((x, 33, 21, x, 32 + min(x - 34, 40 - x), 21), PLASTER)
            self.mix((x, 33, 17, x, 32 + min(x - 34, 40 - x), 17), PLASTER)
        self.window('z', 21, 36, 38, 29, 31)
        # Small lookout gable in the upper shingle tier.
        self.room_box(35, 16, 39, 19, 34, 38)
        self.air((36, 35, 16, 38, 37, 18))
        self.gable_roof(34, 15, 40, 20, 39, 'z')
        for x in range(35, 40):
            self.mix((x, 39, 19, x, 38 + min(x - 34, 40 - x), 19), PLASTER)
            self.mix((x, 39, 16, x, 38 + min(x - 34, 40 - x), 16), PLASTER)
        self.window('z', 19, 36, 38, 35, 37)
        # Glazed openings on all reconstructed elevations.
        for lo, hi in ((10, 11), (19, 21)):
            self.window('z', 29, lo, hi, 8, 10)
            self.window('z', 29, lo, hi, 15, 17)
        self.window('z', 29, 14, 17, 22, 24)
        for lo, hi in ((14, 16), (24, 26)):
            self.window('x', 8, lo, hi, 8, 10, -1)
            self.window('x', 8, lo, hi, 15, 17, -1)
        for lo, hi in ((10, 12), (18, 21)):
            self.window('z', 12, lo, hi, 8, 10, -1)
            self.window('z', 12, lo, hi, 15, 17, -1)
        self.window('z', 29, 34, 36, 8, 10)
        self.window('z', 16, 26, 29, 8, 10, -1)
        self.window('z', 36, 46, 48, 8, 10)
        self.window('z', 36, 43, 47, 14, 16)
        self.window('z', 36, 43, 47, 20, 22)
        for lo, hi in ((27, 28), (32, 34)):
            self.window('x', 50, lo, hi, 8, 10)
            self.window('x', 50, lo, hi, 14, 16)
        for lo, hi in ((33, 35), (39, 41)):
            self.window('z', 19, lo, hi, 22, 25)
        for f in (6, 13, 20):
            self.window('z', 8, 34, 36, f + 2, f + 4, -1)
            self.window('x', 42, 10, 12, f + 2, f + 4)
            self.window('x', 32, 10, 12, f + 2, f + 4, -1)
        self.window('x', 41, 12, 15, 29, 31)
        self.window('z', 9, 35, 37, 29, 31, -1)
        self.window('z', 10, 35, 37, 35, 37, -1)
        # Tower doors and arches are cut after intersecting masses are finished.
        for floor in (6, 13):
            self.air((22, floor + 1, 23, 24, floor + 3, 25))
            self.air((33, floor + 1, 15, 35, floor + 3, 20))
            self.air((39, floor + 1, 26, 41, floor + 2, 28))
        for x in (28, 29):
            self.door(x, 7, 29, hinge='left' if x == 28 else 'right')
        for x in (15, 16):
            self.door(x, 7, 29, hinge='left' if x == 15 else 'right')
        self.door(43, 7, 36)
        # Interior guest-room partition, with a complete door.
        self.fill((9, 14, 20, 22, 19, 20), 'spruce_planks')
        self.log((9, 19, 20, 22, 19, 20), 'x')
        self.door(16, 14, 20, 'north')
        # Main staircase: seven supported treads, three clear blocks above each.
        for i in range(1, 8):
            y, z = 6 + i, 27 - i
            self.fill((25, 6, z, 26, y - 1, z), 'spruce_planks')
            self.air((25, y + 1, z, 26, y + 3, z))
            for x in (25, 26):
                self.stair(x, y, z, 'north', walking=True)
        self.fill((25, 13, 18, 26, 13, 19), 'spruce_planks')
        self.air((25, 14, 18, 26, 16, 19))
        # A full-height supported ladder serves the tower's five levels.
        self.log((38, 7, 10, 38, 37, 10))
        for y in range(7, 36):
            self.block(38, y, 11, 'ladder', facing='south', waterlogged='false')
            self.block(38, y, 12, 'air')
        self.air((38, 36, 11, 38, 37, 12))
        # The roof void is part of the upper room rather than an unserved floor.
        self.rooms = [
            ('Taproom', (17, 7, 24), (9, 7, 21, 22, 12, 28)),
            ('Kitchen and larder', (16, 7, 16), (9, 7, 13, 22, 12, 19)),
            ('Entrance hall', (30, 7, 26), (27, 7, 21, 38, 12, 28)),
            ('Ground tower pantry', (36, 7, 14), (33, 7, 9, 41, 12, 18)),
            ('Garden parlour', (45, 7, 32), (41, 7, 26, 49, 12, 35)),
            ('South guest chamber', (16, 14, 24), (9, 14, 21, 22, 19, 28)),
            ('North guest chamber', (16, 14, 17), (9, 14, 13, 22, 19, 19)),
            ('Upper gallery', (30, 14, 24), (27, 14, 20, 39, 17, 27)),
            ('Tower map room', (36, 14, 14), (33, 14, 9, 41, 19, 18)),
            ('Garden guest room', (45, 14, 30), (41, 14, 26, 49, 17, 35)),
            ('Tower library', (36, 21, 14), (33, 21, 9, 41, 26, 18)),
            ('Tower study', (36, 28, 14), (34, 28, 10, 40, 33, 17)),
            ('Lookout chamber', (36, 35, 14), (35, 35, 11, 39, 38, 16)),
        ]

    def furnishings(self):
        """Fit the inn with dining, cooking, sleeping, storage and study furniture."""
        # Taproom: two long tables, chairs, casks and bar service.
        for x, z in ((12, 24), (19, 24)):
            self.table(x, 7, z, 2)
            for xx in (x, x + 1):
                self.stair(xx, 7, z - 1, 'south', 'oak')
                self.stair(xx, 7, z + 1, 'north', 'oak')
        for x in range(10, 15):
            self.block(x, 7, 19, 'barrel', facing='south', open='false')
            self.block(x, 8, 19, 'spruce_slab', type='top', waterlogged='false')
        self.block(10, 9, 19, 'potted_brown_mushroom')
        self.lantern(14, 9, 19)
        for z in (21, 27):
            self.block(21, 7, z, 'barrel', facing='up', open='false')
        # Kitchen: masonry hearth, cooking appliances and a preparation bench.
        self.mix((18, 7, 13, 21, 10, 14), STONE)
        for x, name in ((18, 'furnace'), (19, 'smoker'), (20, 'blast_furnace')):
            self.block(x, 7, 15, name, facing='south', lit='true')
        self.block(21, 7, 15, 'cauldron')
        self.fill((18, 10, 15, 21, 10, 15), 'stone_brick_slab', type='top', waterlogged='false')
        for x in range(10, 14):
            self.block(x, 7, 14, 'barrel', facing='south', open='false')
            self.block(x, 8, 14, 'spruce_planks')
        self.block(10, 9, 14, 'potted_red_mushroom')
        self.block(13, 9, 14, 'lantern', hanging='false', waterlogged='false')
        self.block(14, 7, 14, 'crafting_table')
        # Sturdy stone chimney rising beside the west ridge.
        self.mix((20, 11, 14, 21, 28, 15), STONE)
        self.fill((19, 29, 13, 22, 29, 16), 'stone_brick_slab', type='top', waterlogged='false')
        for x in (20, 21):
            self.block(x, 30, 14, 'campfire', facing='north', lit='true', signal_fire='false', waterlogged='false')
        self.fill((19, 31, 13, 22, 31, 16), 'cobbled_deepslate_slab', type='bottom', waterlogged='false')
        # Hall reception and broad bench, with a central runner.
        for z in range(22, 28):
            self.block(30, 7, z, 'red_carpet')
        self.block(37, 7, 24, 'barrel', facing='west', open='false')
        self.block(37, 8, 24, 'lectern', facing='west', has_book='false', powered='false')
        for x in range(33, 37):
            self.stair(x, 7, 27, 'north', 'oak')
        self.block(38, 7, 27, 'spruce_planks')
        self.lantern(38, 8, 27)
        # Pantry and map room sit in the base of the tower.
        for floor in (6, 13):
            for z in (13, 16):
                self.block(33, floor + 1, z, 'barrel', facing='east', open='false')
                self.block(33, floor + 2, z, 'barrel', facing='east', open='false')
            self.block(40, floor + 1, 16, 'spruce_planks')
            self.lantern(40, floor + 2, 16)
        self.block(35, 14, 10, 'cartography_table')
        self.block(35, 14, 15, 'spruce_stairs', facing='east', half='bottom', shape='straight', waterlogged='false')
        self.table(40, 14, 14)
        # Parlour around a compact fireplace with seating and flowered side table.
        self.fill((47, 7, 26, 49, 10, 26), 'stone_bricks')
        self.block(48, 7, 27, 'smoker', facing='south', lit='true')
        for z in (31, 32, 33):
            self.stair(42, 7, z, 'east', 'oak')
        self.table(47, 7, 32)
        self.block(48, 7, 34, 'barrel', facing='up', open='false')
        self.lantern(48, 8, 34)
        # Three complete guest suites, each with beds, storage and working desk.
        for x, z, color in ((10, 23, 'red'), (20, 23, 'orange'), (10, 16, 'green'), (18, 16, 'green')):
            self.bed(x, 14, z, 'north', color)
            self.block(x + 1, 14, z - 1, 'barrel', facing='up', open='false')
            self.lantern(x + 1, 15, z - 1)
        for x, z in ((11, 27), (12, 18)):
            self.table(x, 14, z, 2)
            self.stair(x + 1, 14, z - 1, 'south', 'oak')
        for x, z in ((21, 27), (21, 18)):
            self.block(x, 14, z, 'chest', facing='west', type='single', waterlogged='false')
        self.bed(47, 14, 33, 'north', 'yellow')
        self.bed(48, 14, 33, 'north', 'yellow')
        self.block(49, 14, 32, 'barrel', facing='up', open='false')
        self.lantern(49, 15, 32)
        self.table(43, 14, 34)
        self.stair(43, 14, 33, 'south', 'oak')
        self.block(42, 14, 30, 'chest', facing='east', type='single', waterlogged='false')
        # Upstairs gallery has a reading alcove beneath its glazed dormer.
        self.fill((34, 14, 24, 34, 15, 26), 'bookshelf')
        self.stair(32, 14, 24, 'west', 'oak')
        self.block(32, 14, 26, 'spruce_planks')
        self.lantern(32, 15, 26)
        self.block(29, 14, 30, 'spruce_planks')
        self.block(29, 15, 30, 'potted_fern')
        # Tower library and study contain desks, shelves and a writing station.
        for floor in (20, 27):
            xwall = 33 if floor == 20 else 34
            for z in (13, 14, 15, 16):
                self.fill((xwall, floor + 1, z, xwall, floor + 3, z), 'bookshelf')
            self.block(39, floor + 1, 15, 'spruce_planks')
            self.lantern(39, floor + 2, 15)
            self.block(37, floor + 1, 16, 'lectern', facing='north', has_book='false', powered='false')
            self.stair(36, floor + 1, 16, 'east', 'oak')
            self.block(35, floor + 1, 10 if floor == 20 else 11, 'barrel', facing='up', open='false')
        self.block(36, 28, 20, 'cartography_table')
        self.block(38, 28, 20, 'potted_dead_bush')
        self.block(38, 27, 20, 'spruce_planks')
        # Lookout is a tiny furnished sleeping and observation room.
        self.bed(35, 35, 15, 'north', 'orange')
        self.block(39, 35, 15, 'barrel', facing='up', open='false')
        self.lantern(39, 36, 15)
        self.block(37, 35, 16, 'cartography_table')
        # Ceiling lanterns are anchored to solid crossbeams and near each floor.
        for x, floor, z in ((16, 6, 26), (16, 6, 16), (30, 6, 23), (45, 6, 30), (16, 13, 25), (16, 13, 16), (45, 13, 29)):
            self.block(x, floor + 5, z, 'spruce_planks')
            self.block(x, floor + 4, z, 'chain', axis='y', waterlogged='false')
            self.lantern(x, floor + 3, z, hanging=True)

    def exterior(self):
        """Add porches, courtyard furniture, fencing, ivy and conifers."""
        # Deep west porch, with a shallow shingled shed canopy.
        for z in range(30, 36):
            y = 15 - (z - 29) // 2
            for x in range(6, 26):
                self.block(x, y - 1, z, self.rng.choice(ROOF))
                self.stair(x, y, z, 'north', 'deepslate_tile')
        self.log((6, 11, 35, 25, 11, 35), 'x')
        for x in (7, 13, 20, 25):
            self.mix((x, 7, 34, x, 7, 34), STONE)
            self.log((x, 8, 34, x, 11, 34))
            self.stair(x, 11, 33, 'south', 'spruce')
            if x != 13:
                self.block(x, 10, 33, 'spruce_fence', north='false', east='false', south='false', west='false', waterlogged='false')
        for x in (10, 17, 23):
            self.block(x, 11, 34, 'chain', axis='y', waterlogged='false')
            self.lantern(x, 10, 34, hanging=True)
        # Entrance canopy and brackets.
        self.log((27, 10, 30, 31, 10, 30), 'x')
        self.lantern(27, 9, 30, hanging=True)
        self.lantern(31, 9, 30, hanging=True)
        for x in (27, 31):
            self.block(x, 7, 30, 'barrel', facing='up', open='false')
        # Right-hand porch, supported posts and glowing front window.
        for x in (41, 49):
            self.log((x, 7, 38, x, 11, 38))
        self.log((41, 11, 38, 49, 11, 38), 'x')
        for x in range(40, 51):
            self.fill((x, 12, 37, x, 12, 38), 'spruce_planks')
            self.stair(x, 12, 39, 'north', 'deepslate_tile')
        self.lantern(42, 10, 38, hanging=True)
        self.lantern(48, 10, 38, hanging=True)
        # Market goods around the sheltered taproom frontage.
        for x, z, h in ((9, 32, 1), (10, 32, 2), (11, 32, 1), (21, 32, 1), (22, 32, 2), (23, 32, 1), (8, 37, 1), (10, 39, 1), (18, 33, 1), (35, 31, 1), (36, 32, 1), (39, 31, 2)):
            for dy in range(h):
                self.block(x, 7 + dy, z, 'barrel' if (x + z + dy) % 3 else 'stripped_oak_log', **({'facing': 'up', 'open': 'false'} if (x + z + dy) % 3 else {'axis': 'y'}))
        for x, z in ((15, 38), (38, 39), (46, 41)):
            self.table(x, 7, z, 2)
            for xx in (x, x + 1):
                self.stair(xx, 7, z - 1, 'south', 'oak')
                self.stair(xx, 7, z + 1, 'north', 'oak')
        # Low fences divide planted terrace edges from the open approach.
        for x0, x1, z in ((8, 21, 42), (36, 51, 43), (41, 51, 40)):
            for x in range(x0, x1 + 1):
                if z == 40 and 44 <= x <= 48:
                    continue
                self.block(x, 7, z, 'spruce_fence', east=str(x < x1).lower(), west=str(x > x0).lower(), north='false', south='false', waterlogged='false')
        for x, z in ((8, 42), (21, 42), (36, 43), (51, 43)):
            self.log((x, 7, z, x, 8, z))
            self.lantern(x, 9, z)
        # Hanging inn sign, a stylized copper sunrise on a framed ochre panel.
        self.log((25, 12, 33, 28, 12, 33), 'x')
        self.block(28, 11, 33, 'chain', axis='y', waterlogged='false')
        self.block(28, 10, 33, 'stripped_oak_log', axis='z')
        self.block(28, 10, 34, 'copper_trapdoor', facing='south', half='bottom', open='true', powered='false', waterlogged='false')
        # Dense garden beds and established shrubs around the stone terrace.
        for x, z, plant in ((6, 39, 'fern'), (8, 40, 'azalea'), (21, 40, 'flowering_azalea'), (35, 41, 'fern'), (39, 42, 'azalea'), (51, 37, 'flowering_azalea'), (52, 32, 'fern'), (6, 26, 'azalea'), (7, 10, 'fern'), (45, 21, 'flowering_azalea')):
            self.planter(x, 7, z, plant)
        for x, z in ((5, 38), (6, 42), (19, 44), (22, 44), (35, 44), (39, 44), (50, 41), (53, 35), (52, 23)):
            for dx, dz in ((0, 0), (1, 0), (0, 1)):
                if self.get(x + dx, 6, z + dz) is not None:
                    self.block(x + dx, 7, z + dz, 'moss_block')
                    self.block(x + dx, 8, z + dz, 'flowering_azalea_leaves' if (x + dz) % 3 == 0 else 'oak_leaves', persistent='true', distance='1', waterlogged='false')
        # Ivy clings to the rear/east wall and selected porch uprights.
        for x, z, ymin, ymax in ((51, 30, 7, 17), (51, 35, 7, 14), (24, 28, 9, 18), (43, 16, 7, 24), (7, 18, 7, 17)):
            for y in range(ymin, ymax + 1):
                if self.get(x, y, z) not in (None, 'minecraft:air'):
                    continue
                backing = self.get(x - 1, y, z)
                face = 'west'
                if backing in (None, 'minecraft:air'):
                    backing = self.get(x + 1, y, z)
                    face = 'east'
                if backing not in (None, 'minecraft:air'):
                    props = {f: 'false' for f in ('north', 'east', 'south', 'west', 'up')}
                    props[face] = 'true'
                    self.block(x, y, z, 'vine', **props)
        # Forest silhouettes flank the tower without swallowing the facades.
        for x, z, base, height in ((5, 9, 2, 34), (48, 8, 2, 35), (54, 17, 2, 25), (3, 22, 2, 19)):
            self.conifer(x, z, base, height)
        # Ferns, blossoms, exposed stones and path fragments on the lowest ground.
        flowers = ['fern'] * 6 + ['short_grass'] * 5 + ['dandelion', 'poppy', 'azure_bluet', 'oxeye_daisy']
        for x in range(2, 58):
            for z in range(3, 53):
                if self.get(x, 1, z) not in ('minecraft:grass_block', 'minecraft:moss_block') or self.get(x, 2, z) not in (None, 'minecraft:air'):
                    continue
                if self.rng.random() < 0.35:
                    self.block(x, 2, z, self.rng.choice(flowers))
                elif self.rng.random() < 0.12:
                    self.block(x, 2, z, self.rng.choice(['mossy_cobblestone', 'cobblestone', 'andesite']))
        # Patchy courtyard wear; clear approach remains legible and continuous.
        for x in range(7, 53):
            for z in range(35, 45):
                if (26 <= x <= 33) or self.get(x, 7, z) not in (None, 'minecraft:air'):
                    continue
                if self.rng.random() < 0.06:
                    self.block(x, 6, z, 'moss_block')
                    self.block(x, 7, z, 'short_grass')

    def conifer(self, x, z, base, height):
        """Grow a rooted, layered spruce with an irregular tapering crown."""
        self.fill((x, 1, z, x, base + height, z), 'spruce_log', axis='y')
        for dy in range(5, height + 3):
            radius = max(1 if dy <= height else 0, round(5 * (height - dy) / height))
            if dy % 4 == 1:
                radius = max(1 if dy <= height else 0, radius - 1)
            if dy % 4 == 2:
                radius = max(1 if dy <= height else 0, radius - 2)
            for dx in range(-radius, radius + 1):
                for dz in range(-radius, radius + 1):
                    if dx == dz == 0 and dy <= height - 3:
                        continue
                    if abs(dx) + abs(dz) > radius + (1 if dy % 4 == 0 else 0):
                        continue
                    if not self.inside(x + dx, base + dy, z + dz):
                        continue
                    old = self.get(x + dx, base + dy, z + dz)
                    if old not in (None, 'minecraft:air') and not (dx == dz == 0 and dy > height - 3):
                        continue
                    if radius > 1 and abs(dx) + abs(dz) == radius + 1 and self.rng.random() < 0.3:
                        continue
                    self.block(x + dx, base + dy, z + dz, 'spruce_leaves', persistent='true', distance='1', waterlogged='false')

    def finish_details(self):
        """Finish bearing brackets, planted edges, roof wear and inhabited room details."""
        # Complete bearing timber below decorative eave stairs.
        for x in (7, 13, 20, 25):
            self.block(x, 10, 33, 'spruce_planks')
        self.log((40, 11, 39, 50, 11, 39), 'x')
        for x in range(32, 43):
            for z in range(8, 20):
                if (x in (32, 42) or z in (8, 19)) and self.get(x, 39, z) in (None, 'minecraft:air'):
                    self.block(x, 39, z, self.rng.choice(ROOF))
        self.fill((34, 28, 17, 34, 30, 17), 'bookshelf')
        # Horizontal brace ends break the heavy frame into carpentry-sized details.
        for x, y, z, facing in ((8, 13, 30, 'south'), (13, 13, 30, 'south'), (18, 13, 30, 'south'), (23, 13, 30, 'south'), (40, 13, 37, 'south'), (45, 13, 37, 'south'), (50, 13, 37, 'south'), (43, 20, 13, 'east'), (43, 20, 18, 'east')):
            self.block(x, y, z, 'oak_button', face='wall', facing=facing, powered='false')
        # Golden oak corbels sit below projecting upper beams at the visible gables.
        for x in (8, 13, 18, 23):
            self.block(x, 12, 30, 'spruce_planks')
            self.stair(x, 13, 30, 'north', 'oak')
        for x in (40, 45, 50):
            self.block(x, 12, 37, 'spruce_planks')
            self.stair(x, 13, 37, 'north', 'oak')
        # A few flat shingles interrupt perfect roof rows, like patched slate.
        for x, z in ((8, 18), (11, 25), (20, 19), (22, 25)):
            y = 20 + min(x - 6, 25 - x)
            if self.get(x, y, z) and 'stairs' in self.get(x, y, z):
                self.block(x, y, z, 'deepslate_bricks')
                self.block(x, y + 1, z, 'deepslate_brick_slab', type='bottom', waterlogged='false')
        # Ivy runs up porch posts and over low retaining stones.
        for x in (7, 20):
            for y in range(8, 11):
                self.block(x, y, 35, 'vine', north='true', east='false', south='false', west='false', up='false')
        for x in (10, 11, 18, 37, 38, 47, 48):
            for y in range(3, 6):
                if self.get(x, y, 45) not in (None, 'minecraft:air'):
                    self.block(x, y, 46, 'vine', north='true', east='false', south='false', west='false', up='false')
        # Shrub clusters at ground level soften the square masonry edge.
        for x, z in ((8, 48), (17, 47), (22, 49), (37, 48), (43, 48), (52, 45), (55, 33)):
            self.block(x, 1, z, 'rooted_dirt')
            self.block(x, 2, z, 'oak_log', axis='y')
            for dx, dy, dz in ((0, 1, 0), (1, 0, 0), (-1, 0, 0), (0, 0, 1), (0, 0, -1)):
                self.block(x + dx, 2 + dy, z + dz, 'flowering_azalea_leaves' if (x + dx) % 3 == 0 else 'oak_leaves', persistent='true', distance='1', waterlogged='false')
        # Small kitchen herbs, a drinks shelf and guest rugs make the rooms inhabited.
        self.fill((10, 10, 18, 14, 10, 18), 'spruce_planks')
        for x in (10, 12, 14):
            self.block(x, 11, 18, 'potted_fern' if x == 12 else 'potted_brown_mushroom')
        for x0, x1, z0, z1, floor, color in ((14, 18, 22, 26, 13, 'red'), (14, 16, 15, 18, 13, 'green'), (44, 46, 29, 31, 13, 'yellow'), (44, 46, 30, 33, 6, 'orange')):
            for x in range(x0, x1 + 1):
                for z in range(z0, z1 + 1):
                    if self.get(x, floor + 1, z) in (None, 'minecraft:air'):
                        self.block(x, floor + 1, z, color + '_carpet')
        # Crossbeams carry the hanging lights all the way into bearing walls.
        for y, z in ((11, 26), (11, 16), (18, 25), (18, 16)):
            self.log((9, y, z, 22, y, z), 'x')
        self.log((28, 11, 23, 40, 11, 23), 'x')
        for y, z in ((11, 30), (18, 29)):
            self.log((41, y, z, 49, y, z), 'x')
        self.block(9, 10, 18, 'spruce_planks')
        self.block(37, 43, 17, 'deepslate_tiles')
        # Additional casks and a low woodpile match the dense porch props.
        for x in range(9, 12):
            self.block(x, 7, 35, 'oak_log', axis='z')
        self.block(10, 8, 35, 'oak_log', axis='z')
        for x, z in ((18, 31), (34, 32), (39, 35)):
            self.block(x, 7, z, 'barrel', facing='up', open='false')

    def build(self) -> 'Build':
        """Build the Amberpine Inn and its complete terraced landscape."""
        self.windows = []
        self.doors = []
        self.walking_stairs = []
        self.terrain()
        self.architecture()
        self.furnishings()
        self.exterior()
        self.finish_details()
        return self

    def export(self, path: Path) -> Path:
        """Write the schematic so that two runs produce identical bytes.

        Args:
            path (Path): Destination .litematic path.

        Returns:
            Path: The path written.
        """
        schematic = self.canvas.to_litematica(
            name=CONFIG["name"],
            author=CONFIG["author"],
            description=CONFIG["description"],
            minecraft_data_version=CONFIG["data_version"],
        )
        # The metadata otherwise records the current wall clock, which changes the bytes on every run.
        schematic.metadata.time_created = 0
        schematic.metadata.time_modified = 0
        path.parent.mkdir(parents=True, exist_ok=True)
        schematic.save(path)
        return path

    def verify(self, path: Path) -> Dict[str, object]:
        """Reload the written file and confirm it matches what was placed.

        Args:
            path (Path): Schematic to reload.

        Returns:
            Dict[str, object]: Size, block count and palette size of the reloaded file.

        Raises:
            AssertionError: When a reloaded cell differs from what was placed.
        """
        loaded = load_schematic(path)
        size_y, size_z, size_x = loaded.size_yzx
        assert (size_x, size_y, size_z) == (self.size_x, self.size_y, self.size_z), "size changed on reload"
        ids = loaded.read_flat(0, loaded.volume).reshape(loaded.size_yzx)
        for (x, y, z), state in self.placed.items():
            assert loaded.palette[int(ids[y, z, x])] == state, f"cell (x={x}, y={y}, z={z}) changed on reload"
        return {
            "size_xyz": [size_x, size_y, size_z],
            "placed_blocks": len(self.placed),
            "palette_states": len(loaded.palette),
        }


def main() -> None:
    """Generate the build, export it, and verify the exported file."""
    parser = argparse.ArgumentParser(description="Generate a Minecraft schematic.")
    parser.add_argument("--output", type=Path, default=Path(CONFIG["output"]))
    parser.add_argument("--seed", type=int, default=CONFIG["seed"])
    args = parser.parse_args()

    build = Build(seed=args.seed).build()
    path = build.export(args.output)
    report = build.verify(path)
    print(f"wrote {path}: {report}")


if __name__ == "__main__":
    main()
