"""Live GUI preview server: render an app's JSON Schema as an RJSF form.

No Node/build step — the shared @acellera/pm-rjsf renderer (the same one pmview
uses) is loaded from esm.sh in the browser, so the preview matches production.
This closes the authoring loop: edit signature -> refresh -> see the form ->
copy inputs.json.
"""

from __future__ import annotations

import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

from ._generate import func_to_entry, signature_to_inputs_model

_PREVIEW_HTML = """<!doctype html>
<html><head><meta charset="utf-8"><title>pmmanifest GUI preview</title>
<style>
 body{font-family:system-ui,sans-serif;max-width:760px;margin:2rem auto;padding:0 1rem;color:#333}
 pre{background:#f5f5f5;padding:1rem;border-radius:8px;overflow:auto}
 .err{color:#b00}
 /* Form + grouping CSS is injected by @acellera/pm-rjsf (PMForm). */
</style></head>
<body>
<h2 id="title">Loading...</h2>
<p>Live RJSF preview of the app's JSON Schema. Submit to see the resulting
<code>inputs.json</code> (also validated server-side by the Pydantic model).</p>
<div id="root"></div>
<h3>Resulting inputs.json</h3>
<pre id="out">-</pre>
<script type="module">
import React from "https://esm.sh/react@18";
import { createRoot } from "https://esm.sh/react-dom@18/client";
import { PMForm } from "https://esm.sh/@acellera/pm-rjsf@0.0.2?deps=react@18,react-dom@18,@mui/material@5,@mui/icons-material@5,@emotion/react@11,@emotion/styled@11";

const e = React.createElement;
const data = await (await fetch("/schema")).json();
document.getElementById("title").textContent = data.name + " - GUI preview";
const out = document.getElementById("out");
function onSubmit({ formData }) {
  out.textContent = JSON.stringify(formData, null, 2);
  fetch("/inputs", { method: "POST", body: JSON.stringify(formData) })
    .then(r => r.json())
    .then(v => { if (!v.ok) out.innerHTML += '\\n\\n<span class="err">server validation: '
                                              + v.error + "</span>"; });
}

// Grouping panels, map fields, file widgets, "?" tooltips and field titles are
// all handled by @acellera/pm-rjsf's templates + transforms, applied inside
// PMForm -- so we hand it the raw schema and uiSchema.
try {
  createRoot(document.getElementById("root")).render(
    e(PMForm, { schema: data.schema, uiSchema: data.uiSchema, onSubmit }));
} catch (err) {
  document.getElementById("root").innerHTML = '<pre class="err">' + err + "</pre>";
}
</script>
</body></html>"""


def serve(func, *, port: int = 8000, function: str | None = None, name: str | None = None):
    """Serve a live RJSF preview of ``func``'s generated JSON Schema on ``port``.

    ``GET /schema`` returns ``{name, schema, uiSchema}``; ``GET /`` serves the
    page; ``POST /inputs`` validates submitted form data with the synthesized
    Pydantic model (proving coercion + PMPath flexibility).
    """
    entry = func_to_entry(func, function=function)
    app_name = name or getattr(func, "__name__", "app")
    payload = {"name": app_name, "schema": entry["inputSchema"], "uiSchema": entry["uiSchema"]}
    model, _ = signature_to_inputs_model(func)

    class _Handler(BaseHTTPRequestHandler):
        def log_message(self, *_args):
            pass

        def _send(self, code, body, ctype):
            data = body.encode() if isinstance(body, str) else body
            self.send_response(code)
            self.send_header("Content-Type", ctype)
            self.send_header("Content-Length", str(len(data)))
            self.end_headers()
            self.wfile.write(data)

        def do_GET(self):
            if self.path == "/":
                self._send(200, _PREVIEW_HTML, "text/html")
            elif self.path == "/schema":
                self._send(200, json.dumps(payload), "application/json")
            else:
                self._send(404, "not found", "text/plain")

        def do_POST(self):
            n = int(self.headers.get("Content-Length", 0))
            raw = self.rfile.read(n).decode() or "{}"
            try:
                model.model_validate(json.loads(raw))
                self._send(200, json.dumps({"ok": True}), "application/json")
            except Exception as ex:  # noqa: BLE001 - surface any validation error
                self._send(
                    200,
                    json.dumps({"ok": False, "error": str(ex).splitlines()[0]}),
                    "application/json",
                )

    print(f"pmmanifest: serving GUI preview on http://127.0.0.1:{port}  (Ctrl-C to stop)")
    ThreadingHTTPServer(("127.0.0.1", port), _Handler).serve_forever()
