# Render sample exteriors and useful interior cuts, with a local labelled HTML gallery.
from __future__ import annotations

import argparse
import html
import json
import subprocess
import sys
import tempfile
from pathlib import Path

ROOT = Path(__file__).resolve().parent
VITE = Path('/work/generator/MCRender/node_modules/.bin/vite-node')
CUTAWAY = Path('/opt/tools/cutaway.py')
RENDER_ONE = Path('/opt/tools/render_schematic.mjs')
CUTS = {'default', 'cliff_courtyard', 'winter_garden_crag', 'cross_gabled_lodge', 'watchtower_inn', 'compact_cottage', 'cross_roof_conservatory'}


def run(command):
    """Run one renderer process and retain error details without noisy asset logs."""
    result = subprocess.run([str(c) for c in command], cwd=ROOT, capture_output=True, text=True)
    if result.returncode:
        raise RuntimeError(result.stdout + result.stderr)


def main():
    """Render all manifest entries and clean temporary cutaway schematics afterward."""
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--manifest', type=Path, default=ROOT / 'samples/manifest.json')
    parser.add_argument('--output', type=Path, default=ROOT / 'previews')
    parser.add_argument('--only', nargs='*', help='Optional sample names to rerender')
    parser.add_argument('--cuts-only', action='store_true', help='Refresh cutaways while retaining existing exterior renders')
    args = parser.parse_args()
    manifest = json.loads(args.manifest.read_text())
    args.output.mkdir(parents=True, exist_ok=True)
    sections = []
    for sample in manifest['samples']:
        name = sample['name']
        source = args.manifest.parent / sample['schematic']
        output = args.output / name
        images = ['front', 'rear'] + (['ground_cutaway', 'stair_section'] if name in CUTS else [])
        if args.only is None or name in args.only:
            output.mkdir(parents=True, exist_ok=True)
            if not args.cuts_only:
                run(['xvfb-run', '-a', VITE, '--script', ROOT / 'render.mjs', '--', source, output])
            if name in CUTS:
                record = json.loads(source.with_suffix('.json').read_text())
                main = record['volumes'][0]
                ox, _, oz = record['origin']
                # Work under test/ and remove the intermediate schematics on success or failure.
                with tempfile.TemporaryDirectory(prefix='render-cuts-', dir=ROOT / 'test') as directory:
                    sliced = Path(directory) / 'cut.litematic'
                    run([sys.executable, CUTAWAY, '--input', source, '--output', sliced, '--max-y', main['base'] + 3])
                    run(['xvfb-run', '-a', VITE, '--script', RENDER_ONE, '--', '--input', sliced, '--output', output / 'ground_cutaway.png', '--size', 640, '--azimuth', 210, '--elevation', 70, '--radius', 2.85])
                    # Keep the side containing the interior flight; look into the exposed section.
                    axis = 'x' if main['axis'] == 'z' else 'z'
                    center = (main[axis + '0'] + main[axis + '1']) // 2
                    right = record['parameters']['stair_side'] == 'right'
                    cut = center + (1 if right else -2) + (ox if axis == 'x' else oz)
                    command = [sys.executable, CUTAWAY, '--input', source, '--output', sliced, '--axis', axis, '--max', cut]
                    if right:
                        command.append('--keep-above')
                    run(command)
                    azimuth = (250 if right else 70) if axis == 'x' else (190 if right else 10)
                    run(['xvfb-run', '-a', VITE, '--script', RENDER_ONE, '--', '--input', sliced, '--output', output / 'stair_section.png', '--size', 640, '--azimuth', azimuth, '--elevation', 22, '--radius', 3.0])
            print(f'rendered {name}: {", ".join(images)}', flush=True)
        p = sample['parameters']
        caption = f"Seed {sample['seed']} | {p['topology']} | {p['floors']} full storeys + attic | {p['profile']} roof | {p['tower']} tower | {p['terrain']}"
        figures = ''.join(f'<figure><a href="{name}/{image}.png"><img loading="lazy" src="{name}/{image}.png" alt="{html.escape(name + ": " + image)}"></a><figcaption>{image.replace("_", " ")}</figcaption></figure>' for image in images)
        sections.append(f'<section><h2>{html.escape(name.replace("_", " "))}</h2><p>{html.escape(caption)}</p><div>{figures}</div></section>')
    page = '''<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Procedural lodges: validation gallery</title><style>body{background:#23282d;color:#eee;font:16px system-ui;margin:2rem auto;max-width:1500px;padding:0 1rem}h1{font-size:2rem}h2{font-size:1.25rem;text-transform:capitalize}section{padding:1rem 0;border-top:1px solid #53606b}section div{display:flex;flex-wrap:wrap}figure{margin:4px;flex:1 1 300px;max-width:48%}img{width:100%;background:#2f343b}figcaption{padding:6px;color:#bdc7ce}p{color:#ccd4db}</style><h1>Procedural lodge gallery</h1><p>Actual schematic geometry. Every sample passed block, circulation, envelope and reload validation. Cutaways expose furniture and stairs; they are inspection artifacts, not playable schematics.</p>''' + ''.join(sections) + '</html>\n'
    (args.output / 'index.html').write_text(page)


if __name__ == '__main__':
    main()
