# Starting point for a Minecraft build generator. Copy into the workspace and fill in build().
# Everything that has to be right for the harness is already right here: bounds checked placement,
# one seeded random source, and a byte reproducible export.
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.nbt.nbt import File as NBTFile
from mcio.nbt.tag import Compound, String, Int, Byte, Float, Double, IntArray, List as NBTList
from mcio.schematic import load_schematic # noqa: E402
from mcio.sketch import LitematicCanvas, Material, parse_blockstate # noqa: E402

CONFIG = {
    "seed": 20260923,
    "size_xyz": (37, 43, 33), # width X, height Y, depth Z
    "minecraft_version": "1.21.1",
    "data_version": 3955,
    "output": "output.litematic",
    "name": "Merchant Manor",
    "author": "generator",
    "description": "The Rowan Exchange: timber merchant manor, furnished hall, cooperage, market, bedrooms, library and lookout.",
}

STONE = ["cobblestone"] * 4 + ["stone_bricks"] * 5 + ["andesite"] * 2 + ["mossy_cobblestone"]
PLASTER = ["smooth_sandstone"] * 9 + ["sandstone"] * 2 + ["cut_sandstone"]
ROOF = ["cobbled_deepslate"] * 5 + ["deepslate_bricks"] * 4 + ["deepslate_tiles"] * 2

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 p(self, x, y, z, name, **properties):
        """Place a vanilla block using a short name.

        Args:
            x, y, z (int): Block coordinates.
            name (str): Vanilla block name.
            **properties (str): State properties.
        """
        self.put(x, y, z, 'minecraft:' + name, **properties)

    def fill(self, bounds, name, **properties):
        """Fill a box with a short vanilla name.

        Args:
            bounds (tuple): Inclusive XYZ bounds.
            name (str): Vanilla block name.
            **properties (str): State properties.
        """
        self.box(bounds, 'minecraft:' + name, **properties)

    def mix(self, bounds, palette):
        """Texture a box with short vanilla names.

        Args:
            bounds (tuple): Inclusive XYZ bounds.
            palette (list): Weighted names.
        """
        self.texture(bounds, ['minecraft:' + name for name in palette])

    def door(self, x, y, z, facing='north', hinge='left'):
        """Place both matching halves of a door.

        Args:
            x, y, z (int): Lower block coordinates.
            facing, hinge (str): Door state.
        """
        for dy, half in enumerate(('lower', 'upper')):
            self.p(x, y + dy, z, 'spruce_door', facing=facing, hinge=hinge, half=half, open='false', powered='false')

    def window(self, x, y, z, width=2, height=3, side='north'):
        """Fit full glass, solid sills, and exterior timber hoods.

        Args:
            x, y, z (int): First window block.
            width, height (int): Window dimensions.
            side (str): Outside face.
        """
        along_x = side in ('north', 'south')
        dx, dz = {'north': (0, -1), 'south': (0, 1), 'west': (-1, 0), 'east': (1, 0)}[side]
        for i in range(width):
            a, b = x + (i if along_x else 0), z + (0 if along_x else i)
            for h in range(height):
                self.p(a, y + h, b, 'light_gray_stained_glass')
                self.windows.append((a, y + h, b, dx, dz))
            self.p(a, y - 1, b, 'spruce_planks')
            self.p(a + dx, y - 1, b + dz, 'spruce_slab', type='top')
            self.p(a + dx, y + height, b + dz, 'spruce_stairs', facing=side, half='top', shape='straight')

    def light(self, x, y, z):
        """Put a lantern on a solid pedestal or surface.

        Args:
            x, y, z (int): Lantern coordinate.
        """
        self.p(x, y, z, 'lantern', hanging='false', waterlogged='false')

    def chest(self, x, y, z, items, facing='north', barrel=False):
        """Place a stocked single chest or barrel with Java 1.21 item stacks.

        Args:
            x, y, z (int): Container coordinate.
            items (list): Item names and counts.
            facing (str): Front face.
            barrel (bool): Use a barrel.
        """
        name = 'barrel' if barrel else 'chest'
        props = {'facing': facing, 'open': 'false'} if barrel else {'facing': facing, 'type': 'single', 'waterlogged': 'false'}
        self.p(x, y, z, name, **props)
        inventory = NBTList[Compound]([Compound({'Slot': Byte(i), 'id': String('minecraft:' + item), 'count': Int(count)}) for i, (item, count) in enumerate(items)])
        self.tiles.append(Compound({'id': String('minecraft:' + name), 'x': Int(x), 'y': Int(y), 'z': Int(z), 'Items': inventory}))

    def frame(self, x, y, z, item, facing='north'):
        """Attach a visible item frame to the given solid block.

        Args:
            x, y, z (int): Supporting block.
            item (str): Display item.
            facing (str): Outward direction.
        """
        dx, dz, number = {'north': (0, -1, 2), 'south': (0, 1, 3), 'west': (-1, 0, 4), 'east': (1, 0, 5)}[facing]
        tx, tz = x + dx, z + dz
        uid = [0x4D414E4F, 0x52000000, 0, len(self.entities) + 1]
        self.entities.append(Compound({'id': String('minecraft:item_frame'), 'Facing': Byte(number), 'TileX': Int(tx), 'TileY': Int(y), 'TileZ': Int(tz), 'Pos': NBTList[Double]([Double(x + .5 + dx * .53125), Double(y + .5), Double(z + .5 + dz * .53125)]), 'Rotation': NBTList[Float]([Float({'north': 180, 'south': 0, 'west': 90, 'east': 270}[facing]), Float(0)]), 'Motion': NBTList[Double]([Double(0), Double(0), Double(0)]), 'UUID': IntArray(uid), 'Invulnerable': Byte(0), 'Fixed': Byte(0), 'Invisible': Byte(0), 'ItemRotation': Byte(0), 'ItemDropChance': Float(1), 'Item': Compound({'id': String('minecraft:' + item), 'count': Int(1)})}))

    def bed(self, x, y, z, color='red', facing='north'):
        """Place a supported bed pair.

        Args:
            x, y, z (int): Foot coordinate.
            color, facing (str): Bed appearance and head direction.
        """
        dx, dz = {'north': (0, -1), 'south': (0, 1), 'east': (1, 0), 'west': (-1, 0)}[facing]
        self.p(x, y, z, color + '_bed', facing=facing, part='foot', occupied='false')
        self.p(x + dx, y, z + dz, color + '_bed', facing=facing, part='head', occupied='false')

    def terrain(self):
        """Build a compact garden, worn paths, and a stone footing."""
        self.fill((1, 0, 1, 35, 0, 31), 'grass_block')
        # Broad working yard in front, service walk around the manor.
        for x in range(2, 35):
            for z in range(2, 31):
                if z < 12 or x in (2, 3, 33, 34) or z >= 29:
                    if self.rng.random() < .66:
                        self.p(x, 0, z, self.rng.choice(['coarse_dirt'] * 4 + ['dirt_path'] * 3 + ['gravel', 'rooted_dirt']))
        self.mix((5, 1, 11, 31, 1, 28), STONE)
        for x, z in [(3, 14), (3, 23), (34, 15), (34, 25), (7, 30), (25, 30), (12, 2), (22, 2)]:
            self.p(x, 1, z, 'mossy_cobblestone')
            self.p(x, 2, z, 'azalea_leaves', persistent='true', distance='1')
            if x in (3, 34):
                self.p(x, 3, z, 'oak_leaves', persistent='true', distance='1')
        for x in range(2, 35):
            for z in range(2, 31):
                if (x < 4 or x > 32 or z < 3 or z > 29) and self.get(x, 1, z) is None and self.get(x, 0, z) == 'minecraft:grass_block':
                    if self.rng.random() < .45:
                        self.p(x, 1, z, self.rng.choice(['short_grass'] * 6 + ['fern', 'oxeye_daisy', 'dandelion']))

    def undercroft(self):
        """Raise a stone basement and the surrounding terrace."""
        for bounds in [(5, 2, 11, 31, 7, 11), (5, 2, 28, 31, 7, 28), (5, 2, 12, 5, 7, 27), (31, 2, 12, 31, 7, 27)]:
            self.mix(bounds, STONE)
        self.fill((5, 7, 11, 31, 7, 11), 'stone_bricks')
        for x in (5, 14, 20, 23, 31):
            self.fill((x, 2, 10, x, 6, 11), 'stone_bricks')
            self.p(x, 6, 10, 'chiseled_stone_bricks')
            self.p(x, 7, 10, 'stone_brick_stairs', facing='south', half='top', shape='straight')
        for z in (16, 22, 28):
            for x in (4, 32):
                self.fill((x, 1, z, x, 6, z), 'stone_bricks')
                self.p(x, 6, z, 'chiseled_stone_bricks')
                self.p(x, 7, z, 'stone_brick_stairs', facing='east' if x == 4 else 'west', half='top')
        self.fill((4, 8, 10, 32, 8, 29), 'stone_bricks')
        self.mix((5, 8, 11, 31, 8, 28), ['spruce_planks'] * 8 + ['dark_oak_planks'])
        # Polished, shouldered arch and a glazed transom above the double door.
        for x in (9, 12):
            self.fill((x, 2, 10, x, 4, 11), 'polished_andesite')
        self.fill((9, 5, 10, 12, 5, 11), 'polished_andesite')
        self.p(9, 4, 10, 'polished_andesite_stairs', facing='east', half='top')
        self.p(12, 4, 10, 'polished_andesite_stairs', facing='west', half='top')
        for x in (10, 11):
            self.door(x, 2, 11, hinge='left' if x == 10 else 'right')
            self.p(x, 4, 11, 'light_gray_stained_glass')
            self.p(x, 1, 10, 'polished_andesite')
        # Glass behind iron bars closes the envelope while preserving the barred look.
        for x in (7, 17, 22):
            for y in (3, 4):
                self.p(x, y, 11, 'gray_stained_glass')
                if x != 17:
                    self.p(x, y, 10, 'iron_bars', north='false', south='false', east='true', west='true')
            self.p(x, 2, 10, 'stone_brick_slab', type='top')
        for z in (15, 19, 25):
            for y in (3, 4):
                self.p(31, y, z, 'gray_stained_glass')
                self.p(32, y, z, 'iron_bars', north='true', south='true', east='false', west='false')
            self.p(32, 2, z, 'stone_brick_slab', type='top')
        for x in (10, 17, 25):
            self.window(x, 3, 28, 2, 2, 'south')
        # Market stock room partition, one doorway, no inaccessible compartments.
        self.fill((21, 2, 12, 21, 7, 27), 'stone_bricks')
        self.door(21, 2, 20, facing='east')
        for x in (26, 27):
            self.door(x, 2, 11, hinge='left' if x == 26 else 'right')
        self.fill((25, 4, 10, 28, 4, 11), 'spruce_planks')
        # Eight broad steps make the terrace the obvious public entrance.
        for z in range(3, 11):
            y = z - 2
            self.mix((15, 0, z, 19, y - 1, z), STONE)
            self.fill((15, y, z, 19, y, z), 'stone_brick_stairs', facing='south', half='bottom', shape='straight')
            for x in (14, 20):
                self.fill((x, 0, z, x, y, z), 'stripped_spruce_log', axis='y')
                self.p(x, y + 1, z, 'spruce_fence', north='true', south='true')
        for x in range(4, 33):
            if x not in range(14, 21):
                self.p(x, 9, 10, 'spruce_fence', east='true', west='true')
            self.p(x, 9, 29, 'spruce_fence', east='true', west='true')
        for z in range(11, 29):
            for x in (4, 32):
                self.p(x, 9, z, 'spruce_fence', north='true', south='true')
        for x, z in [(4, 10), (32, 10), (4, 29), (32, 29)]:
            self.p(x, 9, z, 'stripped_dark_oak_log')
            self.light(x, 10, z)

    def timber(self):
        """Frame the hall with warm plaster panels and deep window reveals."""
        for bounds in [(7, 9, 13, 29, 14, 13), (7, 9, 26, 29, 14, 26), (7, 9, 14, 7, 14, 25), (29, 9, 14, 29, 14, 25)]:
            self.mix(bounds, PLASTER)
        self.fill((7, 15, 12, 29, 15, 26), 'spruce_planks')
        for z in (13, 26):
            for x in (7, 12, 15, 20, 24, 29):
                self.fill((x, 9, z, x, 14, z), 'stripped_spruce_log', axis='y')
            for y in (9, 14):
                self.fill((7, y, z, 29, y, z), 'dark_oak_log', axis='x')
        for x in (7, 29):
            for z in (13, 18, 22, 26):
                self.fill((x, 9, z, x, 14, z), 'stripped_spruce_log', axis='y')
            self.fill((x, 14, 13, x, 14, 26), 'dark_oak_log', axis='z')
        for x in (9, 22, 26):
            self.window(x, 10, 13)
        for x in (9, 17, 25):
            self.window(x, 10, 26, side='south')
        for x, side in ((7, 'west'), (29, 'east')):
            for z in (15, 20, 24):
                self.window(x, 10, z, 1, 3, side)
        for x in (16, 19):
            self.fill((x, 9, 12, x, 12, 13), 'stripped_dark_oak_log')
        self.door(17, 9, 13)
        self.door(18, 9, 13, hinge='right')
        self.fill((16, 12, 12, 19, 12, 13), 'spruce_planks')
        self.fill((16, 12, 11, 19, 12, 11), 'spruce_stairs', facing='south', half='bottom')
        for x in (15, 20):
            self.p(x, 10, 12, 'spruce_planks')
            self.light(x, 11, 12)
        for z in (12, 27):
            self.fill((6, 15, z, 30, 15, z), 'spruce_slab', type='top')
        # Repeating carved corbels make the terrace facade read as heavy timber.
        for x in (7, 12, 15, 20, 24, 29):
            self.p(x, 13, 12, 'spruce_stairs', facing='south', half='top')
            self.p(x, 14, 12, 'spruce_planks')
            self.p(x, 13, 27, 'spruce_stairs', facing='north', half='top')

    def roofs(self):
        """Join two steep roofs with a closed union of their roof surfaces."""
        heights = {}
        for x in range(6, 31):
            for z in range(11, 29):
                h = 16 + min(z - 11, 28 - z)
                heights[x, z] = (h, 'south' if z < 20 else 'north', x in (6, 30) or z in (11, 28))
        for x in range(21, 32):
            for z in range(10, 21):
                h = 16 + (min(x - 21, 31 - x) * 3) // 2
                if h >= heights.get((x, z), (-1,))[0]:
                    heights[x, z] = (h, 'east' if x < 26 else 'west', z == 10 or x in (21, 31))
        # Close the upper storey before laying the weather surface.
        for x in range(7, 30):
            for z in range(13, 27):
                if x in (7, 29) or z in (13, 26):
                    h = heights[x, z][0]
                    self.mix((x, 16, z, x, h - 1, z), PLASTER)
                    if x in (7, 12, 15, 20, 24, 29) or z in (18, 22):
                        self.fill((x, 16, z, x, h - 1, z), 'stripped_spruce_log')
        # Front cross gable projects one block over the hall wall.
        for x in range(22, 31):
            h = heights[x, 12][0]
            self.mix((x, 15, 12, x, h - 1, 12), PLASTER)
            if x in (22, 26, 30):
                self.fill((x, 16, 12, x, h - 1, 12), 'stripped_spruce_log')
        for z in (11, 12):
            for x in (22, 30):
                self.fill((x, 15, z, x, heights[x, z][0] - 1, z), 'spruce_planks')
        for (x, z), (h, direction, trim) in heights.items():
            self.p(x, h, z, 'spruce_planks' if trim else self.rng.choice(ROOF))
            self.p(x, h + 1, z, 'spruce_stairs' if trim else 'cobbled_deepslate_stairs', facing=direction, half='bottom', shape='straight')
            # Tall cross gable steps have a filled riser beneath the next course.
            for nx, nz in ((x - 1, z), (x + 1, z), (x, z - 1), (x, z + 1)):
                if (nx, nz) in heights and heights[nx, nz][0] < h - 1:
                    self.p(x, h - 1, z, 'spruce_planks' if trim else self.rng.choice(ROOF))
        # Carved end grain bosses, raised eaves and ridge finials.
        for x in (6, 30):
            for z in (11, 13, 15, 17, 20, 22, 24, 26, 28):
                h = heights[x, z][0]
                self.p(x, h, z, 'spruce_log', axis='x')
                self.p(x, h - 1, z, 'spruce_stairs', facing='east' if x == 6 else 'west', half='top')
        for x in (21, 23, 25, 27, 29, 31):
            h = heights[x, 10][0]
            self.p(x, h, 10, 'spruce_log', axis='z')
            self.p(x, h - 1, 10, 'spruce_stairs', facing='south', half='top')
        self.window(25, 18, 12, 2, 3)
        self.window(7, 18, 17, 1, 3, 'west')
        self.window(29, 18, 21, 2, 3, 'east')
        for x in (6, 30):
            for z in (11, 28):
                self.p(x, 18, z, 'spruce_slab', type='bottom')
        self.roof_heights = heights

    def tower(self):
        """Add the central study tower, gabled crown, and smaller western spire."""
        # The tower cuts through the main roof; its walls replace the roof intersections.
        self.fill((15, 16, 17, 23, 32, 25), 'air')
        for bounds in [(15, 16, 17, 23, 32, 17), (15, 16, 25, 23, 32, 25), (15, 16, 18, 15, 32, 24), (23, 16, 18, 23, 32, 24)]:
            self.mix(bounds, PLASTER)
        for x in (15, 19, 23):
            for z in (17, 25):
                self.fill((x, 16, z, x, 32, z), 'stripped_spruce_log')
        for x in (15, 23):
            for z in (21,):
                self.fill((x, 16, z, x, 32, z), 'stripped_spruce_log')
        for y in (22, 28, 32):
            for z in (17, 25):
                self.fill((15, y, z, 23, y, z), 'dark_oak_log', axis='x')
            for x in (15, 23):
                self.fill((x, y, 17, x, y, 25), 'dark_oak_log', axis='z')
        for y in (22, 28):
            self.fill((16, y, 18, 22, y, 24), 'spruce_planks')
        self.door(15, 16, 18, facing='west')
        self.door(23, 16, 20, facing='east')
        for y, height in ((24, 3), (29, 3)):
            for x in (17, 21):
                self.window(x, y, 17, 1, height)
                self.window(x, y, 25, 1, height, 'south')
            for x, side in ((15, 'west'), (23, 'east')):
                self.window(x, 26 if y == 24 else y, 19, 1, 2 if y == 24 else height, side)
                self.window(x, y, 23, 1, height, side)
        for x in range(14, 25):
            h = 33 + (min(x - 14, 24 - x) * 6) // 5
            for z in (17, 25):
                self.mix((x, 33, z, x, max(33, h - 1), z), PLASTER)
                if x == 19:
                    self.fill((x, 33, z, x, h - 1, z), 'stripped_spruce_log')
            for z in range(16, 27):
                trim = z in (16, 26) or x in (14, 24)
                self.p(x, h, z, 'spruce_planks' if trim else self.rng.choice(ROOF))
                self.p(x, h + 1, z, 'spruce_stairs' if trim else 'deepslate_tile_stairs', facing='east' if x < 19 else 'west', half='bottom')
                if x == 19:
                    self.p(x, h + 1, z, 'spruce_slab', type='bottom')
                if min(x - 14, 24 - x) == 5:
                    self.p(x, h - 1, z, 'spruce_planks' if trim else 'deepslate_bricks')
            if x % 2 == 0:
                for z in (16, 26):
                    self.p(x, h, z, 'spruce_log', axis='z')
                    self.p(x, h - 1, z, 'spruce_stairs', facing='south' if z == 16 else 'north', half='top')
        for z in (16, 26):
            self.p(19, 40, z, 'spruce_planks')
            self.p(19, 41, z, 'spruce_fence')
        self.window(19, 35, 17, 1, 2)
        self.window(19, 35, 25, 1, 2, 'south')
        # Ladder stays in one corner, leaving each landing walkable.
        for y in range(16, 30):
            self.p(22, y, 24, 'ladder', facing='west', waterlogged='false')
        # Secondary turret: stone shaft, small accessible watch room, steep slate cap.
        self.fill((7, 16, 19, 11, 28, 23), 'air')
        for bounds in [(7, 16, 19, 11, 28, 19), (7, 16, 23, 11, 28, 23), (7, 16, 20, 7, 28, 22), (11, 16, 20, 11, 28, 22)]:
            self.mix(bounds, ['andesite', 'polished_andesite', 'diorite', 'andesite'])
        self.fill((8, 23, 20, 10, 23, 22), 'spruce_planks')
        self.door(11, 16, 21, facing='east')
        for x, y, z, side in [(9, 25, 19, 'north'), (7, 25, 21, 'west'), (11, 25, 21, 'east'), (9, 25, 23, 'south')]:
            self.window(x, y, z, 1, 2, side)
        for y in range(16, 25):
            self.p(10, y, 22, 'ladder', facing='west')
        for y, radius in [(29, 3), (30, 2), (31, 2), (32, 1), (33, 1), (34, 0)]:
            for x in range(9 - radius, 10 + radius):
                for z in range(21 - radius, 22 + radius):
                    name = 'spruce_planks' if y == 29 else self.rng.choice(ROOF)
                    self.p(x, y, z, name)
                    if y == 29 and (x in (6, 12) or z in (18, 24)):
                        self.p(x, y + 1, z, 'spruce_slab', type='bottom')
        self.p(9, 35, 21, 'deepslate_brick_wall')
        self.p(9, 36, 21, 'lightning_rod', facing='up')

    def stairs_and_furnishings(self):
        """Connect every level and furnish the storage, hall, bedrooms and study."""
        # Internal service stairs from storage to hall, with a three-block clear stairwell.
        for z in range(20, 27):
            y = 28 - z
            self.fill((18, y + 1, z, 19, y + 3, z), 'air')
            self.fill((18, 2, z, 19, y - 1, z), 'stone_bricks') if y > 2 else None
            self.fill((18, y, z, 19, y, z), 'stone_brick_stairs', facing='north', half='bottom')
        for z in range(20, 25):
            self.p(17, 9, z, 'spruce_fence', north='true', south='true')
        # Main stair to the attic, away from the hearth and entrance.
        for z in range(17, 24):
            y = 32 - z
            self.fill((9, y + 1, z, 10, y + 3, z), 'air')
            if y > 9:
                self.fill((9, 9, z, 10, y - 1, z), 'spruce_planks')
            self.fill((9, y, z, 10, y, z), 'spruce_stairs', facing='north', half='bottom')
        # A little upper landing also leads to the spire ladder.
        self.fill((8, 15, 18, 8, 15, 19), 'spruce_planks')
        self.fill((8, 16, 18, 8, 18, 18), 'air')
        self.door(11, 16, 21, facing='east')
        for y in range(16, 25):
            self.p(10, y, 22, 'ladder', facing='west')
        # Undercroft cooperage and goods racks.
        for x in (6, 7, 8, 13, 14):
            for y in (2, 3):
                self.chest(x, y, 27, [('wheat', 32), ('potato', 24)], barrel=True)
        for z in (15, 17, 23):
            self.chest(6, 2, z, [('iron_ingot', 12), ('coal', 32)])
            self.p(6, 4, z, 'spruce_planks')
            self.light(6, 5, z)
        self.chest(13, 2, 14, [('emerald', 18), ('gold_ingot', 8)])
        self.fill((12, 2, 22, 14, 2, 23), 'hay_block', axis='y')
        self.p(14, 3, 23, 'hay_block', axis='y')
        self.fill((11, 2, 18, 13, 2, 18), 'spruce_planks')
        self.p(11, 3, 18, 'crafting_table')
        self.light(13, 3, 18)
        self.frame(14, 3, 27, 'wheat')
        self.p(17, 2, 12, 'stone_bricks')
        self.light(17, 3, 12)
        self.p(20, 2, 17, 'stone_bricks')
        self.light(20, 3, 17)
        self.p(8, 16, 20, 'spruce_planks')
        self.light(8, 17, 20)
        self.p(16, 2, 27, 'stone_bricks')
        self.light(16, 3, 27)
        for x, z in ((23, 26), (25, 26), (29, 26), (29, 17), (29, 21)):
            self.chest(x, 2, z, [('bread', 16), ('apple', 24), ('carrot', 32)], barrel=True)
        for x, z in ((23, 14), (29, 14), (23, 24), (29, 24)):
            self.p(x, 2, z, 'spruce_planks')
            self.light(x, 3, z)
        self.fill((24, 2, 17, 27, 2, 17), 'spruce_planks')
        self.p(24, 3, 17, 'crafting_table')
        self.p(27, 3, 17, 'flower_pot')
        self.frame(25, 2, 26, 'apple')
        # Hall: carpet runner and merchant counter.
        for x in range(13, 17):
            for z in range(15, 20):
                self.p(x, 9, z, 'red_carpet' if x in (13, 16) or z in (15, 19) else 'orange_carpet')
        self.fill((21, 9, 16, 25, 9, 16), 'dark_oak_planks')
        self.p(21, 10, 16, 'potted_fern')
        self.p(23, 10, 16, 'yellow_candle', candles='3', lit='true')
        self.light(25, 10, 16)
        self.p(23, 9, 18, 'spruce_stairs', facing='south')
        self.chest(25, 9, 18, [('emerald', 32), ('paper', 16)])
        self.frame(24, 11, 13, 'emerald', facing='south')
        # Seating and dining table.
        self.fill((12, 9, 23, 15, 9, 23), 'dark_oak_planks')
        self.p(12, 10, 23, 'white_candle', candles='3', lit='true')
        self.p(15, 10, 23, 'flower_pot')
        for x in (12, 14):
            self.p(x, 9, 22, 'spruce_stairs', facing='north')
            if x == 12:
                self.p(x, 9, 24, 'spruce_stairs', facing='south')
        # Hearth and stone flue sit against the east wall, clear of windows.
        self.fill((26, 9, 22, 28, 9, 24), 'stone_bricks')
        self.fill((28, 10, 22, 28, 13, 24), 'bricks')
        self.p(27, 10, 23, 'campfire', facing='west', lit='true', signal_fire='false')
        self.fill((26, 10, 22, 26, 11, 22), 'polished_andesite')
        self.fill((26, 10, 24, 26, 11, 24), 'polished_andesite')
        self.fill((26, 12, 22, 28, 12, 24), 'stone_bricks')
        self.p(26, 10, 23, 'iron_bars', north='true', south='true')
        self.fill((27, 13, 22, 28, 14, 24), 'bricks')
        # Kitchen keeps a clear central passage to the service stair.
        self.fill((21, 9, 25, 24, 9, 25), 'spruce_planks')
        self.p(21, 9, 25, 'smoker', facing='north', lit='false')
        self.p(22, 9, 25, 'furnace', facing='north', lit='false')
        self.p(23, 9, 25, 'water_cauldron', level='3')
        self.p(24, 9, 25, 'crafting_table')
        self.light(24, 10, 25)
        self.fill((21, 12, 25, 25, 12, 25), 'spruce_planks')
        self.p(22, 13, 25, 'flower_pot')
        for x, z in ((8, 14), (13, 25), (20, 14)):
            self.p(x, 9, z, 'spruce_planks')
            self.light(x, 10, z)
        # Attic bedrooms, with trunks, carpets and bedside candles.
        self.bed(12, 16, 16, 'red')
        self.bed(13, 16, 16, 'red')
        self.chest(13, 16, 18, [('leather_boots', 1), ('bread', 4)])
        self.p(11, 16, 15, 'spruce_planks')
        self.light(11, 17, 15)
        self.p(12, 16, 17, 'red_carpet')
        self.bed(26, 16, 16, 'blue')
        self.p(27, 16, 15, 'spruce_planks')
        self.light(27, 17, 15)
        self.chest(28, 16, 18, [('book', 5), ('compass', 1)])
        for z in range(18, 24):
            self.p(25, 16, z, 'blue_carpet')
        self.fill((27, 16, 24, 28, 17, 24), 'bookshelf')
        self.p(26, 16, 24, 'spruce_planks')
        self.light(26, 17, 24)
        # Tower lower landing: quiet map room below the study.
        self.fill((16, 16, 19, 16, 18, 22), 'bookshelf')
        self.p(17, 16, 20, 'spruce_planks')
        self.light(17, 17, 20)
        self.p(20, 16, 19, 'cartography_table')
        self.frame(19, 18, 17, 'map', facing='south')
        # Study, accessible directly from the ladder landing.
        self.fill((16, 23, 20, 16, 25, 23), 'bookshelf')
        self.fill((18, 23, 18, 20, 23, 18), 'dark_oak_planks')
        self.p(18, 24, 18, 'potted_dead_bush')
        self.p(20, 24, 18, 'white_candle', candles='3', lit='true')
        self.p(19, 23, 20, 'spruce_stairs', facing='south')
        self.p(21, 23, 21, 'lectern', facing='west', has_book='true', powered='false')
        self.tiles.append(Compound({'id': String('minecraft:lectern'), 'x': Int(21), 'y': Int(23), 'z': Int(21), 'Page': Int(0), 'Book': Compound({'id': String('minecraft:written_book'), 'count': Int(1), 'components': Compound({'minecraft:written_book_content': Compound({'title': String('Merchant Ledger'), 'author': String('House of the Rowan'), 'generation': Int(0), 'resolved': Byte(1), 'pages': NBTList[String]([String('{"text":"Autumn ledger\\n\\nWheat, wool, copper and salt.\\n\\nA warm hearth for every traveler."}')])})})})}))
        self.p(16, 23, 24, 'spruce_planks')
        self.light(16, 24, 24)
        self.chest(20, 23, 24, [('emerald', 12), ('book', 6), ('compass', 1)])
        self.fill((18, 23, 21, 19, 23, 23), 'green_carpet')
        # Top lookout: a bench, telescope stand and map cabinet.
        self.fill((16, 29, 21, 16, 29, 23), 'spruce_stairs', facing='west')
        self.p(19, 29, 18, 'cartography_table')
        self.p(19, 30, 18, 'yellow_candle', candles='2', lit='true')
        self.p(21, 29, 21, 'spruce_planks')
        self.frame(21, 29, 21, 'spyglass')
        self.p(16, 29, 24, 'spruce_planks')
        self.light(16, 30, 24)
        self.fill((18, 29, 21, 19, 29, 23), 'green_carpet')
        # The tiny turret has a useful lookout and its own light.
        self.p(8, 24, 20, 'spruce_planks')
        self.light(8, 25, 20)
        self.p(8, 24, 22, 'spruce_stairs', facing='west')

    def market_and_chimney(self):
        """Build the striped street stall, left loading shelter and smoking brick stack."""
        for x in (23, 31):
            self.fill((x, 1, 6, x, 5, 6), 'spruce_fence', north='false', south='false', east='false', west='false')
            self.p(x, 1, 6, 'stripped_spruce_log')
        for x in range(23, 32):
            color = 'red_wool' if (x - 23) // 2 % 2 == 0 else 'white_wool'
            for z in range(6, 11):
                y = 5 if z <= 7 else 6
                self.p(x, y, z, color)
            self.p(x, 4, 6, color)
        self.fill((23, 6, 10, 31, 6, 10), 'spruce_planks')
        self.fill((24, 1, 7, 30, 1, 7), 'spruce_planks')
        self.fill((24, 2, 7, 30, 2, 7), 'barrel', facing='up', open='false')
        for x, block in ((24, 'pumpkin'), (25, 'melon'), (29, 'hay_block')):
            self.p(x, 3, 7, block)
        self.p(27, 3, 7, 'flower_pot')
        self.light(30, 3, 7)
        self.frame(26, 2, 7, 'bread')
        self.frame(28, 2, 7, 'apple')
        # A gabled trade sign, solidly attached to the stall beam.
        self.fill((25, 7, 10, 29, 7, 10), 'dark_oak_planks')
        self.frame(27, 7, 10, 'emerald')
        # Left lean-to, loaded with coopered casks and bundled bales.
        for x in (2, 8):
            for z in (5, 10):
                self.fill((x, 1, z, x, 5, z), 'stripped_spruce_log')
                self.p(x, 1, z, 'stone_bricks')
        self.fill((2, 1, 5, 8, 1, 10), 'spruce_planks')
        for z in range(4, 12):
            y = 5 + (z - 4) // 3
            self.fill((1, y, z, 9, y, z), 'spruce_planks')
            self.fill((1, y + 1, z, 9, y + 1, z), 'spruce_slab', type='bottom')
        for x, z in ((3, 9), (4, 9), (7, 9), (7, 7)):
            self.chest(x, 2, z, [('wheat', 32), ('coal', 16)], barrel=True)
        self.chest(4, 2, 6, [('iron_ingot', 16), ('copper_ingot', 24)])
        self.p(3, 3, 9, 'barrel', facing='north', open='false')
        self.fill((5, 2, 9, 6, 3, 9), 'hay_block', axis='x')
        self.light(7, 3, 7)
        # A tall, offset brick stack, with a real signal campfire at its open top.
        self.fill((27, 15, 23, 28, 31, 24), 'bricks')
        for y in (18, 25, 29):
            self.fill((27, y, 23, 28, y, 24), 'granite')
        for y in (26, 32):
            self.fill((26, y, 22, 29, y, 25), 'brick_slab', type='top')
            self.fill((27, y, 23, 28, y, 24), 'bricks')
        self.p(27, 32, 23, 'hay_block')
        self.p(27, 33, 23, 'campfire', facing='north', lit='true', signal_fire='true')
        self.p(28, 33, 24, 'brick_wall')
        self.p(28, 33, 23, 'brick_wall')
        self.p(27, 33, 24, 'brick_wall')

    def greenery(self):
        """Attach climbing vines and compact ivy clusters to existing masonry and timber."""
        # Only place wall vines against solid, window-free facade blocks.
        for x, z, levels, face in [(8, 12, range(9, 14), 'south'), (13, 12, range(9, 15), 'south'), (30, 17, range(9, 15), 'west'), (6, 24, range(9, 14), 'east'), (14, 20, range(23, 32), 'east'), (16, 16, range(21, 30), 'south'), (22, 16, range(23, 31), 'south'), (24, 24, range(24, 32), 'west'), (6, 11, range(2, 7), 'south')]:
            dx, dz = {'south': (0, 1), 'north': (0, -1), 'east': (1, 0), 'west': (-1, 0)}[face]
            for y in levels:
                support = self.get(x + dx, y, z + dz) or 'air'
                if self.get(x, y, z) in (None, 'minecraft:air') and all(word not in support for word in ('air', 'glass', 'door', 'stairs', 'slab')):
                    self.p(x, y, z, 'vine', **{face: 'true'})
        # Roof ivy is physically rooted to the wood edge, then grows in connected leaves.
        clusters = [(6, 18, 11), (6, 21, 14), (14, 26, 17), (24, 30, 22), (15, 24, 16), (21, 23, 10), (5, 3, 13), (13, 2, 10)]
        protected = {(x + dx, y, z + dz) for x, y, z, dx, dz in self.windows}
        offsets = [(1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)]
        for cx, cy, cz in clusters:
            for _ in range(95):
                x = cx + self.rng.randint(-1, 1)
                y = cy + self.rng.randint(-3, 1)
                z = cz + self.rng.randint(-1, 1)
                if not self.inside(x, y, z) or (x, y, z) in protected or self.get(x, y, z) not in (None, 'minecraft:air'):
                    continue
                adjacent = [self.get(x + dx, y + dy, z + dz) or 'air' for dx, dy, dz in offsets]
                if any(any(w in s for w in ('log', 'planks', 'deepslate', 'leaves', 'cobblestone')) for s in adjacent):
                    self.p(x, y, z, 'oak_leaves', persistent='true', distance='1')
        # Planters stand on solid terrace decking, clear of the entrance.
        for x, z in ((6, 12), (30, 13), (6, 27), (30, 27)):
            self.p(x, 9, z, 'composter', level='8')
            self.p(x, 10, z, 'azalea_leaves', persistent='true', distance='1')

    def orient_front(self):
        """Reflect the plan so the north-facing facade matches the reference's left and right.

        The architectural plan uses eastward coordinates for the market wing.
        Seen from the street, west is on the viewer's right. Reflect geometry,
        states, block entities and hanging entities together before export.
        """
        placed = self.placed
        self.placed = {}
        self.canvas = LitematicCanvas((self.size_y, self.size_z, self.size_x))
        swap = {'east': 'west', 'west': 'east'}
        for (x, y, z), state in placed.items():
            name, _, raw = state.partition('[')
            props = dict(part.split('=') for part in raw.rstrip(']').split(',') if '=' in part)
            mirrored = {}
            for key, value in props.items():
                if key == 'facing':
                    value = swap.get(value, value)
                elif key == 'hinge':
                    value = 'right' if value == 'left' else 'left'
                elif key == 'shape':
                    value = {'inner_left': 'inner_right', 'inner_right': 'inner_left', 'outer_left': 'outer_right', 'outer_right': 'outer_left'}.get(value, value)
                mirrored[swap.get(key, key)] = value
            self.put(self.size_x - 1 - x, y, z, name, **mirrored)
        for tile in self.tiles:
            tile['x'] = Int(self.size_x - 1 - int(tile['x']))
        for entity in self.entities:
            entity['TileX'] = Int(self.size_x - 1 - int(entity['TileX']))
            entity['Pos'][0] = Double(self.size_x - float(entity['Pos'][0]))
            entity['Facing'] = Byte({4: 5, 5: 4}.get(int(entity['Facing']), int(entity['Facing'])))
            entity['Rotation'][0] = Float((-float(entity['Rotation'][0])) % 360)
        self.windows = [(self.size_x - 1 - x, y, z, -dx, dz) for x, y, z, dx, dz in self.windows]

    def build(self) -> 'Build':
        """Construct the complete manor from ground up.

        Returns:
            Build: This furnished instance.
        """
        self.tiles, self.entities, self.windows = [], [], []
        self.terrain()
        self.undercroft()
        self.timber()
        self.roofs()
        self.tower()
        self.stairs_and_furnishings()
        self.market_and_chimney()
        self.greenery()
        self.orient_front()
        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)
        nbt = schematic.write_to_nbt()
        region = nbt["Regions"][CONFIG["name"]]
        region["TileEntities"] = NBTList[Compound](self.tiles)
        region["Entities"] = NBTList[Compound](self.entities)
        NBTFile(nbt).save(path, gzipped=False)
        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()
