# -*- coding: utf-8 -*-
"""
Wiki 健康檢查（lint）。用法：
  python tools/wiki_lint.py                       # 只檢查 Wiki
  python tools/wiki_lint.py --kmdb <KMDB 路徑>    # 另外檢查 KMDB 書目與知識萃取檔
輸出：lint-report.md
"""
import re, os, sys, glob, json, difflib
from collections import Counter, defaultdict

ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
REQ = ['id', 'type', 'title', 'summary', 'status', 'updated']
LINK_TYPES = {'引用', '概念相近', '方法延伸', '立場衝突', '研究者推論'}
MAX_LINES = 120
issues = defaultdict(list)

def fm(text):
    m = re.match(r'---\n(.*?)\n---', text, re.S)
    return m.group(1) if m else ''

pages = {}
for f in glob.glob(os.path.join(ROOT, '*.md')) + glob.glob(os.path.join(ROOT, '*', '*.md')):
    rel = os.path.relpath(f, ROOT).replace('\\', '/')
    if rel.startswith(('tools/', 'raw/')) or rel in ('lint-report.md', 'AGENTS.md', 'README.md'): continue
    pages[os.path.basename(f)[:-3]] = (rel, open(f, encoding='utf-8-sig').read())

inbound = Counter()
alias_owner = defaultdict(list)
for name, (rel, t) in pages.items():
    meta = fm(t)
    if name not in ('index', 'log', 'books-catalog') and not re.fullmatch(r'[a-z0-9]+(?:-[a-z0-9]+)*', name):
        issues['檔名不符合規則（只用英文小寫、數字與連字號）'].append(rel)
    for k in (REQ if name not in ('index', 'log') else ['id', 'type']):
        if not re.search(rf'^{k}:', meta, re.M):
            issues['缺少 frontmatter 欄位'].append(f'{rel}：{k}')
    # 連線類型：同時支援 {type: 引用, ...} 與逐行條列的 - type: 引用 兩種寫法
    for lt in re.findall(r'(?:\{|^\s*-\s+)type:\s*([^,}\n]+)', meta, re.M):
        lt = lt.strip()
        if lt not in LINK_TYPES:
            issues['未定義的連線類型'].append(f'{rel}：{lt}')
    links = re.findall(r'target:\s*([\w-]+)', meta) + \
        [x.strip().rstrip(chr(92)).split('/')[-1] for x in re.findall(r'\[\[([^\]|#]+)(?:#[^\]|]*)?(?:\|[^\]]*)?\]\]', t)]
    for tgt in links:
        tgt = tgt[:-3] if tgt.endswith('.md') else tgt
        if tgt not in pages:
            issues['斷掉的連結'].append(f'{rel} → {tgt}')
        elif tgt != name and name not in ('index', 'log'):   # 索引與紀錄的連結不算連入，孤兒頁才查得出來
            inbound[tgt] += 1
    # 別名：同時支援 aliases: [甲, 乙] 與 Obsidian 屬性面板寫出的逐行條列
    am = re.search(r'^aliases:\s*\[(.*)\]', meta, re.M)
    bm = re.search(r'^aliases:\s*\n((?:[ \t]+-[^\n]*\n?)+)', meta, re.M)
    names = am.group(1).split(',') if am else re.findall(r'-\s*(.+)', bm.group(1)) if bm else []
    for a in [x.strip().strip('"\'').lower() for x in names if x.strip()]:
        alias_owner[a].append(name)
    for ln_no, ln in enumerate(t.split('\n'), 1):
        if re.match(r'\s*[-*]?\s*`?原文依據`?', ln) and not re.search(r'[（(]SRC-[A-Z\-0-9]+ p\.\d', ln) and '待回查' not in ln:
            issues['標為原文依據卻沒有附頁碼'].append(f'{rel} 第 {ln_no} 行')
    if 'status: draft' in meta and '/concepts/' not in '/' + rel:
        issues['仍為草稿、待人工核對（提醒）'].append(rel)
    if '/concepts/' in '/' + rel:
        if not re.search(r'[（(]SRC-[A-Z\-0-9]+ p\.\d', t):
            issues['概念頁沒有附頁碼的引用'].append(rel)
        n = t.count('\n')
        if n > MAX_LINES:
            issues['頁面超過長度上限'].append(f'{rel}：{n} 行')
        if 'status: draft' in meta:
            issues['仍為草稿、待人工核對（提醒）'].append(rel)
        if '待回查' in t:
            issues['含「待回查」頁碼（提醒）'].append(f"{rel}：{t.count('待回查')} 處")

idx = pages.get('index', ('', ''))[1]
for name, (rel, t) in pages.items():
    if name in ('index', 'log'): continue
    if f'[[{name}]]' not in idx:
        issues['index.md 沒有列出'].append(rel)
    if inbound[name] == 0 and name != 'books-catalog':
        issues['孤兒頁（沒有其他頁連入）'].append(rel)
for a, owners in alias_owner.items():
    if len(set(owners)) > 1:
        issues['別名重複（可能是同一概念被建成兩頁）'].append(f'「{a}」：' + '、'.join(sorted(set(owners))))

# ---- KMDB 原始資料健康檢查 ----
if '--kmdb' in sys.argv:
    kmdb = sys.argv[sys.argv.index('--kmdb') + 1]
    cat = json.load(open(os.path.join(kmdb, 'book_knowledge_catalog.json'), encoding='utf-8-sig'))
    titles = [(b['書名'], re.split(r'[\\/]', b['cleaned_md'])[-2]) for b in cat]
    norm = lambda s: re.sub(r'[\W_（）()：:]+|第.版|修訂版|新版|升級版|典藏版|珍藏版|全新', '', s.lower())
    seen = set()
    for i, (t1, s1) in enumerate(titles):
        for t2, s2 in titles[i + 1:]:
            r = max(difflib.SequenceMatcher(None, norm(t1), norm(t2)).ratio(),
                    difflib.SequenceMatcher(None, s1, s2).ratio() - 0.05)
            if r >= 0.85 and (s1, s2) not in seen:
                seen.add((s1, s2))
                issues['KMDB：疑似重複或不同版本的書'].append(f'{t1}（{s1}）≈ {t2}（{s2}），相似度 {r:.2f}')
    big = Counter()
    for b in cat:
        for x in (b.get('大分類') or '').split('；'):
            big[x.strip()] += 1
        if re.search(r'\bname\b|middle', b.get('大分類', '') + b.get('中分類', '')):
            issues['KMDB：分類欄位殘留程式欄位名稱'].append(f"{b['書名']}：大分類＝{b['大分類'][:30]}")
    issues['KMDB：大分類用語統計（提醒）'].append(
        f'{len(cat)} 本書共出現 {len(big)} 種不同的大分類用語；只出現一次的有 {sum(1 for v in big.values() if v == 1)} 種。'
        '大分類目前是每本書各自的章節主題，不是跨書共用的分類表。')
    kfiles = sorted(glob.glob(os.path.join(kmdb, 'books', '*', 'knowledge_extraction_*.md')))
    issues['KMDB：檢查範圍（提醒）'].append(f'書目 {len(cat)} 筆全部檢查；知識萃取檔本次檢查 {len(kfiles)} 份。')
    for f in kfiles:
        t = open(f, encoding='utf-8').read()
        slug = os.path.basename(os.path.dirname(f))
        n_items = len(re.findall(r'^#{2,4}\s*知識卡\s*\d+', t, re.M))
        n_loc = len(re.findall(r'證據位置', t))
        if n_items != 8:
            issues['KMDB：知識萃取的條目數不是 8，或條目標題格式不同'].append(f'{slug}：{n_items}')
        if n_items and n_loc < n_items:
            issues['KMDB：有條目缺少「證據位置」欄位'].append(f'{slug}：條目 {n_items}、證據位置 {n_loc}')
        if not re.search(r'^## 文件定位摘要\s*\n+\S', t, re.M):
            issues['KMDB：「文件定位摘要」為空或格式不同'].append(slug)

order = sorted(issues, key=lambda k: ('提醒' in k, k.startswith('KMDB'), k))
must = [k for k in order if '提醒' not in k]
lines = ['# Wiki 健康檢查報告', '', f'檢查頁數：{len(pages)}',
         f'需要處理：{len(must)} 類；只是提醒：{len(order) - len(must)} 類。標題含「提醒」的項目不必修改，只是讓你知道還有哪些頁待核對。', '']
for k in order:
    lines += [f'## {k}（{len(issues[k])}）', ''] + [f'- {x}' for x in issues[k][:40]]
    if len(issues[k]) > 40: lines.append(f'- ……另有 {len(issues[k]) - 40} 筆')
    lines.append('')
if not issues: lines.append('沒有發現問題。')
open(os.path.join(ROOT, 'lint-report.md'), 'w', encoding='utf-8').write('\n'.join(lines))
print('\n'.join(lines[:60]))
