#!/usr/bin/env python3
"""在项目文件夹根目录生成 index.html（可浏览、可下载的目录页）和一个全量 zip。
用法：python3 生成下载页.py   （在 NobadyBox项目 文件夹里运行）"""
import os, sys, html, zipfile, datetime, urllib.parse

ROOT = os.path.dirname(os.path.abspath(__file__))
DESC = {
    '01_官网': '官网源码和部署版（静态站）',
    '02_硬件方案': '硬件方案 v13–v23 的拆解图和演示页（HTML，双击就能打开），历史版本和源文件在 历史版本/',
    '03_拓竹打印文件': 'v23 的 3 个 STL 打印文件、预览页和打印说明；v13 旧版留作参考',
    '04_众筹页开发': '众筹页需求',
    '05_对话记录': '对话记录',
    '06_截图': '官网和硬件方案截图',
    '09_零件与寿命核对': '零件可买性和寿命核对表',
    '10_打样与量产方案': 'v23 打样、量产方式、零件清单、成本和耐用性（Markdown）',
}
KEY = [
    ('03_拓竹打印文件/v23/预览.html', '3D 打印件预览（可旋转）'),
    ('03_拓竹打印文件/v23/打印说明.md', '打印参数、孔位、配合'),
    ('02_硬件方案/硬件方案_v23_通用机型演示.html', 'v23 产品演示'),
    ('02_硬件方案/硬件方案_v23_通用机型拆解图.html', 'v23 逐级拆解图'),
    ('10_打样与量产方案/NobadyBox v23 打样与量产方案.md', '打样与量产方案'),
    ('02_硬件方案/历史版本/版本记录.md', '版本记录 v13–v23'),
    ('07_采购清单.html', '采购清单（v13 时期，待更新）'),
    ('08_组装手册.html', '组装手册（v13 时期，待更新）'),
    ('00_先看我.md', '文件夹说明'),
    ('01_官网/部署版/index.html', '官网'),
]
SKIP_DIRS = {'.git', '.notes', 'node_modules', '__pycache__', 'uploads'}
TODAY = datetime.date.today().isoformat()
ZIP = f'NobadyBox项目_全部文件_{TODAY}.zip'

def human(n):
    for u in ('B', 'KB', 'MB', 'GB'):
        if n < 1024: return f'{n:.0f} {u}' if u == 'B' else f'{n:.1f} {u}'
        n /= 1024
    return f'{n:.1f} TB'

def walk():
    out = {}
    for dp, dns, fns in os.walk(ROOT):
        dns[:] = sorted(d for d in dns if d not in SKIP_DIRS and not d.startswith('.'))
        rel = os.path.relpath(dp, ROOT)
        files = []
        for f in sorted(fns):
            if f.startswith('.') or f == 'index.html' and rel == '.' or f.endswith('.zip') and rel == '.': continue
            p = os.path.join(dp, f); files.append((f, os.path.getsize(p)))
        out[rel] = files
    return out

def make_zip(tree):
    for f in os.listdir(ROOT):
        if f.startswith('NobadyBox项目_全部文件_') and f.endswith('.zip') and f != ZIP: os.remove(os.path.join(ROOT, f))
    with zipfile.ZipFile(os.path.join(ROOT, ZIP), 'w', zipfile.ZIP_DEFLATED, compresslevel=6) as z:
        for rel, files in tree.items():
            for f, _ in files:
                p = os.path.join(ROOT, rel, f) if rel != '.' else os.path.join(ROOT, f)
                z.write(p, os.path.join('NobadyBox项目', rel, f) if rel != '.' else os.path.join('NobadyBox项目', f))
    return os.path.getsize(os.path.join(ROOT, ZIP))

def link(rel):
    return urllib.parse.quote(rel)

def build(tree, zsize):
    total = sum(s for fs in tree.values() for _, s in fs); nfiles = sum(len(fs) for fs in tree.values())
    css = """
:root{--bg:#f6f7f9;--panel:#fff;--ink:#1c1f23;--muted:#6b7280;--line:#e5e7eb;--accent:#d1621a;color-scheme:light dark}
@media (prefers-color-scheme:dark){:root:not([data-theme=light]){--bg:#15181c;--panel:#1f2328;--ink:#e8eaed;--muted:#9aa3ad;--line:#2d333b}}
:root[data-theme=dark]{--bg:#15181c;--panel:#1f2328;--ink:#e8eaed;--muted:#9aa3ad;--line:#2d333b}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.55 -apple-system,"PingFang SC","Noto Sans SC",sans-serif}
.wrap{max-width:980px;margin:0 auto;padding:24px 16px 60px}h1{font-size:24px;margin:0 0 6px}.lead{color:var(--muted);margin:0 0 18px}
a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}
.zip{display:inline-block;background:var(--accent);color:#fff;padding:10px 16px;border-radius:10px;font-weight:600;margin:4px 0 22px}.zip:hover{text-decoration:none;opacity:.92}
.key{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px;margin-bottom:26px}
.key a{display:block;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 14px;color:var(--ink)}.key a b{display:block;color:var(--accent);font-size:14px;margin-bottom:2px}.key a span{font-size:12px;color:var(--muted);word-break:break-all}
details{background:var(--panel);border:1px solid var(--line);border-radius:12px;margin:10px 0;padding:0 14px}summary{cursor:pointer;padding:12px 0;font-weight:600}summary small{font-weight:400;color:var(--muted);margin-left:8px}
table{width:100%;border-collapse:collapse;margin:0 0 12px;font-size:14px}td{padding:6px 4px;border-top:1px solid var(--line);vertical-align:top}td.n{text-align:right;color:var(--muted);white-space:nowrap;width:90px}td.d{color:var(--muted);font-size:12px;width:36%}
.foot{color:var(--muted);font-size:12px;margin-top:24px}
"""
    h = [f'<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>NobadyBox 项目文件</title><style>{css}</style></head><body><div class="wrap">']
    h.append('<h1>NobadyBox 项目文件</h1>')
    h.append(f'<p class="lead">AI 手机仓的全部设计资料：硬件方案、3D 打印文件、源文件、官网。共 {nfiles} 个文件，{human(total)}。更新于 {TODAY}。</p>')
    h.append(f'<a class="zip" href="{link(ZIP)}" download>下载全部文件（zip，{human(zsize)}）</a>')
    h.append('<div class="key">')
    for rel, title in KEY:
        if os.path.exists(os.path.join(ROOT, rel)): h.append(f'<a href="{link(rel)}"><b>{html.escape(title)}</b><span>{html.escape(rel)}</span></a>')
    h.append('</div>')
    order = sorted(tree.keys(), key=lambda r: (r == '.', r))
    tops = [r for r in order if r != '.' and '/' not in r]
    for top in tops:
        subs = [r for r in order if r == top or r.startswith(top + '/')]
        n = sum(len(tree[r]) for r in subs); sz = sum(s for r in subs for _, s in tree[r])
        h.append(f'<details{" open" if top in ("02_硬件方案","03_拓竹打印文件","10_打样与量产方案") else ""}><summary>{html.escape(top)}<small>{html.escape(DESC.get(top, ""))} · {n} 个文件 · {human(sz)}</small></summary>')
        for r in subs:
            if not tree[r]: continue
            if r != top: h.append(f'<div style="margin:6px 0 2px;color:var(--muted);font-size:13px">{html.escape(r[len(top)+1:])}/</div>')
            h.append('<table>')
            for f, s in tree[r]:
                h.append(f'<tr><td><a href="{link(os.path.join(r, f))}">{html.escape(f)}</a></td><td class="n">{human(s)}</td></tr>')
            h.append('</table>')
        h.append('</details>')
    if tree.get('.'):
        h.append('<details><summary>根目录<small>' + str(len(tree['.'])) + ' 个文件</small></summary><table>')
        for f, s in tree['.']: h.append(f'<tr><td><a href="{link(f)}">{html.escape(f)}</a></td><td class="n">{human(s)}</td></tr>')
        h.append('</table></details>')
    h.append('<p class="foot">这个页面由 生成下载页.py 生成。重新运行它会刷新列表和 zip。</p></div></body></html>')
    open(os.path.join(ROOT, 'index.html'), 'w', encoding='utf-8').write('\n'.join(h))

if __name__ == '__main__':
    tree = walk()
    zsize = make_zip(tree)
    build(tree, zsize)
    print('ok', ZIP, human(zsize))
