#!/usr/bin/env python3
"""Generate client-update-flow as-is module.svg via fireworks-tech-graph rules."""
import os

W, H = 1280, 720
NW, NH = 180, 56
CLW = 220

L = []
def emit(s): L.append(s)

emit('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 %d %d" font-family="Segoe UI, Helvetica, Arial, sans-serif">' % (W, H))
emit('  <defs>')
emit('    <marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="10" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#475569"/></marker>')
emit('    <marker id="arrR" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="10" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#c62828"/></marker>')
emit('    <style>')
emit('      .title { font-size:18px; font-weight:700; fill:#7f1d1d; }')
emit('      .panel-lbl { font-size:13px; font-weight:700; }')
emit('      .node-lbl { font-size:13px; font-weight:600; fill:#0f172a; text-anchor:middle; }')
emit('      .node-sub { font-size:10.5px; fill:#475569; text-anchor:middle; }')
emit('      .edge-lbl { font-size:10.5px; fill:#475569; }')
emit('      .edge-lbl-r { font-size:10.5px; fill:#c62828; }')
emit('      .legend { font-size:11px; fill:#475569; }')
emit('    </style>')
emit('  </defs>')

emit('  <rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, H))
emit('  <text x="24" y="32" class="title">as-is &#183; module &#183; harness-toolkit 客户端同步链路</text>')

# Panel containers
TK_X, TK_Y, TK_W, TK_H = 40, 70, 560, 620
emit('  <rect x="%d" y="%d" width="%d" height="%d" fill="#f0f9ff" stroke="#1976d2" stroke-width="1.4" stroke-dasharray="6 4" rx="8"/>' % (TK_X, TK_Y, TK_W, TK_H))
emit('  <text x="%d" y="%d" class="panel-lbl" fill="#1976d2">harness-rd-toolkit (source)</text>' % (TK_X+12, TK_Y+22))

CL_X, CL_Y, CL_W, CL_H = 700, 70, 540, 620
emit('  <rect x="%d" y="%d" width="%d" height="%d" fill="#fffaf0" stroke="#f57c00" stroke-width="1.4" stroke-dasharray="6 4" rx="8"/>' % (CL_X, CL_Y, CL_W, CL_H))
emit('  <text x="%d" y="%d" class="panel-lbl" fill="#f57c00">客户端项目 (targetDir)</text>' % (CL_X+12, CL_Y+22))

# Node positions (snap x to 120-ish grid, y to 80-ish)
cli_x, cli_y = 80, 130
ver_x, ver_y = 80, 280
ini_x, ini_y = 340, 130
upd_x, upd_y = 340, 280

claude_x, claude_y = 760, 130
set_x, set_y = 760, 220
ctx_x, ctx_y = 760, 310
cfg_x, cfg_y = 760, 400
stamp_x, stamp_y = 760, 490
stamp_w, stamp_h = 220, 80

finding_x, finding_y = 80, 500
finding_w, finding_h = 460, 170

def R(x,y,w,h): return (x+w, y+h//2)
def LE(x,y,w,h): return (x, y+h//2)
def T(x,y,w,h): return (x+w//2, y)
def B(x,y,w,h): return (x+w//2, y+h)

# ===== EDGES FIRST =====
sx,sy = R(cli_x,cli_y,NW,NH); ex,ey = LE(ini_x,ini_y,NW,NH)
emit('  <path d="M %d %d L %d %d" stroke="#475569" stroke-width="1.5" fill="none" marker-end="url(#arr)"/>' % (sx,sy,ex,ey))
emit('  <text x="%d" y="%d" class="edge-lbl">init</text>' % ((sx+ex)//2-12, sy-8))

sx,sy = R(cli_x,cli_y+20,NW,NH); ex,ey = LE(upd_x,upd_y,NW,NH)
mx = (sx+ex)//2
emit('  <path d="M %d %d L %d %d L %d %d L %d %d" stroke="#475569" stroke-width="1.5" fill="none" marker-end="url(#arr)"/>' % (sx,sy,mx,sy,mx,ey,ex,ey))
emit('  <text x="%d" y="%d" class="edge-lbl">update</text>' % (mx+8, (sy+ey)//2))

sx,sy = B(cli_x,cli_y,NW,NH); ex,ey = T(ver_x,ver_y,NW,NH)
emit('  <path d="M %d %d L %d %d" stroke="#475569" stroke-width="1.5" fill="none" marker-end="url(#arr)"/>' % (sx,sy,ex,ey))
emit('  <text x="%d" y="%d" class="edge-lbl">version</text>' % (sx+8, (sy+ey)//2))

sx,sy = T(upd_x,upd_y,NW,NH); ex,ey = B(ini_x,ini_y,NW,NH)
emit('  <path d="M %d %d L %d %d" stroke="#94a3b8" stroke-width="1.3" fill="none" stroke-dasharray="4 3" marker-end="url(#arr)"/>' % (sx,sy,ex,ey))
emit('  <text x="%d" y="%d" class="edge-lbl">复用 init 符号</text>' % (sx+8, (sy+ey)//2))

# update -> cfg (hash sync) green-ish main
sx,sy = R(upd_x,upd_y,NW,NH); ex,ey = LE(cfg_x,cfg_y,CLW,NH)
mx = (sx+ex)//2
emit('  <path d="M %d %d L %d %d L %d %d L %d %d" stroke="#475569" stroke-width="1.5" fill="none" marker-end="url(#arr)"/>' % (sx,sy,mx,sy,mx,ey,ex,ey))
emit('  <text x="%d" y="%d" class="edge-lbl">hash 同步</text>' % (mx+8, sy-6))

# update -> settings.* (union merge, dashed soft)
sx,sy = R(upd_x,upd_y+15,NW,NH); ex,ey = LE(set_x,set_y,CLW,NH)
mx = (sx+ex)//2
emit('  <path d="M %d %d L %d %d L %d %d L %d %d" stroke="#94a3b8" stroke-width="1.3" fill="none" stroke-dasharray="4 3" marker-end="url(#arr)"/>' % (sx,sy,mx,sy,mx,ey,ex,ey))
emit('  <text x="%d" y="%d" class="edge-lbl">字段 union merge</text>' % (mx+8, sy+18))

# update -> CLAUDE.md (red dashed, "完全不管")
sx,sy = R(upd_x,upd_y-15,NW,NH); ex,ey = LE(claude_x,claude_y,CLW,NH)
mx = (sx+ex)//2
emit('  <path d="M %d %d L %d %d L %d %d L %d %d" stroke="#c62828" stroke-width="1.5" fill="none" stroke-dasharray="5 3" marker-end="url(#arrR)"/>' % (sx,sy,mx,sy,mx,ey,ex,ey))
emit('  <text x="%d" y="%d" class="edge-lbl-r">完全不管 (不对称守护)</text>' % (mx+8, ey-6))

# update -> CONTEXT.md (hash sync)
sx,sy = R(upd_x,upd_y+30,NW,NH); ex,ey = LE(ctx_x,ctx_y,CLW,NH)
mx = (sx+ex)//2
emit('  <path d="M %d %d L %d %d L %d %d L %d %d" stroke="#475569" stroke-width="1.5" fill="none" marker-end="url(#arr)"/>' % (sx,sy,mx,sy,mx,ey,ex,ey))
emit('  <text x="%d" y="%d" class="edge-lbl">hash 同步</text>' % (mx+8, ey+18))

# update -> stamp (red, "不读 / 不写", orthogonal down-right)
mx_upd = upd_x + NW//2
sy = upd_y + NH
mx_stamp = stamp_x + stamp_w//2
ey = stamp_y
GY = (sy + ey) // 2
emit('  <path d="M %d %d L %d %d L %d %d L %d %d" stroke="#c62828" stroke-width="1.5" fill="none" stroke-dasharray="5 3" marker-end="url(#arrR)"/>' % (mx_upd, sy, mx_upd, GY, mx_stamp, GY, mx_stamp, ey))
emit('  <text x="%d" y="%d" class="edge-lbl-r">不读 / 不写</text>' % ((mx_upd+mx_stamp)//2, GY-6))

# ===== NODES =====
def node(x, y, w, h, fill, stroke, label, sub=None, label_fill='#0f172a', sub_fill='#475569', stroke_w=1.6, dash=None):
    d = ' stroke-dasharray="%s"' % dash if dash else ''
    emit('  <rect x="%d" y="%d" width="%d" height="%d" rx="8" fill="%s" stroke="%s" stroke-width="%s"%s/>' % (x, y, w, h, fill, stroke, stroke_w, d))
    cy = y + h//2
    if sub:
        emit('  <text x="%d" y="%d" class="node-lbl" fill="%s">%s</text>' % (x+w//2, cy-4, label_fill, label))
        emit('  <text x="%d" y="%d" class="node-sub" fill="%s">%s</text>' % (x+w//2, cy+12, sub_fill, sub))
    else:
        emit('  <text x="%d" y="%d" class="node-lbl" fill="%s">%s</text>' % (x+w//2, cy+5, label_fill, label))

node(cli_x, cli_y, NW, NH, '#e3f2fd', '#1976d2', 'cli.ts', 'command dispatch')
node(ver_x, ver_y, NW, NH, '#e3f2fd', '#1976d2', 'version.ts', 'VERSION 常量 only')
node(ini_x, ini_y, NW, NH, '#e3f2fd', '#1976d2', 'commands/init.ts', 'scaffoldDir / install*')
node(upd_x, upd_y, NW, NH, '#e3f2fd', '#1976d2', 'commands/update.ts', 'runUpdate / syncOneFile')

node(claude_x, claude_y, CLW, NH, '#fff3e0', '#f57c00', 'CLAUDE.md', 'sentinel(init) ; update 不接')
node(set_x, set_y, CLW, NH, '#fff3e0', '#f57c00', '.claude/settings.json', 'hooks 字段 union merge')
node(ctx_x, ctx_y, CLW, NH, '#fff3e0', '#f57c00', 'CONTEXT.md', 'hash 不同 -> skipped-conflict')
node(cfg_x, cfg_y, CLW, NH, '#fff3e0', '#f57c00', 'pipeline*.yaml / gates / skills', 'hash 同步, 冲突 skipped')

# stamp gap node
emit('  <rect x="%d" y="%d" width="%d" height="%d" rx="8" fill="#ffe0e0" stroke="#c62828" stroke-width="1.8" stroke-dasharray="5 3"/>' % (stamp_x, stamp_y, stamp_w, stamp_h))
emit('  <text x="%d" y="%d" class="node-lbl" fill="#c62828">(缺失) 客户端无版本戳记</text>' % (stamp_x+stamp_w//2, stamp_y+24))
emit('  <text x="%d" y="%d" class="node-sub" fill="#c62828">无 .harness-toolkit-version 文件</text>' % (stamp_x+stamp_w//2, stamp_y+42))
emit('  <text x="%d" y="%d" class="node-sub" fill="#c62828">update 无法判断"该不该提示"</text>' % (stamp_x+stamp_w//2, stamp_y+58))

# findings callout
emit('  <rect x="%d" y="%d" width="%d" height="%d" rx="8" fill="#fef2f2" stroke="#c62828" stroke-width="1.6" stroke-dasharray="5 3"/>' % (finding_x, finding_y, finding_w, finding_h))
emit('  <text x="%d" y="%d" class="node-lbl" fill="#c62828">三处与需求冲突的现状缺口</text>' % (finding_x+12, finding_y+24))
emit('  <text x="%d" y="%d" class="node-sub" fill="#c62828">1.  无版本提示路径 (更新提示维度空白)</text>' % (finding_x+12, finding_y+50))
emit('  <text x="%d" y="%d" class="node-sub" fill="#c62828">2.  --force 直接整文件覆盖用户修改</text>' % (finding_x+12, finding_y+74))
emit('  <text x="%d" y="%d" class="node-sub" fill="#c62828">3.  dest 缺失 -> 无条件 created, 删除被恢复</text>' % (finding_x+12, finding_y+98))
emit('  <text x="%d" y="%d" class="node-sub" fill="#c62828">违反"未获认可, 不允许修改或删除客户</text>' % (finding_x+12, finding_y+122))
emit('  <text x="%d" y="%d" class="node-sub" fill="#c62828">已做的配置" 硬约束</text>' % (finding_x+12, finding_y+140))

# legend
LG_Y = H - 56
emit('  <rect x="40" y="%d" width="1200" height="44" fill="#f8fafc" stroke="#cbd5e1" rx="6"/>' % LG_Y)
emit('  <text x="56" y="%d" class="legend" font-weight="700">Legend:</text>' % (LG_Y+26))
emit('  <rect x="120" y="%d" width="18" height="14" rx="3" fill="#e3f2fd" stroke="#1976d2"/>' % (LG_Y+14))
emit('  <text x="146" y="%d" class="legend">toolkit 模块</text>' % (LG_Y+26))
emit('  <rect x="240" y="%d" width="18" height="14" rx="3" fill="#fff3e0" stroke="#f57c00"/>' % (LG_Y+14))
emit('  <text x="266" y="%d" class="legend">客户端配置面</text>' % (LG_Y+26))
emit('  <rect x="360" y="%d" width="18" height="14" rx="3" fill="#ffe0e0" stroke="#c62828" stroke-dasharray="3 2"/>' % (LG_Y+14))
emit('  <text x="386" y="%d" class="legend">与需求冲突/缺口 (红色虚线)</text>' % (LG_Y+26))
emit('  <line x1="620" y1="%d" x2="650" y2="%d" stroke="#475569" stroke-width="1.5"/>' % (LG_Y+22, LG_Y+22))
emit('  <text x="658" y="%d" class="legend">主同步路径</text>' % (LG_Y+26))
emit('  <line x1="760" y1="%d" x2="790" y2="%d" stroke="#94a3b8" stroke-width="1.3" stroke-dasharray="4 3"/>' % (LG_Y+22, LG_Y+22))
emit('  <text x="798" y="%d" class="legend">软引用 / 字段合并</text>' % (LG_Y+26))

emit('</svg>')

p = 'docs/4plus1/delta/client-update-flow/as-is/rendered/module.svg'
with open(p, 'w', encoding='utf-8') as f:
    f.write('\n'.join(L))
print('written lines=%d size=%d' % (len(L), os.path.getsize(p)))
