#!/usr/bin/env python3
"""
Built Different — trade-reference HTML generator.
Markdown is the SOURCE OF TRUTH (agents read it). This renders the human HTML view FROM it,
so the two never drift: edit the .md, re-run this, the .html rebuilds.

    python3 build.py HVAC-Mechanic.md       # one trade
    python3 build.py                          # all *.md in this folder (skips README/build)

Pure stdlib. No deps. Images referenced by relative path (kept out of the HTML so it stays light).
"""
import sys, os, re, glob, html

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

def inline(s):
    """Minimal markdown inline -> HTML: links, bold, code, warn glyph."""
    s = html.escape(s, quote=False)
    s = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href="\2" target="_blank" rel="noopener">\1</a>', s)
    s = re.sub(r'\*\*([^*]+)\*\*', r'<strong>\1</strong>', s)
    s = re.sub(r'`([^`]+)`', r'<code>\1</code>', s)
    s = s.replace('⚠', '<span class="warn">⚠</span>').replace('⭐', '★')
    return s

def parse_frontmatter(text):
    fm = {}
    m = re.match(r'^---\n(.*?)\n---\n', text, re.S)
    body = text
    if m:
        for line in m.group(1).splitlines():
            if ':' in line:
                k, v = line.split(':', 1)
                fm[k.strip()] = v.strip()
        body = text[m.end():]
    return fm, body

def split_sections(body):
    """Return list of (title, content) for each `## ` section."""
    parts = re.split(r'^## (.+)$', body, flags=re.M)
    # parts[0] is preamble (title + L0 + intro); then alternating title, content
    secs = []
    for i in range(1, len(parts), 2):
        secs.append((parts[i].strip(), parts[i+1]))
    return parts[0], secs

def parse_tools(content):
    tools = []
    blocks = re.split(r'^### (.+)$', content, flags=re.M)
    for i in range(1, len(blocks), 2):
        name = blocks[i].strip()
        block = blocks[i+1]
        hero = '⭐' in name or 'hero' in name.lower()
        name = name.replace('⭐ hero', '').replace('⭐', '').strip().rstrip(' .')
        name = re.sub(r'^\d+\.\s*', '', name)
        img = ''
        mimg = re.search(r'!\[[^\]]*\]\(([^)]*)\)', block)
        if mimg: img = mimg.group(1).strip()
        fields = []
        for fm in re.finditer(r'^- \*\*([^:*]+):\*\*\s*(.+)$', block, flags=re.M):
            fields.append((fm.group(1).strip(), fm.group(2).strip()))
        tools.append({'name': name, 'hero': hero, 'img': img, 'fields': fields})
    return tools

def render_bullets(content):
    """Render a section's '- ' bullets (and '> ' notes, plain paras) to HTML."""
    out = []
    for raw in content.splitlines():
        line = raw.rstrip()
        if not line.strip():
            continue
        if line.startswith('> '):
            out.append(f'<p class="note">{inline(line[2:])}</p>')
        elif re.match(r'^\d+\.\s', line):
            out.append(f'<li>{inline(re.sub(r"^\d+\.\s","",line))}</li>')
        elif line.startswith('- '):
            out.append(f'<li>{inline(line[2:])}</li>')
        elif line.startswith('*') and line.endswith('*'):
            out.append(f'<p class="hint">{inline(line.strip("*"))}</p>')
        elif line.startswith('### '):
            out.append(f'<h4>{inline(line[4:])}</h4>')
        else:
            out.append(f'<p>{inline(line)}</p>')
    # wrap consecutive <li> in <ul>
    htmlout, inlist = [], False
    for el in out:
        if el.startswith('<li>'):
            if not inlist: htmlout.append('<ul>'); inlist = True
            htmlout.append(el)
        else:
            if inlist: htmlout.append('</ul>'); inlist = False
            htmlout.append(el)
    if inlist: htmlout.append('</ul>')
    return '\n'.join(htmlout)

CSS = """
:root{--bg:#0a1a2f;--panel:#10243d;--panel2:#0d1d31;--ink:#e8eef5;--mut:#8aa3bf;--line:#1d3a5c;--gold:#f2b138;--accent:#3fa7ff;--warn:#ff7a59;}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.5 -apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif}
.wrap{max-width:1180px;margin:0 auto;padding:28px 22px 80px}
a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}
code{background:#0006;padding:1px 5px;border-radius:4px;font-size:.9em}
.warn{color:var(--warn);font-weight:700}
header.hero{background:linear-gradient(135deg,#10243d,#0d1d31);border:1px solid #1d3a5c;border-radius:14px;padding:22px 24px;margin-bottom:18px}
header.hero h1{margin:0 0 6px;font-size:26px;letter-spacing:.3px}
header.hero .meta{color:var(--mut);font-size:13.5px}
.badge{display:inline-block;background:#163a2a;color:#7fe0a8;border:1px solid #2a6b4a;border-radius:999px;padding:2px 10px;font-size:12px;margin-left:6px}
.charcard{background:var(--panel);border:1px solid #1d3a5c;border-left:4px solid var(--gold);border-radius:12px;padding:16px 18px;margin:14px 0 26px}
.charcard b{color:var(--gold)}
h2.sec{font-size:18px;margin:30px 0 12px;padding-bottom:8px;border-bottom:1px solid #1d3a5c}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px}
.tool{background:var(--panel);border:1px solid #1d3a5c;border-radius:12px;overflow:hidden;display:flex;flex-direction:column}
.tool.hero{border-color:var(--gold);box-shadow:0 0 0 1px var(--gold) inset}
.tool .imgwrap{height:220px;background:#fff;display:flex;align-items:center;justify-content:center;overflow:hidden;cursor:zoom-in}
.tool .imgwrap img{max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;display:block}
.tool .noimg{color:var(--mut);font-size:13px;text-align:center;padding:0 14px;cursor:default}
/* lightbox — pure CSS/JS, static-file safe */
#lb{position:fixed;inset:0;background:rgba(4,10,20,.92);display:none;align-items:center;justify-content:center;z-index:99;cursor:zoom-out;padding:30px}
#lb.on{display:flex}
#lb img{max-width:94vw;max-height:92vh;object-fit:contain;border-radius:8px;box-shadow:0 10px 60px #000}
#lb .cap{position:fixed;bottom:18px;left:0;right:0;text-align:center;color:#cfe0f1;font-size:14px}
.tool .body{padding:13px 15px}
.tool h3{margin:0 0 8px;font-size:15.5px}
.tool h3 .star{color:var(--gold)}
.tool .f{margin:6px 0;font-size:13.5px;color:#d4e0ee}
.tool .f b{color:var(--accent);font-weight:600}
ul{margin:6px 0 6px 2px;padding-left:18px}li{margin:4px 0}
.note{background:#0d2336;border-left:3px solid var(--accent);padding:8px 12px;border-radius:6px;color:#cfe0f1;font-size:13.5px}
.hint{color:var(--mut);font-size:13px;font-style:italic}
.panel{background:var(--panel2);border:1px solid #1d3a5c;border-radius:12px;padding:14px 18px}
.comments{background:#161108;border:1px solid #3a2e12}
.comments h4{color:var(--gold);margin:10px 0 4px}
footer{margin-top:40px;color:var(--mut);font-size:12.5px;border-top:1px solid #1d3a5c;padding-top:14px}
"""

def build(mdpath):
    text = open(mdpath).read()
    fm, body = parse_frontmatter(text)
    preamble, secs = split_sections(body)
    trade = fm.get('trade', os.path.basename(mdpath))
    out = [f'<!doctype html><html lang="en"><head><meta charset="utf-8">',
           f'<meta name="viewport" content="width=device-width,initial-scale=1">',
           f'<title>{html.escape(trade)} — Built Different reference</title>',
           f'<style>{CSS}</style></head><body><div class="wrap">']
    # header
    special = '<span class="badge">★ SPECIAL CARD</span>' if fm.get('special_card','').lower()=='true' else ''
    out.append(f'<header class="hero"><h1>{html.escape(trade)} {special}</h1>'
               f'<div class="meta">{html.escape(fm.get("trade_full",""))} &nbsp;·&nbsp; '
               f'Wages {html.escape(fm.get("wages","?"))} &nbsp;·&nbsp; {html.escape(fm.get("training","?"))} '
               f'&nbsp;·&nbsp; Outlook {html.escape(fm.get("outlook","?"))}</div></header>')
    if fm.get('status'):
        out.append(f'<p class="note">{inline(fm["status"])}</p>')
    # sections
    for title, content in secs:
        if title.lower().startswith('tool'):
            out.append(f'<h2 class="sec">{html.escape(title)}</h2>')
            intro = content.split('###')[0].strip()
            if intro: out.append(render_bullets(intro))
            out.append('<div class="grid">')
            for t in parse_tools(content):
                cls = 'tool hero' if t['hero'] else 'tool'
                if t['img'] and 'no-image' not in t['img']:
                    imgsrc = html.escape(t['img'])
                    imghtml = (f'<div class="imgwrap" onclick="zoom(this)"><img src="{imgsrc}" '
                               f'data-cap="{html.escape(t["name"])}" alt="{html.escape(t["name"])}" loading="lazy"></div>')
                else:
                    imghtml = '<div class="imgwrap"><div class="noimg">no reference image yet — see source / generate from description</div></div>'
                star = ' <span class="star">★ hero</span>' if t['hero'] else ''
                fields = ''.join(f'<div class="f"><b>{html.escape(k)}:</b> {inline(v)}</div>' for k,v in t['fields'])
                out.append(f'<div class="{cls}">{imghtml}<div class="body"><h3>{html.escape(t["name"])}{star}</h3>{fields}</div></div>')
            out.append('</div>')
        elif title.lower() == 'comments':
            out.append(f'<h2 class="sec">{html.escape(title)}</h2>')
            out.append(f'<div class="panel comments">{render_bullets(content)}</div>')
        elif title.lower() == 'connections':
            continue  # vault-graph only; skip in HTML
        else:
            out.append(f'<h2 class="sec">{html.escape(title)}</h2>')
            wrap = 'panel' if title.lower().startswith(('ppe','authenticity','brief','character')) else ''
            inner = render_bullets(content)
            out.append(f'<div class="{wrap}">{inner}</div>' if wrap else inner)
    out.append(f'<footer>Generated from <code>{html.escape(os.path.basename(mdpath))}</code> by build.py — '
               f'do NOT edit this HTML directly; edit the markdown and re-run. '
               f'Markdown is the agent-facing source of truth.</footer>')
    out.append('<div id="lb" onclick="this.classList.remove(\'on\')"><img id="lbimg" src=""><div class="cap" id="lbcap"></div></div>')
    out.append('<script>function zoom(w){var i=w.querySelector("img");if(!i)return;'
               'document.getElementById("lbimg").src=i.src;'
               'document.getElementById("lbcap").textContent=i.dataset.cap||"";'
               'document.getElementById("lb").classList.add("on");}'
               'document.addEventListener("keydown",function(e){if(e.key=="Escape")document.getElementById("lb").classList.remove("on");});</script>')
    out.append('</div></body></html>')
    htmlpath = os.path.join(HERE, fm.get('generated_html') or (os.path.splitext(os.path.basename(mdpath))[0] + '.html'))
    open(htmlpath, 'w').write('\n'.join(out))
    return htmlpath

if __name__ == '__main__':
    args = sys.argv[1:]
    if not args:
        args = [f for f in glob.glob(os.path.join(HERE,'*.md'))
                if os.path.basename(f) not in ('README.md',)]
    for a in args:
        p = a if os.path.isabs(a) else os.path.join(HERE, a)
        print('built ->', build(p))
