"""Assemble the spike: site/out/pub/ = progress page + lookup test page + browse/ (index, thumbs, blobs, cards, vendor).
usage: python3 tools/build_site.py "<HH:MM CET>"   then tools/publish.sh"""
import html, json, os, shutil, sys, time
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(ROOT, 'lib'))
from lookup import Library  # noqa: E402

OUT = f'{ROOT}/site/out/pub'
E = html.escape
QUERIES = ['yellow cab', 'police car', 'fire engine', 'ambulance', 'delivery truck', 'race car', 'go-kart', 'tractor', 'fishing boat',
           'rowboat', 'tank', 'jeep', 'helicopter', 'gunship', 'fighter jet', 'bomber', 'spaceship', 'motorcycle', 'bicycle',
           'house', 'castle', 'lighthouse', 'chapel', 'watchtower', 'market hall', 'tent', 'wall with a window', 'roof',
           'wooden crate', 'barrel', 'hay bale', 'well', 'street lamp', 'bench', 'anchor', 'sofa', 'bed', 'shopping trolley',
           'rock', 'tree', 'assault rifle', 'shotgun', 'pistol', 'sword', 'villager', 'football player', 'hair', 'horse',
           'dragon', 'troll', 'bookcase monster', 'brick']


def main(stamp):
    idx = json.load(open(f'{ROOT}/index.json'))
    prog = json.load(open(f'{ROOT}/site/progress.json'))
    lib = Library(idx)
    if os.path.isdir(OUT):
        shutil.rmtree(OUT)
    os.makedirs(OUT)
    b = f'{OUT}/browse'
    shutil.copytree(f'{ROOT}/browse', b, copy_function=os.link, ignore=shutil.ignore_patterns('render.html', 'compare.html', 'placeholder'))
    browse_idx = json.loads(json.dumps(idx))
    for a in browse_idx['assets']:
        a['thumbnail'] = a.get('thumbnail', f"thumbs/{a['glb']}.jpg").replace('site/out/measure/thumbs/', 'thumbs/')
    open(f'{b}/index.json','w').write(json.dumps(browse_idx, separators=(',', ':')))
    shutil.copytree(f'{ROOT}/site/out/measure/thumbs', f'{b}/thumbs', copy_function=os.link)
    for folder in ('media',):
        if os.path.isdir(f'{ROOT}/{folder}'):
            shutil.copytree(f'{ROOT}/{folder}', f'{b}/{folder}', copy_function=os.link)
    for name in ('neighbours.json',):
        if os.path.exists(f'{ROOT}/{name}'): shutil.copy(f'{ROOT}/{name}', b)
    os.makedirs(f'{b}/blobs')
    for a in idx['assets']:  # every version the viewer can switch to, not just the active one
        for g in {a['glb'], *(v['glb'] for v in a.get('vers', []))}:
            os.link(f"{ROOT}/store/blobs/{g}.glb", f'{b}/blobs/{g}.glb')
    shutil.copytree(f'{ROOT}/cards', f'{b}/cards')
    # lookup test
    rows, gaps, total_ms = [], set(), 0
    for q in QUERIES:
        r = lib.search(q, limit=4); total_ms += r['ms']
        gaps.update(r['gaps'])
        hit = ', '.join(E(h['name']) for h in r['hits']) or ('<span class=gap>gap: ' + E(', '.join(r['gaps'])) + '</span>' if r['gaps'] else '<span class=gap>no match</span>')
        how = f"alias → {r['alias']}" if r['alias'] else (f"class → {r['path']}" if r['path'] else 'words')
        rows.append(f"<tr><td><a href='browse/index.html?q={E(q)}'>{E(q)}</a></td><td>{E(how)}</td><td>{r['total']}</td><td>{hit}</td><td>{r['ms']:.2f}</td></tr>")
    found = sum(1 for q in QUERIES if lib.search(q)['total'])
    page(f'{OUT}/lookup.html', 'Lookup test', f"""<h2>Lookup test: {len(QUERIES)} everyday words a player might type</h2>
<p class=lead>{found} of {len(QUERIES)} find something today; the rest are <b>library gaps</b> (the class exists in the tree but holds no model yet).
Gaps never fall back to a lookalike name (the football player called "Tank" is not a tank). Average {total_ms / len(QUERIES):.2f} ms per lookup, plain code, no model call.</p>
<div style='overflow-x:auto'><table><tr><th>Words</th><th>How it was routed</th><th>Matches</th><th>Top results</th><th>ms</th></tr>{''.join(rows)}</table></div>
<h3>Production list from the gaps ({len(gaps)} classes)</h3><p>{E(', '.join(sorted(gaps)))}</p>""", 'lookup', stamp)
    # progress page
    kinds = {k: v.get('count', 0) for k, v in idx['tree'].items()}
    A = idx['assets']
    nflag = sum(1 for a in A if a['in_band'] is False or a['front_suspect'] or not a['game_use'])
    sample = [a for a in A if a['id'].split('/')[1] in ('kenney-taxi', 'harbour-lighthouse', 'harbour-trawler', 'fps-kr7', 'store-sofa-3',
              'forge-kart-standard', 'cast-crowd-fan-f1', 'harbour-wall-window', 'tideglass-tree-ancient-broad', 'store-brute-billig',
              'harbour-chapel', 'harbour-barrel')]
    strip = ''.join(f"<a href='browse/index.html?q={E(a['name'])}'><img src='browse/thumbs/{a['glb']}.jpg' alt='{E(a['name'])}' title='{E(a['name'])}'></a>" for a in sample)
    ph = ''.join(f"<div class='ph {p['state']}'><div class=pid>{p['id']} <span>{p['state']}</span></div><b>{E(p['name'])}</b><ul>{''.join(f'<li>{E(i)}</li>' for i in p['items'])}</ul></div>" for p in prog['phases'])
    result = ''
    for res in prog.get('results') or ([prog['result']] if prog.get('result') else []):
        os.makedirs(f'{OUT}/shots', exist_ok=True)
        for sh in res['shots']:
            shutil.copy(f'{ROOT}/site/{sh}', f'{OUT}/{sh}')
        result += (f"<h3>{E(res['title'])}</h3><div class=strip style='grid-template-columns:repeat({len(res['shots'])},1fr)'>"
                  + ''.join(f"<img src='{sh}' alt=''>" for sh in res['shots']) + "</div><ul>"
                  + ''.join(f'<li>{E(f)}</li>' for f in res['facts']) + f"</ul><p><a href='{E(res['url'])}'>Open</a></p>")
    log = ''.join(f"<li><b>{E(l['t'])}</b> {E(l['text'])}</li>" for l in reversed(prog['log']))
    kind_rows = ''.join(f"<span class=k>{E(k)} <b>{n}</b></span>" for k, n in kinds.items())
    page(f'{OUT}/index.html', 'Progress', f"""<h2>Forge Asset Library: build progress</h2>
<p class=lead>{E(prog['go'])} Started {E(prog['started'])}. Blueprint: <a href="https://dashboard.le-borgne.fr/docs/v.html?d=FORGE-3D-ASSET-LIBRARY-BLUEPRINT-2026-10-07.md">v3 (reviewed twice by Astra, Fable 5.1, Gemini 3.8)</a>.</p>
<div class=strip>{strip}</div>
<div class=nums><div><b>{idx['count']}</b>assets with cards</div><div><b>{len(set(a['id'].split('/')[0] for a in A))}</b>classes filled</div>
<div><b>{nflag}</b>honest flags (size, front, licence)</div><div><b>{sum(a['bytes'] for a in A) / 1e6:.0f} MB</b>of models</div></div>
<p>{kind_rows}</p>
{result}<h3>Phases</h3><div class=phs>{ph}</div>
<h3>Log (newest first)</h3><ul class=log>{log}</ul>""", 'progress', stamp)
    print('site ready', OUT, sum(os.path.getsize(os.path.join(d, f)) for d, _, fs in os.walk(OUT) for f in fs) // 1_000_000, 'MB')


def page(path, title, body, cur, stamp):
    nav = ''.join(f"<a href='{h}'{' aria-current=page' if k == cur else ''}>{t}</a>" for k, h, t in
                  (('progress', 'index.html', 'Progress'), ('browse', 'browse/index.html', 'Browse'), ('houses', 'browse/houses.html', 'Houses from atoms'), ('buildings', 'browse/buildings.html', 'Buildings (3 styles)'), ('settlement', 'browse/settlement.html', 'Villages and towns'), ('landmarks', 'browse/landmarks.html', 'Church and cathedral'), ('lookup', 'lookup.html', 'Lookup test')))
    open(path, 'w').write(f"""<!doctype html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1">
<title>Forge Asset Library: {title}</title><style>
:root{{--line:#e1e4e9;--mut:#5d6573;--acc:#2f5bea;--warn:#b25e00}}*{{box-sizing:border-box}}
body{{margin:0;font:15px/1.55 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:#f4f5f7;color:#16191f}}
header{{position:sticky;top:0;background:#fff;border-bottom:1px solid var(--line);display:flex;gap:16px;align-items:center;padding:10px 20px;flex-wrap:wrap;z-index:2}}
header h1{{font-size:16px;margin:0}}header small{{color:var(--mut)}}nav{{display:flex;gap:4px;margin-left:auto}}
nav a{{color:var(--mut);text-decoration:none;padding:6px 10px;border-radius:7px;font-weight:550;outline:none}}nav a:hover{{background:#eef1f6;color:#16191f}}
nav a:focus-visible{{box-shadow:0 0 0 2px var(--acc)}}nav a[aria-current=page]{{background:#16191f;color:#fff}}
main{{max-width:1100px;margin:auto;padding:20px;overflow-wrap:anywhere}}.ph,.nums div{{min-width:0}}h2{{margin:.2em 0 .3em}}.lead{{color:#333}}a{{color:var(--acc)}}
.strip{{display:grid;grid-template-columns:repeat(6,1fr);gap:8px;margin:16px 0}}.strip img{{width:100%;border-radius:10px;display:block;border:1px solid var(--line)}}
.nums{{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}}.nums div{{background:#fff;border:1px solid var(--line);border-radius:12px;padding:12px;color:var(--mut);font-size:13px}}
.nums b{{display:block;font-size:26px;color:#16191f}}.k{{display:inline-block;background:#fff;border:1px solid var(--line);border-radius:99px;padding:2px 10px;margin:3px;font-size:13px}}
.phs{{display:grid;grid-template-columns:1fr 1fr;gap:12px}}.ph{{background:#fff;border:1px solid var(--line);border-radius:12px;padding:12px 16px}}
.pid{{font-weight:700;color:var(--mut)}}.pid span{{font-size:12px;padding:1px 8px;border-radius:99px;background:#eef1f6;margin-left:6px}}
.ph.done .pid span{{background:#e8f5ee;color:#1e7a46}}.ph.active .pid span{{background:#e7edff;color:var(--acc)}}
.log li{{margin:6px 0}}table{{border-collapse:collapse;width:100%;background:#fff;font-size:14px}}td,th{{padding:6px 8px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}}
.gap{{color:var(--warn);font-weight:600}}
@media (max-width:700px){{.strip{{grid-template-columns:repeat(3,1fr)}}.nums,.phs{{grid-template-columns:1fr 1fr}}table{{font-size:12px}}nav{{margin-left:0}}}}
</style></head><body><header><h1>Forge Asset Library</h1><small>updated {E(stamp)}</small><nav>{nav}</nav></header><main>{body}</main></body></html>""")


if __name__ == '__main__':
    main(sys.argv[1] if len(sys.argv) > 1 else time.strftime('%H:%M'))
