#!/usr/bin/env python3
"""Dev server for the reikhelm playground, headless depth/FPV export, and the
pre-baked first-person explorer. Pure stdlib — no deps.

  python3 tools/serve.py                 # serve reikhelm-web on :8000

Three things, one server:
  GET  /<path>          static file from the web root (reikhelm-web/)
  GET  /out/<path>      static file from the output tree (tools/out/) — lets the
                        explorer load baked atlas frames + manifests
  POST /save/<path>.png write the raw request body (a PNG) under tools/out/<path>;
                        sub-dirs allowed and created (e.g. atlas/7/r3_N.png)
                        → {"ok": true, "path": "...", "bytes": N}

All generated images live under tools/out/ (git-ignored, organized):
  out/depth/    top-down + one-off depth maps     out/fpv/     first-person stills
  out/atlas/<seed>/   a baked explorable dungeon (frames + manifest.json)
"""
import argparse
import json
import os
import sys
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import unquote

REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


def safe_join(base, rel):
    """Join rel under base, collapsing any '..' so it can't escape base."""
    rel = os.path.normpath("/" + rel).lstrip("/")
    return os.path.join(base, rel)


class Handler(SimpleHTTPRequestHandler):
    out_dir = "."

    extensions_map = {
        **SimpleHTTPRequestHandler.extensions_map,
        ".js": "text/javascript",
        ".mjs": "text/javascript",
        ".wasm": "application/wasm",
        ".json": "application/json",
    }

    # Route /out/<path> to the output tree; everything else from the web root (cwd).
    def translate_path(self, path):
        p = unquote(path.split("?", 1)[0].split("#", 1)[0])
        if p == "/out" or p.startswith("/out/"):
            return safe_join(self.out_dir, p[len("/out/"):]) if p != "/out" else self.out_dir
        return super().translate_path(path)

    def end_headers(self):
        self.send_header("Cache-Control", "no-store")  # dev server: always serve fresh modules
        super().end_headers()

    def do_POST(self):
        if not self.path.startswith("/save/"):
            self.send_error(404, "only /save/<path>.png is supported")
            return
        rel = unquote(self.path[len("/save/"):])
        if not (rel.endswith(".png") or rel.endswith(".json")):
            self.send_error(400, "path must end in .png or .json")
            return
        dest = safe_join(self.out_dir, rel)
        length = int(self.headers.get("Content-Length", 0))
        body = self.rfile.read(length)
        os.makedirs(os.path.dirname(dest), exist_ok=True)
        with open(dest, "wb") as f:
            f.write(body)
        payload = json.dumps({"ok": True, "path": dest, "bytes": len(body)}).encode()
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(payload)))
        self.end_headers()
        self.wfile.write(payload)
        sys.stderr.write(f"saved {os.path.relpath(dest, REPO)}  ({len(body)} bytes)\n")

    def log_message(self, fmt, *args):  # quiet GETs; POSTs/errors print via do_POST/send_error
        pass


def main():
    ap = argparse.ArgumentParser(description=__doc__,
                                 formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("--port", type=int, default=8000)
    ap.add_argument("--root", default=os.path.join(REPO, "reikhelm-web"), help="static web root")
    ap.add_argument("--out", default=os.path.join(REPO, "tools", "out"), help="output tree (served at /out, target of /save)")
    args = ap.parse_args()

    Handler.out_dir = os.path.abspath(args.out)
    os.makedirs(Handler.out_dir, exist_ok=True)
    root = os.path.abspath(args.root)
    os.chdir(root)
    httpd = ThreadingHTTPServer(("127.0.0.1", args.port), Handler)
    print(f"serving {os.path.relpath(root, REPO)} at http://127.0.0.1:{args.port}")
    print(f"  /out/ → {os.path.relpath(Handler.out_dir, REPO)}   POST /save/<path>.png writes there")
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        httpd.shutdown()


if __name__ == "__main__":
    main()
