# -*- coding: utf-8 -*-
"""原创离线合成样例；不连接模型、不读取账户。输出位于脚本旁。
已有输入保留，修改输入后可以重跑。失败边界由 --negative 单独演示。
"""
from pathlib import Path
import sys, json, csv, math, io, hashlib, html, base64, re
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
BASE = Path(__file__).resolve().parent
NEG = '--negative' in sys.argv
def write(name, value):
    p=BASE/name; p.parent.mkdir(parents=True,exist_ok=True)
    if isinstance(value,bytes): p.write_bytes(value)
    else: p.write_text(value,encoding='utf-8',newline='')
def seed(name,value):
    if not (BASE/name).exists(): write(name,value)
def jwrite(name,value): write(name,json.dumps(value,ensure_ascii=False,indent=2))
def jseed(name,value):
    if not (BASE/name).exists(): jwrite(name,value)
def load(name): return json.loads((BASE/name).read_text(encoding='utf-8'))
def table(name,heads,rows):
    f=io.StringIO(newline='');w=csv.writer(f);w.writerow(heads);w.writerows(rows);write(name,f.getvalue())
def page(name,title,body):
    write(name,'<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>'+html.escape(title)+'</title><style>body{max-width:1000px;margin:2rem auto;padding:0 1rem;font:17px/1.7 system-ui}table{border-collapse:collapse}td,th{border:1px solid #aaa;padding:.5rem}pre{white-space:pre-wrap;overflow-wrap:anywhere}img,svg{max-width:100%;height:auto}a{color:#125caa}</style><h1>'+html.escape(title)+'</h1>'+body+'</html>')
def emit(data): print(json.dumps(data,ensure_ascii=False))

seed('categories.csv','month,category,value\nJan,A,7\nJan,B,4\nFeb,B,6\nFeb,A,5\nFeb,C,3\n')
jseed('palette.json',{'A':'#1565c0','B':'#b45100','C':'#478000'});palette=load('palette.json');rows=list(csv.DictReader((BASE/'categories.csv').open()))
if NEG:rows.append({'month':'Mar','category':'D','value':'8'})
unknown=sorted({r['category'] for r in rows}-palette.keys())
if unknown:raise ValueError('新类别需显式分配颜色：'+','.join(unknown))
bars=[];mapping=[]
for i,r in enumerate(rows):
    y=45+i*45;color=palette[r['category']];width=int(r['value'])*30;bars.append(f'<text x="8" y="{y+16}">{r["month"]} {r["category"]}</text><rect x="100" y="{y}" width="{width}" height="25" fill="{color}"/><text x="{110+width}" y="{y+18}">{r["value"]}</text>');mapping.append([r['month'],r['category'],color])
write('stable-colors.svg','<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 320"><rect width="480" height="320" fill="white"/><text x="10" y="22">Explicit category palette</text>'+''.join(bars)+'</svg>')
before=[];seen={};cycle=list(palette.values())
for i,r in enumerate(rows):
    month=seen.setdefault(r['month'],[])
    if r['category'] not in month:month.append(r['category'])
    color=cycle[month.index(r['category'])%len(cycle)];y=45+i*45;width=int(r['value'])*30;before.append(f'<text x="8" y="{y+16}">{r["month"]} {r["category"]}</text><rect x="100" y="{y}" width="{width}" height="25" fill="{color}"/>')
write('before-after.svg','<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 320"><rect width="960" height="320" fill="white"/><text x="10" y="22">BEFORE: positional cycle</text>'+''.join(before)+'<g transform="translate(480,0)"><text x="10" y="22">AFTER: category mapping</text>'+''.join(bars)+'</g></svg>');table('color-assignment.csv',['month','category','color'],mapping);emit({'assignment':mapping})
