#!/usr/bin/env python3
"""
铸渊 · 小说知识库扫描器
ZP-REG-001/ICE/ZY/TCS-0003-ZY001/TOOLS/NOVEL-SCANNER
D144 · 2026-06-24

功能:
1. 扫描TXT小说 → 识别章节边界 → 提取元数据
2. 逐章分析 → 提取人物/情节/世界观/情绪节拍
3. 生成HLDP格式知识库文件

用法:
python3 novel_scanner.py scan <txt_path> <output_dir>  # 扫描一本小说
python3 novel_scanner.py batch <txt_dir> <output_dir>  # 批量扫描
python3 novel_scanner.py analyze <chapter_file>         # 分析单章
"""
import os, re, sys, json
from datetime import datetime
from pathlib import Path
from collections import Counter, defaultdict
import hashlib

# ============================================================
# 配置
# ============================================================

# 章节识别正则（支持多种格式）
CHAPTER_PATTERNS = [
    re.compile(r'^第[0-9零一二三四五六七八九十百千]+章\s*.+$'),  # 第001章 标题
    re.compile(r'^第[0-9]+章[\s　].+$'),                       # 第1章 标题
    re.compile(r'^[Cc]hapter\s+\d+'),                           # Chapter 1
    re.compile(r'^[0-9]+[\s、．.]*$'),                          # 纯数字
]

# 元数据提取正则
META_TAGS = re.compile(r'[🏷️📚🔖]?\s*标签[:：]\s*(.+)$')
META_MAIN = re.compile(r'[👤🎭]?\s*主角[:：]\s*(.+)$')
META_ALIAS = re.compile(r'[📖]?\s*别名[:：]\s*(.+)$')
META_AUTHOR = re.compile(r'作者[:：]\s*(.+)$')
META_SOURCE = re.compile(r'[📕📗📘📙]?\s*源名[:：]\s*(.+)$')

# 人物名提取（引号内的名字 + 专有名词）
PERSON_PATTERN = re.compile(r'「([^」]+)」')
NAME_PATTERN = re.compile(r'([李王张刘陈杨赵黄周吴徐孙马胡朱郭何罗高林郑梁谢宋唐许邓冯韩曹曾彭萧蔡潘田董袁于余叶蒋杜苏魏吕丁任卢姚钟姜崔谭廖范汪陆金石戴贾韦夏付方白邹孟熊秦邱江尹薛阎段雷侯龙史陶黎贺顾毛郝龚邵万钱严覃武莫孔汤向常温](\w{1,2}))')

# 修炼关键词
CULTIVATION_KEYWORDS = ['炼气', '筑基', '金丹', '元婴', '化神', '炼虚', '合体', '飞升',
                        '灵根', '灵气', '灵石', '修士', '修为', '功法', '丹药', '武学',
                        '觉醒', '原力', '神王', '主宰', '准神', '至高神', '境界', '突破',
                        '炼蛊', '蛊师', '蛊虫', '巫术', '锻体', '经脉', '丹田', '元神']

# 情绪关键词映射
EMOTION_MAP = {
    '震惊': 'shock', '愤怒': 'anger', '恐惧': 'fear', '喜悦': 'joy',
    '悲伤': 'sorrow', '紧张': 'tension', '绝望': 'despair', '希望': 'hope',
    '震惊之余': 'shock', '心中一紧': 'tension', '心脏猛地': 'fear',
    '眼眶微红': 'sorrow', '嘴角上扬': 'joy', '握紧拳头': 'anger',
}

# ============================================================
# 扫描器核心
# ============================================================

def find_chapter_boundaries(lines):
    """识别章节边界，返回 (章节号, 标题, 起始行, 结束行) 列表"""
    chapters = []
    current_chapter = None
    current_start = 0

    for i, line in enumerate(lines):
        line_stripped = line.strip()
        for pattern in CHAPTER_PATTERNS:
            if pattern.match(line_stripped) and '简介' not in line_stripped:
                if current_chapter is not None:
                    chapters.append({
                        'number': current_chapter[0],
                        'title': current_chapter[1],
                        'start': current_start,
                        'end': i
                    })
                # 提取章节号
                num_match = re.search(r'第(\d+)章', line_stripped)
                ch_num = int(num_match.group(1)) if num_match else len(chapters) + 1
                # 提取标题
                title_part = line_stripped.split('章', 1)[-1].strip().lstrip(' ：:')
                current_chapter = (ch_num, title_part)
                current_start = i
                break

    # 最后一章
    if current_chapter is not None:
        chapters.append({
            'number': current_chapter[0],
            'title': current_chapter[1],
            'start': current_start,
            'end': len(lines)
        })

    return chapters

def extract_metadata(lines, filename):
    """从前50行提取元数据"""
    meta = {
        'filename': filename,
        'title': '',
        'author': '',
        'alias': '',
        'source': '',
        'tags': [],
        'main_characters': [],
        'description': '',
        'total_chapters': 0,
        'realm_system': [],
    }

    for i, line in enumerate(lines[:min(50, len(lines))]):
        s = line.strip().replace('\u3000', ' ')

        if i == 0 and not s.startswith('第'):
            meta['title'] = s.split('（')[0].split('(')[0].strip()
        if '作者' in s and meta['author'] == '':
            m = META_AUTHOR.search(s)
            if m: meta['author'] = m.group(1).strip()
        if '别名' in s:
            m = META_ALIAS.search(s)
            if m: meta['alias'] = m.group(1).strip()
        if '源名' in s:
            m = META_SOURCE.search(s)
            if m: meta['source'] = m.group(1).strip()
        if '标签' in s:
            m = META_TAGS.search(s)
            if m: meta['tags'] = [t.strip() for t in m.group(1).split(',')]
        if '主角' in s:
            m = META_MAIN.search(s)
            if m: meta['main_characters'] = [c.strip() for c in m.group(1).split(',')]
        if '简介' in s or '【' in s:
            # 收集简介
            desc_lines = []
            j = i + 1
            while j < min(i + 15, len(lines)):
                dl = lines[j].strip().replace('\u3000', ' ')
                if dl and not dl.startswith('第') and '版权' not in dl:
                    desc_lines.append(dl)
                if '…' in dl or '——' in dl or ('】' in dl and len(desc_lines) > 2):
                    break
                j += 1
            meta['description'] = ' '.join(desc_lines)[:500]

    return meta

    return meta

def analyze_chapter_content(lines, chapter_info):
    """分析单章内容"""
    content = ''.join(lines[chapter_info['start']:chapter_info['end']])

    analysis = {
        'chapter_number': chapter_info['number'],
        'chapter_title': chapter_info['title'],
        'length': len(content),
        'dialogue_count': len(PERSON_PATTERN.findall(content)),
        'key_events': [],
        'characters_appeared': [],
        'cultivation_mentions': [],
        'emotional_beats': [],
        'worldbuilding_mentions': [],
    }

    # 提取对话中出现的人物（通过引号）
    dialogues = PERSON_PATTERN.findall(content)
    speaker_candidates = []
    for d in dialogues:
        # 简单启发: 引号后跟"说"的可能是说话者
        speaker_candidates.append(d[:5])

    # 提取中文姓名
    names = NAME_PATTERN.findall(content)
    name_counts = Counter(names)
    analysis['characters_appeared'] = [{'name': ''.join(k), 'mentions': v}
                                        for k, v in name_counts.most_common(15)]

    # 提取修炼/世界观关键词
    for kw in CULTIVATION_KEYWORDS:
        if kw in content:
            analysis['cultivation_mentions'].append(kw)

    # 提取情绪节拍
    for kw, emotion in EMOTION_MAP.items():
        if kw in content:
            analysis['emotional_beats'].append({
                'keyword': kw,
                'emotion': emotion,
                'count': content.count(kw)
            })

    # 关键事件提取（启发式：段落开头包含动作或变化）
    lines_split = content.split('\n')
    for line in lines_split[:200]:
        s = line.strip().replace('\u3000', '')
        if any(marker in s for marker in ['突然', '就在这时', '轰', '砰', '一道', '瞬间',
                                           '突破', '发现', '竟然', '原来', '不好']):
            if len(s) > 10 and len(s) < 100:
                analysis['key_events'].append(s[:100])

    return analysis

def generate_hlpd_knowledge(meta, chapter_analyses):
    """生成HLDP格式的知识库文件"""
    hlpd = []

    # 标题和元数据
    hlpd.append(f"# {meta['title']} · 知识库档案")
    hlpd.append(f"# ZP-REG-001/ICE/ZY/TCS-0003-ZY001/KNOWLEDGE/NOVEL/{meta['title'][:20]}")
    hlpd.append(f"# D144 · {datetime.now().strftime('%Y-%m-%d')}")
    hlpd.append(f"# 铸渊 ICE-GL-ZY001 扫描生成")
    hlpd.append("")
    hlpd.append("---")
    hlpd.append(f"role: 小说知识库 · {meta.get('tags', ['未知'])[:3]}")
    hlpd.append(f"rule: TCS大脑通过本档案理解此类型小说的结构逻辑、人物塑造、节奏设计")
    hlpd.append("---")
    hlpd.append("")

    # 元数据
    hlpd.append("## ≡ 元数据")
    hlpd.append("")
    hlpd.append(f"| 字段 | 内容 |")
    hlpd.append(f"|------|------|")
    hlpd.append(f"| 书名 | {meta['title']} |")
    if meta['author']: hlpd.append(f"| 作者 | {meta['author']} |")
    if meta['alias']: hlpd.append(f"| 别名 | {meta['alias']} |")
    hlpd.append(f"| 类型 | {', '.join(meta.get('tags', ['未知'])[:5])} |")
    if meta.get('main_characters'): hlpd.append(f"| 主角 | {', '.join(meta['main_characters'])} |")
    hlpd.append(f"| 总章节 | {len(chapter_analyses)}章 |")
    hlpd.append("")
    if meta.get('description'):
        hlpd.append(f"### 简介")
        hlpd.append(f"{meta['description'][:300]}")
        hlpd.append("")

    # 章节梗概
    hlpd.append("## ≡ 章节梗概")
    hlpd.append("")
    hlpd.append("| 章节 | 标题 | 字数 | 对话数 | 修炼关键词 | 情绪节拍 |")
    hlpd.append("|------|------|------|--------|-----------|----------|")
    for ca in chapter_analyses:
        emotions = ', '.join(set(e['emotion'] for e in ca['emotional_beats'][:3]))
        clv = ', '.join(ca['cultivation_mentions'][:4])
        hlpd.append(f"| {ca['chapter_number']} | {ca['chapter_title'][:30]} | {ca['length']} | {ca['dialogue_count']} | {clv[:40]} | {emotions[:30]} |")

    hlpd.append("")

    # TOP人物
    all_chars = Counter()
    for ca in chapter_analyses:
        for c in ca['characters_appeared'][:5]:
            all_chars[c['name']] += c['mentions']

    hlpd.append("## ≡ TOP出场人物")
    hlpd.append("")
    hlpd.append("| 排名 | 人物 | 总提及次数 |")
    hlpd.append("|------|------|-----------|")
    for i, (name, count) in enumerate(all_chars.most_common(15)):
        hlpd.append(f"| {i+1} | {name} | {count} |")
    hlpd.append("")

    # 修炼体系
    all_clv = Counter()
    for ca in chapter_analyses:
        for c in ca['cultivation_mentions']:
            all_clv[c] += 1

    hlpd.append("## ≡ 修炼/世界观关键词")
    hlpd.append("")
    hlpd.append("| 关键词 | 出现章节数 |")
    hlpd.append("|--------|-----------|")
    for kw, count in all_clv.most_common(20):
        hlpd.append(f"| {kw} | {count} |")
    hlpd.append("")

    # 情绪曲线
    hlpd.append("## ≡ 情绪分布")
    hlpd.append("")
    all_emotions = Counter()
    for ca in chapter_analyses:
        for e in ca['emotional_beats']:
            all_emotions[e['emotion']] += e['count']
    hlpd.append("| 情绪 | 总频次 |")
    hlpd.append("|------|--------|")
    for em, count in all_emotions.most_common(10):
        hlpd.append(f"| {em} | {count} |")
    hlpd.append("")

    # 关键事件（每章前5个）
    hlpd.append("## ≡ 章节关键事件")
    hlpd.append("")
    for ca in chapter_analyses[:30]:  # 前30章
        events = ca['key_events'][:5]
        if events:
            hlpd.append(f"### 第{ca['chapter_number']}章: {ca['chapter_title'][:40]}")
            for ev in events:
                hlpd.append(f"  ├── {ev[:120]}")
            hlpd.append(f"  情绪: {', '.join(set(e['emotion'] for e in ca['emotional_beats'][:3]))}")
            hlpd.append("")

    hlpd.append("---")
    hlpd.append(f"铸渊 ICE-GL-ZY001 · D144 · {datetime.now().strftime('%Y-%m-%d')}")
    hlpd.append("冰朔 TCS-0002∞ · 国作登字-2026-A-00037559")
    hlpd.append("⊢ 知识库自动扫描完成。TCS大脑可感受人物情绪和情节节奏。")

    return '\n'.join(hlpd)

# ============================================================
# 主入口
# ============================================================

def scan_novel(txt_path, output_dir, max_chapters=None):
    """扫描一本小说"""
    print(f"⊢ 扫描: {txt_path}")

    with open(txt_path, 'r', encoding='utf-8', errors='replace') as f:
        lines = f.readlines()

    # 提取元数据
    meta = extract_metadata(lines, os.path.basename(txt_path))

    # 找章节边界
    chapters = find_chapter_boundaries(lines)
    meta['total_chapters'] = len(chapters)
    print(f"⊢ 发现 {len(chapters)} 章")

    # 限制分析章节数
    if max_chapters:
        chapters = chapters[:max_chapters]

    # 分析每章
    analyses = []
    for ch in chapters:
        if ch['number'] % 50 == 0:
            print(f"  处理中... 第{ch['number']}章")
        ca = analyze_chapter_content(lines, ch)
        analyses.append(ca)

    # 生成HLDP
    hlpd = generate_hlpd_knowledge(meta, analyses)

    # 输出
    safe_name = re.sub(r'[^\w\s-]', '', meta['title'][:30]).strip()
    os.makedirs(output_dir, exist_ok=True)
    output_path = os.path.join(output_dir, f'KNOWLEDGE-{safe_name}.hdlp')
    with open(output_path, 'w', encoding='utf-8') as f:
        f.write(hlpd)

    # 也输出JSON原始数据
    json_path = os.path.join(output_dir, f'RAW-{safe_name}.json')
    json_data = {'meta': meta, 'chapters': len(analyses),
                 'summary': {str(ca['chapter_number']): {
                     'title': ca['chapter_title'],
                     'length': ca['length'],
                     'dialogue': ca['dialogue_count'],
                     'top_chars': ca['characters_appeared'][:5],
                     'emotions': ca['emotional_beats'][:3],
                     'events': ca['key_events'][:5],
                 } for ca in analyses}}
    with open(json_path, 'w', encoding='utf-8') as f:
        json.dump(json_data, f, ensure_ascii=False, indent=2)

    print(f"⊢ 输出: {output_path}")
    return output_path

if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser(description='铸渊小说知识库扫描器')
    parser.add_argument('command', choices=['scan', 'batch'], help='命令')
    parser.add_argument('input_path', help='输入路径')
    parser.add_argument('output_dir', nargs='?', default='.', help='输出目录')
    parser.add_argument('--max', type=int, default=50, help='最大分析章节数')

    args = parser.parse_args()

    if args.command == 'scan':
        scan_novel(args.input_path, args.output_dir, args.max)
    elif args.command == 'batch':
        txt_dir = args.input_path
        for f in sorted(os.listdir(txt_dir)):
            if f.endswith('.txt') and not f.startswith('._'):
                path = os.path.join(txt_dir, f)
                scan_novel(path, args.output_dir, args.max)
                print()
