"""Work order: the commercial paperwork the jeweler prints with the job.

Self-contained HTML: spec, exact dims, per-alloy weights, print/cast
instructions with the shrink-compensation warning, setting notes, and a QC
checklist. Volumes come from the exported STLs (trimesh): no CAD rebuild.
"""

from __future__ import annotations

import base64
import datetime
import html
import importlib.util
import json
import sys
from pathlib import Path

from ringcli.alloys import DENSITY, alloy_from_metal


def _load_spec(gen_path: Path) -> dict:
    spec = importlib.util.spec_from_file_location("gen_mod", gen_path)
    mod = importlib.util.module_from_spec(spec)
    sys.path.insert(0, str(gen_path.parent))
    spec.loader.exec_module(mod)
    return mod.SPEC


def write_workorder(gen_path: str, outdir: str) -> str:
    gen = Path(gen_path)
    outdir = Path(outdir)
    spec = _load_spec(gen)
    name = spec["name"]
    from ringcli.templates import head_dims
    hd = head_dims(spec)

    import trimesh
    cast = trimesh.load(outdir / f"{name}-cast.stl")
    vol = cast.volume
    prints = sorted(outdir.glob(f"{name}-print*.stl"))
    print_file = prints[-1].name if prints else "(print STL missing)"
    print_vol = trimesh.load(prints[-1]).volume if prints else 0.0
    shrink = float(spec.get("casting", {}).get("shrink_scale", 1.0))
    alloy = alloy_from_metal(spec.get("metal", "white"), spec.get("casting"))
    stone_mm = spec.get("stone_mm")
    production = bool(spec.get("production")) or stone_mm is not None
    st = hd["style"]

    png = outdir / f"{name}.png"
    img = ""
    if png.exists():
        b64 = base64.b64encode(png.read_bytes()).decode()
        img = f'<img src="data:image/png;base64,{b64}" style="max-width:420px">'

    rows = "".join(
        f"<tr{' class=hl' if a == alloy else ''}><td>{a}</td>"
        f"<td>{vol * d:.2f} g</td><td>{print_vol * d:.2f} g</td></tr>"
        for a, d in DENSITY.items())

    if stone_mm:
        stone_desc = (f"{stone_mm['width']} × {stone_mm['length']} × "
                      f"{stone_mm.get('depth', '?')} mm (from cert "
                      f"{html.escape(str(spec.get('stone', {}).get('cert', 'n/a')))})")
        stone_warn = ""
    else:
        stone_desc = (f"{hd['stone_w']:.2f} × {hd['stone_l']:.2f} mm")
        stone_warn = ('<p class="warn">ESTIMATED from carat: measure the '
                      'physical stone before setting.</p>')

    if shrink != 1.0:
        shrink_box = (f'<p class="warn">Print file is PRE-SCALED ×{shrink:.4f} '
                      f'for casting shrinkage: slicer scale MUST stay 100%.</p>')
    else:
        shrink_box = ('<p class="warn">Print file is at NOMINAL size: apply '
                      'your calibrated shrink % in the slicer '
                      '(run <code>ringcli calibrate</code> to dial it in).</p>')

    arch = hd["arch"]
    if arch == "bezel":
        setting = (f"Bezel: lip {st.get('bezel_lip_mm', st['bezel_lip'])} mm above "
                   "girdle, clearance built in: burnish over after seating.")
    else:
        setting = (f"Prongs: tips extend ~{st.get('prong_overlength_mm', 1.2)} mm "
                   "above the crown. Cut the bearing at girdle height (~1/2 prong "
                   "depth), seat the stone, bend and shape tips. Bearing "
                   f"clearance {st.get('seat_clearance_mm', 0.08)} mm per side is "
                   "built into the seat: do not open further.")
    if arch in ("halo", "pave"):
        setting += (" Halo/pavé melee are DECORATIVE in CAD: bench-set with "
                    "calibrated melee; the azure holes are the drill guides.")

    now = datetime.date.today().isoformat()
    doc = f"""<!doctype html><meta charset="utf-8">
<title>Work order: {html.escape(name)}</title>
<style>
body{{font:14px/1.5 -apple-system,sans-serif;max-width:820px;margin:2em auto;padding:0 1em}}
h1{{font-size:1.4em}} h2{{font-size:1.05em;margin-top:1.6em;border-bottom:1px solid #ccc}}
table{{border-collapse:collapse}} td,th{{border:1px solid #ccc;padding:4px 10px}}
.hl{{background:#fdf3d0;font-weight:600}}
.warn{{background:#fde8e8;border:1px solid #e0a0a0;padding:8px 12px;border-radius:6px}}
ul.qc{{list-style:none;padding:0}} ul.qc li::before{{content:"☐ "}}
code{{background:#eee;padding:1px 5px;border-radius:4px}}
</style>
<h1>Work order: {html.escape(name)} <small>({now})</small></h1>
{img}
<h2>Ring</h2>
<p>US size {spec.get('size')}: inner Ø {2 * hd['inner_r']:.2f} mm ·
band {st['band_width']:.2f} × {st['band_thickness']:.2f} mm ·
head top {hd['girdle_z'] + hd['crown_h']:.1f} mm from center ·
{'PRODUCTION build' if production else 'CONCEPT build (not for production)'}</p>
<h2>Stone</h2>
<p>{spec.get('cut')}: {stone_desc}, {spec.get('carat')} ct nominal</p>
{stone_warn}
<h2>Metal</h2>
<p>Cast body {vol:.1f} mm³; with sprue/stems {print_vol:.1f} mm³.</p>
<table><tr><th>alloy</th><th>ring weight</th><th>with sprues</th></tr>{rows}</table>
<p><small>Densities are nominal: confirm with your caster.</small></p>
<h2>Print &amp; cast</h2>
<p>Print <code>{html.escape(print_file)}</code> in castable resin, upright as
exported (sprue and stems land on the plate). Add fine slicer supports for any
overhangs the stems don't cover. Standard burnout for your resin; our sprue is
the casting feed.</p>
{shrink_box}
<h2>Setting</h2>
<p>{setting}</p>
<h2>QC checklist</h2>
<ul class="qc">
<li>Seat coupon printed; REAL stone drops to girdle line, sits level</li>
<li>Print ID / band dims spot-checked with calipers</li>
<li>Cast ring ID within ±0.05 mm of size {spec.get('size')}</li>
<li>No porosity at prong bases / bezel lip</li>
<li>Prongs full length after cleanup</li>
<li>Stone tight and level after setting</li>
<li>Final weight recorded vs estimate</li>
</ul>
<h2>Spec</h2>
<pre>{html.escape(json.dumps(spec, indent=2))}</pre>
"""
    out = outdir / f"{name}-workorder.html"
    out.write_text(doc)
    return str(out)


if __name__ == "__main__":
    print(write_workorder(sys.argv[1], sys.argv[2]))
