#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""静态自检：零外链 / 无原稿痕迹 / 页码引用 / JS 语法 / 页面清单。"""
import io
import json
import os
import re
import subprocess
import sys

W = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
DECK = os.path.join(W, '培训与上手', '培训', '价格谈判培训_攻心为上_deck.html')
html = io.open(DECK, encoding='utf-8').read()
ok = True


def say(label, val, good):
    global ok
    ok = ok and good
    print('%s %-46s %s' % ('✅' if good else '❌', label, val))


print('文件：%s  (%.1f KB, %d 页)' % (DECK, len(html.encode('utf-8')) / 1024.0,
                                   len(re.findall(r'class="slide ', html))))

# ① 零外部依赖
ext = {
    'src="http': len(re.findall(r'src\s*=\s*["\']http', html, re.I)),
    'href="http': len(re.findall(r'href\s*=\s*["\']http', html, re.I)),
    '<script src': len(re.findall(r'<script[^>]*\bsrc\s*=', html, re.I)),
    '<link': len(re.findall(r'<link', html, re.I)),
    'https://': html.count('https://'),
}
for k, v in ext.items():
    say('零外链 · %s' % k, v, v == 0)
say('http:// 仅 SVG 命名空间', html.count('http://'),
    html.count('http://') == html.count('http://www.w3.org/2000/svg'))
say('@import / url(http', len(re.findall(r'@import|url\(\s*["\']?https?:', html, re.I)),
    len(re.findall(r'@import|url\(\s*["\']?https?:', html, re.I)) == 0)

# ② 无原稿痕迹
BAD = ['原稿', '参考PPT', '参考 PPT', '源PPT', '源 PPT', '改编', '重排与提炼', '讲师', '講師',
       '梁辉', '梁輝', 'lianghui', 'LIANG HUI', '抖音', '泰康', '華潤', '华润', '美加',
       'guizang', '歸藏', '归藏', 'op7418', '主讲人', '课件', 'training liang', 'TRAINER']
for k in BAD:
    n = html.count(k)
    say('无痕迹 · 「%s」' % k, n, n == 0)

# ③ 页码引用 pXX（先剔除 base64 内联资源，避免误报）
scan = re.sub(r'data:[^"\')]{40,}', 'DATAURI', html)
pg = sorted(set(re.findall(r'(?<![A-Za-z0-9])[pP]\s*(\d{1,2})(?![\d%])', scan)))
pg = [x for x in pg if int(x) >= 3]
say('无 pXX 页码引用', pg, not pg)
for k in ['原稿 p', '第 24 页', '第 37 页', 'p24', 'p37', '（原稿', '出处', ' 页）']:
    say('无痕迹 · 「%s」' % k, scan.count(k), scan.count(k) == 0)

# ④ 来源/出处类结构性措辞
for k in ['OCR', '视觉核对', '存疑', '自检清单', '自问自答']:
    print('   · 「%s」出现 %d 次' % (k, html.count(k)))

# ⑤ emoji
emo = re.findall(r'[\U0001F300-\U0001FAFF\u2600-\u27BF\uFE0F]', html)
say('无 emoji', emo, not emo)

# ⑥ 内联 JS 语法
blocks = re.findall(r'<script[^>]*>(.*?)</script>', html, re.S)
print('   内联 script 块：%d' % len(blocks))
tmp = os.path.join(W, '培训与上手', '培训', '_js_check')
os.makedirs(tmp, exist_ok=True)
allok = True
for i, b in enumerate(blocks):
    f = os.path.join(tmp, 'js%d.js' % i)
    io.open(f, 'w', encoding='utf-8').write(b)
    r = subprocess.run(['node', '--check', f], capture_output=True, text=True)
    if r.returncode != 0:
        allok = False
        print('   ❌ js%d.js: %s' % (i, (r.stderr or '')[-300:]))
say('node --check 全部通过', '%d 块' % len(blocks), allok)

# ⑦ 页面清单
titles = re.findall(r'data-slide-id="([^"]+)"[^>]*data-layout="([^"]+)"[^>]*data-title="([^"]+)"', html)
ids = re.findall(r'data-slide-id="([^"]+)"', html)
print('\n页码  data-layout            页面标题                                    slide-id')
for i, (sid, lay, t) in enumerate(titles):
    print('%3d   %-20s %-38s %s' % (i + 1, lay, t, sid))
say('页数 = slide 数', '%d / %d' % (len(ids), len(re.findall(r'class="slide ', html))),
    len(ids) == len(re.findall(r'class="slide ', html)))
say('slide-id 唯一', len(ids) == len(set(ids)), len(ids) == len(set(ids)))

nt = json.loads(re.search(r'var SPEAKER_NOTES = (\[.*?\]);', html, re.S).group(1))
say('讲稿备注条数 = 页数', '%d' % len(nt), len(nt) == len(ids))
say('备注 id 与页面 id 一一对应', '', [n['id'] for n in nt] == ids)
print('   备注字段齐备：%s' % all(
    all(k in n and n[k] for k in ('id', 'title', 'purpose', 'talk', 'transition')) for n in nt))
chap = [t for t in titles if 'SWISS-COVER-ASCII' == t[1]]
say('章节/封面幕封页数', len(chap), len(chap) == 6)

# ⑧ SVG
svgs = re.findall(r'<svg class="sv"[^>]*viewBox="0 0 ([\d.]+) ([\d.]+)"[^>]*>(.*?)</svg>', html, re.S)
print('\n图版 SVG %d 张（.sv）：' % len(svgs))
for vw, vh, b in svgs:
    rects = re.findall(r'<rect[^>]*x="([\d.]+)"[^>]*y="([\d.]+)"[^>]*width="([\d.]+)"', b)
    ws = sorted(set(float(w) for _, _, w in rects))
    print('   viewBox %sx%s  方框 %d 个 宽度集合 %s' % (vw, vh, len(rects), ws))
say('图版 SVG 张数 = 6', len(svgs), len(svgs) == 6)
deco = re.findall(r'<svg class="deco[^"]*"', html)
say('程序化装饰 SVG 张数', len(deco), len(deco) >= 6)

# ⑧b 案例插画（base64 内嵌 · 零外链）
imgs = re.findall(r'<img\b[^>]*>', html)
bad_img = [t[:60] for t in imgs if not re.search(r'src\s*=\s*"data:', t)]
say('img 张数 / 全部 data: 内嵌', '%d 张 · 非内嵌 %d' % (len(imgs), len(bad_img)), not bad_img)
say('img 无 http(s) src', len(re.findall(r'<img\b[^>]*src\s*=\s*"https?:', html)),
    len(re.findall(r'<img\b[^>]*src\s*=\s*"https?:', html)) == 0)
say('内嵌图片总数(含品牌 Logo)', len(re.findall(r'src="data:', html)),
    len(re.findall(r'src="data:', html)) >= 6)

# ⑧c 页面内不得含任何 API/网络调用代码
for k in ['minimax', 'MINIMAX', 'image_generation', 'api_key', 'apiKey', 'apiSecret',
          'Authorization', 'Bearer ', 'fetch(', 'XMLHttpRequest', 'sendBeacon',
          'localStorage', 'sessionStorage', 'document.cookie', 'indexedDB', 'WebSocket',
          'EventSource', 'navigator.send']:
    n = html.count(k)
    say('页内无 API/网络/落盘代码 · 「%s」' % k, n, n == 0)

# ⑨ 品牌
sys.path.insert(0, os.path.join(W, '脚本'))
import brand_kit as BK  # noqa
v = BK.verify(html)
say('brand_kit.verify 全项', [k for k, x in v.items() if not x] or 'all pass', all(v.values()))

# ⑩ 打印 / 移动 / 输入法
say('@media print 存在', '@media print' in html, '@media print' in html)
say('每页一页（page-break-after）', html.count('page-break-after'), html.count('page-break-after') >= 1)
say('viewport meta', 'width=device-width' in html, 'width=device-width' in html)
say('compositionstart 保护', html.count('compositionstart') >= 1, html.count('compositionstart') >= 1)
say('@media (max-width:760px)', '@media (max-width:760px)' in html, '@media (max-width:760px)' in html)
say('data-scroll 内滚容器', html.count('data-scroll='), html.count('data-scroll=') >= 3)

print('\n%s' % ('静态自检全部通过 ✅' if ok else '存在失败项 ❌'))
