#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""生成左右对比页 + 同步 md / htmls / BUILT"""
import re, os, json, time
import diffgen as D
import speech_rate as SR

BASE = '/root/media-hub/export/拍摄'
PAGE = os.path.join(BASE, 'compare-v7-edit.html')
LIVE = '/root/media-hub/export/v7-live.md'
HTMLS = '/root/media-hub/export/v7-live-html.json'

secs, to, tn = D.build()
NEW_S = D.load(D.NEW)
OLD_S = D.load(D.OLD)

# 口播字数：扣掉「（画面…）」这种分镜提示行
def shot_of(S):
    k = 0
    for n, (t, paras) in S.items():
        for p in paras:
            if p.startswith('（画面'):
                k += D.han(p)
    return k
BAK = '/tmp/new.md.bak-prev-freeze'
try:
    BAK_S = D.load(BAK)
    ROUND_CHANGED = [n for n in sorted(NEW_S) if BAK_S.get(n, ('', []))[1] != NEW_S[n][1]]
    _bak_han = sum(D.han(p) for _t, _ps in BAK_S.values() for p in _ps)
    prev_ver_spoken = _bak_han - shot_of(BAK_S)
except Exception:
    ROUND_CHANGED = []
    prev_ver_spoken = None

# 这一轮动过的节保留左右对比；其余节已定稿：左边换成右边定稿文字，绿底/红删除线全清掉，左右同文
FROZEN = set(range(1, 16)) - set(ROUND_CHANGED)
PREV_FIXED = set(range(1, 16))
INS_TAG = re.compile(r'</?ins>')
for _s in secs:
    if _s['n'] in FROZEN:
        _s['right'] = [INS_TAG.sub('', h) for h in _s['right']]
        _s['left'] = list(_s['right'])
        _s['ho'] = _s['hn']
        _s['ins'] = _s['dele'] = 0

shot_han = shot_of(NEW_S)
spoken = tn - shot_han
prev_spoken = to - shot_of(OLD_S)
if prev_ver_spoken:
    prev_spoken = prev_ver_spoken
def _spoken_pairs(S):
    return [(n, sum(D.han(p) for p in paras if not p.startswith('（画面')))
            for n, (t, paras) in S.items()]
_tot = SR.total_of(_spoken_pairs(NEW_S))   # 逐节累加（分节实测语速，见 speech_rate.py）
try:
    _p = SR.total_of(_spoken_pairs(BAK_S))
except Exception:
    _p = 0
_net = spoken - prev_spoken
NET = ('+%d' % _net) if _net > 0 else ('%d' % _net if _net < 0 else '±0')
read_min, read_sec = _tot // 60, _tot % 60

TITLES = {n: NEW_S[n][0] for n in NEW_S}

def oral_han(n, S):
    """口播口径：扣掉「（画面…）」分镜提示行，与提词器一致"""
    if n not in S:
        return None
    return sum(D.han(p) for p in S[n][1] if not p.startswith('（画面'))

rows = []
for s in secs:
    n = s['n']
    _on, _oo = oral_han(n, NEW_S), oral_han(n, BAK_S) if 'BAK_S' in dir() else None
    if _oo is None:
        _oo = _on
    s['hn'], s['ho'] = _on, _oo
    d = s['hn'] - s['ho']
    cls = 'plus' if d > 0 else ('minus' if d < 0 else 'zero')
    sign = ('+%d' % d) if d > 0 else ('%d' % d if d < 0 else '±0')
    title = TITLES[n]
    if n in FROZEN:
        rows.append(dict(n=n, title=title, ho=s['hn'], hn=s['hn'], delta='定稿', cls='keep',
                         note='这一节已定稿：左右同文，不再对比。', frozen=True))
        continue
    if s['ins'] == 0 and s['dele'] == 0:
        note = '这一节一字没动，左右完全一样。'
        cls, sign = 'zero', '±0'
    else:
        bits = []
        if s['ins']:
            bits.append('新增 %d 处' % s['ins'])
        if s['dele']:
            bits.append('删掉 %d 处' % s['dele'])
        note = '跟上一版比：' + '、'.join(bits) + '、汉字 %d → %d（%s）。' % (s['ho'], s['hn'], sign)
    rows.append(dict(n=n, title=title, ho=s['ho'], hn=s['hn'], delta=sign, cls=cls,
                     note=note, frozen=False))

# ---------- 正文字节 ----------
body_secs = []
for s in secs:
    n = s['n']
    r = rows[n - 1]
    left = ''.join('<div class="txt">%s</div>' % h for h in s['left'])
    right = ''.join('<div class="txt edit" contenteditable="true" spellcheck="false" data-sec="%d" data-para="%d">%s</div>'
                    % (n, i, h) for i, h in enumerate(s['right']))
    extra = ''
    if r['frozen']:
        delta_html = '<span class="delta keep">已定稿 · 左右同文</span>'
        old_hd = '已定稿 · 11-04（只读）'
        new_hd = '定稿 · 11-04（点进去就能改）'
    else:
        delta_html = '<span class="delta %s">%d → %d · %s</span>' % (r['cls'], r['ho'], r['hn'], r['delta'])
        old_hd = ('上一版定稿 · 11-04（只读）' if n in PREV_FIXED else '上一版定稿 · 11-03（只读）')
        new_hd = '新版 · 11-04（点进去就能改）'
    body_secs.append(
        '<section id="s%d">\n'
        '<div class="sechead"><span class="num">%02d</span><h2>%s</h2>%s</div>\n'
        '<p class="note">%s%s</p>\n'
        '<div class="cols">\n'
        '<div class="col old"><span class="hd">%s</span>%s</div>'
        '<div class="col new"><span class="hd">%s<em class="st" data-st="%d">自动保存</em></span>%s</div>'
        '</div></section>' % (n, n, r['title'], delta_html,
                              r['note'], extra, old_hd, left, new_hd, n, right))

table = ''.join(
    '<tr><td class="rn"><a href="#s%d">%02d</a></td><td class="rt">%s</td>'
    '%s<td class="rw">%s</td></tr>'
    % (r['n'], r['n'], r['title'],
       ('<td class="rc keep">%d 字</td>' % r['hn'] if r['frozen']
        else '<td class="rc %s">%d → %d</td>' % (r['cls'], r['ho'], r['hn'])),
       r['note'])
    for r in rows)

nav = ''.join('<a href="#s%d">%d</a>' % (i, i) for i in range(1, 16))

ROUND = set(ROUND_CHANGED) if ROUND_CHANGED else set()
changed = [r for r in rows if r['n'] in ROUND]
changed_nums = '、'.join(str(r['n']) for r in changed)
cmp_n = len(rows)
CHG = '、'.join('第 %d 节' % n for n in ROUND_CHANGED) if ROUND_CHANGED else '这一版'
UNCH = 15 - len(ROUND_CHANGED)
KICK = '定稿版 · %s刚改过 · 其余 %d 节不动 · 2026-11-04' % (CHG, UNCH)
LEDE = ('<b>%s已同步到左右两边</b>：左边是上一版（只读，删掉的字标红删除线），右边是现在这版（新加的字标绿）。'
        '<b>其余 %d 节一字没动</b>，左右同文。<b>右边每一节仍然能点进去改</b>，停手 1 秒自动存到服务器。'
        '<b>想批注：用鼠标选中任意文字</b>，松手点「＋ 批注」就写。' % (CHG, UNCH))
FOOT = ('%s：左 = 上一版，右 = 现在这版（绿 = 新加，红删除线 = 删掉）。其余 %d 节已定稿，左右同文。'
        '右边仍然可编辑，改完停手 1 秒自动保存。<br>这一版没有任何人工改写：提词器里是什么字，右栏就是什么字。' % (CHG, UNCH))

body = '''<body>
<div class="wrap">
<div class="bar"><div class="l"><span class="dot"></span><span id="bmsg">正在连保存服务器…</span></div><div class="r">停手 1 秒自动存 · Ctrl+S 立刻存 · 选中文字可批注</div></div>
<header>
<div class="kick">%s</div>
<h1>定稿版</h1>
<p class="lede">%s</p><div class="stats">
<div class="stat"><b>%d</b><span>新版口播汉字（上一版 %d）</span></div>
<div class="stat"><b>%d:%02d</b><span>纯念时长（上一版 %d:%02d）</span></div>
<div class="stat"><b>%d</b><span>成片时长（分钟）</span></div>
<div class="stat"><b>%s</b><span>净增字 · 共 15 节</span></div>
</div>
<h2 class="sub">定稿总表</h2>
<table class="sum"><tr><th>节</th><th>标题</th><th>汉字</th><th>状态</th></tr>%s</table>
<h2 class="sub">逐节正文</h2>
<p class="legend">点下面 1–15 直接跳到那一节。</p>
<nav>%s</nav>
</header>
<main>
%s
</main>
<footer>%s</footer>
</div>
''' % (KICK, LEDE, spoken, prev_spoken, read_min, read_sec, _p // 60, _p % 60,
       round(_tot * 1.19 / 60), NET, table, nav, '\n'.join(body_secs), FOOT)

# ---------- 组装整页 ----------
page = open(PAGE, encoding='utf-8').read()
head = page[:page.find('<body>')]
tail = page[page.find('<script>\nvar TOKEN'):]

# CSS 补三处
ADD_NW = '.sum td:nth-child(1),.sum td:nth-child(3),.sum th:nth-child(1),.sum th:nth-child(3){white-space:nowrap}'
if '.zero{' not in head:
    head = head.replace('.keep{color:#4F6B3E;font-weight:700}',
                        '.keep{color:#4F6B3E;font-weight:700}\n.zero{color:#7C7668}')
if 'white-space:nowrap' not in head:
    head = head.replace('.zero{color:#7C7668}', '.zero{color:#7C7668}\n' + ADD_NW)
head = head.replace('grid-template-columns:repeat(14,1fr)', 'grid-template-columns:repeat(15,1fr)')

# JS：存盘/比对时去掉 <del> 里的旧字，别把删掉的字写进正本
old_js = "function secText(sec){return secEls(sec).map(function(e){return e.innerText;}).join('\\n\\n');}"
new_js = ("function ptxt(e){var c=e.cloneNode(true),ds=c.querySelectorAll('del'),i;"
          "for(i=0;i<ds.length;i++)ds[i].parentNode.removeChild(ds[i]);"
          "return (c.textContent||'').replace(/\\u00a0/g,' ').trim();}\n"
          "function secText(sec){return secEls(sec).map(ptxt).join('\\n\\n');}")
if old_js in tail:
    tail = tail.replace(old_js, new_js)
elif 'function ptxt(' not in tail:
    raise SystemExit('secText 没找到')

# ---------- 写 md 正本 ----------
md = []
for n in range(1, 16):
    t, paras = NEW_S[n]
    md.append('【%d. %s】' % (n, t))
    md.append('')
    md.append('\n\n'.join(paras))
    md.append('')
open(LIVE, 'w', encoding='utf-8').write('\n'.join(md))
mt = os.path.getmtime(LIVE)

# ---------- 写 htmls（右侧带标记的 innerHTML） ----------
hs = {str(s['n']): s['right'] for s in secs}
json.dump(hs, open(HTMLS, 'w', encoding='utf-8'), ensure_ascii=False)

# ---------- BUILT ----------
tail = re.sub(r'var TOKEN="[^"]*", BUILT=[0-9.]+;', 'var TOKEN="%s", BUILT=%r;' % (
    open(os.path.join(BASE, '.compare-token'), encoding='utf-8').read().strip(), mt), tail, count=1)

open(PAGE, 'w', encoding='utf-8').write(head + body + tail)
print('page bytes', os.path.getsize(PAGE))
print('md bytes', os.path.getsize(LIVE), 'BUILT', mt)
print('口播 %d 汉字（上一版 %d），纯念 %d:%02d' % (spoken, prev_spoken, read_min, read_sec))
print('动了这几节：', changed_nums)
