#!/usr/bin/env python3
"""Integrity check for the bagua page's embedded datasets."""
import re, sys, unicodedata

src = open('/Users/terry/DSHarness/bagua/index.html', encoding='utf-8').read()

def grab(name):
    m = re.search(r'const %s\s*=\s*(\[.*?\])\s*;' % name, src, re.S)
    if m:
        return m.group(1)
    m = re.search(r'const %s\s*=\s*(\{.*?\n\});' % name, src, re.S)
    if not m:
        sys.exit(f'找不到常量 {name}')
    return m.group(1)

# ---- trigrams -------------------------------------------------------
tri_src = grab('TRI')
# per-trigram: nm, bits, wx, dir, dirX, xian, hou
tris = {}
for m in re.finditer(
    r'(\w+)\s*:\s*\{nm:"([^"]+)"[^}]*?bits:\[([\d,]+)\][^}]*?wx:"([^"]+)"[^}]*?dir:"([^"]+)"[^}]*?dirX:"([^"]+)"[^}]*?xian:(\d+),hou:(\d+)',
    tri_src):
    tris[m.group(1)] = dict(
        nm=m.group(2), bits=[int(b) for b in m.group(3).split(',')],
        wx=m.group(4), dir=m.group(5), dirX=m.group(6),
        xian=int(m.group(7)), hou=int(m.group(8)))

errs, warns = [], []

print('=== 八卦基础 ===')
print(f'{"key":<6}{"卦":<4}{"卦画":<8}{"五行":<5}{"后天":<5}{"先天":<5}{"先天数":<7}{"洛书数"}')
for k, t in tris.items():
    bars = ''.join('▬' if b else '▬▬' for b in t['bits'])
    print(f'{k:<6}{t["nm"]:<4}{bars:<8}{t["wx"]:<5}{t["dir"]:<5}{t["dirX"]:<5}{t["xian"]:<7}{t["hou"]}')

# canonical trigram bit patterns, bottom -> top, 1 = yang (solid)
CANON = {'乾': [1,1,1], '兑': [1,1,0], '离': [1,0,1], '震': [1,0,0],
         '巽': [0,1,1], '坎': [0,1,0], '艮': [0,0,1], '坤': [0,0,0]}
for k, t in tris.items():
    if t['bits'] != CANON[t['nm']]:
        errs.append(f'{t["nm"]} 卦画错误: {t["bits"]} 应为 {CANON[t["nm"]]}')

# wuxing / direction / numbers
WX_OF = {'乾':'金','兑':'金','坤':'土','艮':'土','震':'木','巽':'木','离':'火','坎':'水'}
HOU_DIR = {'坎':'北','艮':'东北','震':'东','巽':'东南','离':'南','坤':'西南','兑':'西','乾':'西北'}
XIAN_DIR = {'坤':'北','震':'东北','离':'东','兑':'东南','乾':'南','巽':'西南','坎':'西','艮':'西北'}
HOU_NUM = {'坎':1,'坤':2,'震':3,'巽':4,'乾':6,'兑':7,'艮':8,'离':9}
XIAN_NUM = {'乾':1,'兑':2,'离':3,'震':4,'巽':5,'坎':6,'艮':7,'坤':8}
for k, t in tris.items():
    nm = t['nm']
    if t['wx'] != WX_OF[nm]:      errs.append(f'{nm} 五行错: {t["wx"]} 应为 {WX_OF[nm]}')
    if t['dir'] != HOU_DIR[nm]:   errs.append(f'{nm} 后天方位错: {t["dir"]} 应为 {HOU_DIR[nm]}')
    if t['dirX'] != XIAN_DIR[nm]: errs.append(f'{nm} 先天方位错: {t["dirX"]} 应为 {XIAN_DIR[nm]}')
    if t['hou'] != HOU_NUM[nm]:   errs.append(f'{nm} 洛书数错: {t["hou"]} 应为 {HOU_NUM[nm]}')
    if t['xian'] != XIAN_NUM[nm]: errs.append(f'{nm} 先天数错: {t["xian"]} 应为 {XIAN_NUM[nm]}')

# ---- disk ordering --------------------------------------------------
print('\n=== 圆盘排布（自正上方顺时针）===')
for label, order in [('后天', grab('HOU_ORDER')), ('先天', grab('XIAN_ORDER'))]:
    keys = re.findall(r'"(\w+)"', order)
    top = tris[keys[0]]['nm']
    print(f'{label}: ' + ' → '.join(f'{tris[k]["nm"]}{tris[k]["dirX"] if label=="先天" else tris[k]["dir"]}'
                                    for k in keys) + f'   (正上方 = {top})')
    if len(keys) != 8:
        errs.append(f'{label} ORDER 有 {len(keys)} 项，应为 8')

# ---- 64 hexagrams ---------------------------------------------------
print('\n=== 六十四卦上下卦核对 ===')
hex_src = grab('HEX')
rows = re.findall(r'\[(\d+),"([^"]+)","([^"]+)","(\w+)","(\w+)"\]', hex_src)
if len(rows) != 64:
    errs.append(f'HEX 条目 {len(rows)} 条，应为 64')

NATURE = {'乾':'天','坤':'地','震':'雷','巽':'风','坎':'水','离':'火','艮':'山','兑':'泽'}
TRIS = sorted({tris[k]['nm'] for k in tris})

pure = {}
pairs = {}
for n, nm, full, u, l in rows:
    n = int(n)
    un, ln = tris[u]['nm'], tris[l]['nm']
    if full[1] == '为':                            # 纯卦,如「乾为天」
        ok = un == ln == nm and full[0] == nm and full[2] == NATURE[nm]
    else:
        # 通行本卦名一律「上卦意象在前」,如「水雷屯」= 坎上震下、「天火同人」= 乾上离下
        ok = full[0] == NATURE[un] and full[1] == NATURE[ln]
    if not ok:
        errs.append(f'第{n}卦 {nm}({full}) 标注为 上{un}下{ln}，与卦名不符')
    if nm not in full:
        errs.append(f'第{n}卦 卦名「{nm}」未出现在全名「{full}」中')
    if un == ln:
        pure[un] = pure.get(un, 0) + 1
    else:
        pairs.setdefault(frozenset((un, ln)), []).append((n, nm, un, ln))

# 结构不变式: 8 个纯卦各一次; 28 组非纯卦经卦组合各两次且上下颠倒
for t in TRIS:
    if pure.get(t, 0) != 1:
        errs.append(f'纯卦 {t} 出现 {pure.get(t,0)} 次，应为 1 次')
if len(pairs) != 28:
    errs.append(f'非纯卦经卦组合 {len(pairs)} 组，应为 28 组')
for pr, members in sorted(pairs.items(), key=lambda kv: kv[1][0][0]):
    if len(members) != 2:
        errs.append(f'经卦组合 {sorted(pr)} 出现 {len(members)} 次：'
                    + '、'.join(f'{m[0]}{m[1]}' for m in members) + '，应为 2 次')
    elif members[0][2] == members[1][2]:
        errs.append(f'经卦组合 {sorted(pr)} 的两卦 {members[0][1]} 与 {members[1][1]} '
                    f'上下卦同为「上{members[0][2]}下{members[0][3]}」，应为相反取向')

seq = [int(r[0]) for r in rows]
if seq != list(range(1, 65)):
    errs.append('HEX 序号不是连续的 1..64')

for note in warns: print('  ⚠', note)
print(f'已核对 {len(rows)} 卦（纯卦 {sum(pure.values())} + 非纯卦 {len(rows)-sum(pure.values())}）')

print()
if errs:
    print(f'✗ {len(errs)} 处错误:')
    for e in errs: print('  -', e)
    sys.exit(1)
print('✓ 全部数据校验通过')
