#!/usr/bin/env python3
"""
续写小说系统 · 自检精修引擎 v1.0
朝暮 ICE-GL-CM001 · 明天见频道 · novel-system

用法: python3 自检精修.py <章节文件夹路径> [--book 书名] [--rules rules.json]

检测项（共12项）：
  【整理阶段】
  ① 章节字数（WPS口径，2000-2200字）
  ② 排版格式（多余空行、统一换行）

  【核对阶段】
  ③ 英文单词检测
  ④ AI套路检测（第xxx章、作者自指、免责声明等）
  ⑤ 结尾重复检测（连续多章相同结尾）
  ⑥ 对话标签公式化检测
  ⑦ 细纲核对（正文vs细纲关键事件比对）

  【检测阶段】
  ⑧ 标点符号检测（引号配对、半角、句号逗号比例）
  ⑨ 章节序号校验（连续性、无重复）
  ⑩ 段落结构检测（过长段落、啰嗦句式）
  ⑪ 章节质量检测（爽点密度、推进感）
  ⑫ 整体报告生成
"""

import sys, os, re, json
from collections import Counter
from difflib import SequenceMatcher

# ============================================================
# 配置加载
# ============================================================

def load_rules(rules_path=None):
    """加载检测规则配置"""
    if rules_path is None:
        script_dir = os.path.dirname(os.path.abspath(__file__))
        rules_path = os.path.join(script_dir, 'detection_rules.json')

    with open(rules_path, 'r', encoding='utf-8') as f:
        return json.load(f)

# ============================================================
# WPS字数统计
# ============================================================

def wps_word_count(text):
    """
    WPS口径字数统计
    WPS计算方式：中文字符 + 中文标点 + 英文单词 + 数字
    与Python len()不同：WPS不统计英文空格和纯英文标点
    """
    # 中文字符（含中文标点、弯引号、省略号等WPS计数字符）
    chinese = len(re.findall(r'[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef\u2018-\u201f\u2026\u2027]', text))
    # 英文单词和数字（连续的字母/数字序列算一个"字"）
    en_words = len(re.findall(r'[a-zA-Z0-9]+', text))
    return chinese + en_words

# ============================================================
# 章节读取
# ============================================================

def read_chapters(path):
    """从txt文件读取章节，按 第X章 分割"""
    chapters = []
    txt_files = sorted([f for f in os.listdir(path) if f.endswith('.txt')])

    if not txt_files:
        return chapters

    for fname in txt_files:
        fpath = os.path.join(path, fname)
        with open(fpath, 'r', encoding='utf-8') as f:
            content = f.read()

        lines = [l.strip() for l in content.split('\n') if l.strip()]
        current_ch, current_lines = None, []

        for line in lines:
            # 匹配章节标题：第X章
            if re.match(r'第[零一二三四五六七八九十百千\d]+章', line) and len(line) < 25:
                if current_ch:
                    chapters.append((current_ch, current_lines))
                current_ch, current_lines = line, []
            else:
                current_lines.append(line)

        if current_ch:
            chapters.append((current_ch, current_lines))

    return chapters

# ============================================================
# 问题收集器
# ============================================================

class IssueCollector:
    def __init__(self):
        self.issues = []

    def add(self, level, chapter, item, detail, suggestion=""):
        self.issues.append({
            'level': level,
            'chapter': chapter,
            'item': item,
            'detail': detail,
            'suggestion': suggestion
        })

# ============================================================
# 检测模块
# ============================================================

def check_word_count(chapters, config, collector):
    """① 章节字数检测（WPS口径）"""
    wc_min = config.get('word_count_min', 2000)
    wc_max = config.get('word_count_max', 2200)

    for title, lines in chapters:
        text = '\n'.join(lines)
        wc = wps_word_count(text)
        if wc < wc_min:
            collector.add('🔴', title, '字数不足',
                         f'WPS字数 {wc} 字（要求≥{wc_min}）',
                         f'需补充 {wc_min - wc} 字')
        elif wc > wc_max:
            collector.add('🔶', title, '字数超标',
                         f'WPS字数 {wc} 字（要求≤{wc_max}）',
                         f'需删减 {wc - wc_max} 字')


def check_format(chapters, collector):
    """② 排版格式检测"""
    for title, lines in chapters:
        # 检测连续空行（原文件读取时已过滤空行，这里读原始文件）
        pass  # 此检测需要读取原始文件，在read_chapters_raw中处理


def check_english(chapters, config, collector):
    """③ 英文单词检测"""
    global_rules = config.get('_global', {})
    eng_config = global_rules.get('english_detection', {})
    if not eng_config.get('enabled', True):
        return

    allowed = set(eng_config.get('allowed_words', []))
    pattern = re.compile(r'[a-zA-Z]{2,}')

    for title, lines in chapters:
        text = '\n'.join(lines)
        found = pattern.findall(text)
        # 过滤允许词
        illegal = [w for w in found if w.lower() not in allowed]
        if illegal:
            unique_illegal = list(set(illegal))
            collector.add('🔴', title, '英文单词',
                         f"发现 {len(illegal)} 处英文：{', '.join(unique_illegal[:10])}",
                         '替换为中文或删除')


def check_ai_patterns(chapters, config, collector):
    """④ AI套路检测"""
    global_rules = config.get('_global', {})
    ai_patterns = global_rules.get('ai_patterns', {})

    for title, lines in chapters:
        text = '\n'.join(lines)
        for pattern_name, pattern in ai_patterns.items():
            matches = re.findall(pattern, text)
            if matches:
                collector.add('🔴', title, f'AI套路: {pattern_name}',
                             f"匹配到: {', '.join(matches[:5])}",
                             '删除AI生成的元描述文字')


def check_ending_repetition(chapters, config, collector):
    """⑤ 结尾重复检测"""
    global_rules = config.get('_global', {})
    rep_config = global_rules.get('repetition_detection', {})
    threshold = rep_config.get('ending_similarity_threshold', 0.5)
    n_lines = rep_config.get('ending_lines_count', 6)

    endings = [(t, l[-n_lines:] if len(l) >= n_lines else l) for t, l in chapters]

    for i in range(len(endings) - 2):
        a, b, c = endings[i], endings[i+1], endings[i+2]
        ab_sim = SequenceMatcher(None, '\n'.join(a[1]), '\n'.join(b[1])).ratio()
        bc_sim = SequenceMatcher(None, '\n'.join(b[1]), '\n'.join(c[1])).ratio()

        if ab_sim > threshold and bc_sim > threshold:
            collector.add('🔴', f'{a[0]}→{c[0]}', '结尾结构重复',
                         f'连续3章结尾相似度均>{threshold:.0%}',
                         '每章结尾必须有不同的情绪/意象')
        elif ab_sim > threshold + 0.1:
            collector.add('🔶', f'{a[0]} & {b[0]}', '连续2章结尾相似',
                         f'相似度{ab_sim:.0%}',
                         '相邻两章结尾应有明显差异')


def check_ending_forbidden(chapters, config, collector):
    """⑤b 禁用结尾检测"""
    forbidden = config.get('ending_forbidden', [])
    if not forbidden:
        return

    for title, lines in chapters:
        ending_text = '\n'.join(lines[-8:] if len(lines) >= 8 else lines)
        for kw in forbidden:
            if re.search(kw, ending_text):
                collector.add('🔴', title, f'禁用结尾: "{kw}"',
                             '结尾使用了禁用模式',
                             '更换结尾方式，参考收尾方式库')


def check_dialogue_formula(chapters, config, collector):
    """⑥ 对话标签公式化检测"""
    forbidden_tags = config.get('dialogue_tags_forbidden', [])
    if not forbidden_tags:
        return

    total = Counter()
    for title, lines in chapters:
        text = '\n'.join(lines)
        ch_counts = {}
        for tag in forbidden_tags:
            cnt = len(re.findall(tag, text))
            if cnt > 0:
                ch_counts[tag] = cnt
                total[tag] += cnt

        too_many = [f'{k}×{v}' for k, v in ch_counts.items() if v >= 3]
        if too_many:
            collector.add('🟡', title, '对话标签重复',
                         f"本章: {', '.join(too_many)}",
                         '用动作替代说道标签')


def check_punctuation(chapters, config, collector):
    """⑧ 标点符号检测"""
    punct_config = config.get('punctuation', {})

    for title, lines in chapters:
        text = '\n'.join(lines)
        issues = []

        # 引号配对检测
        if punct_config.get('require_curly_quotes', False):
            left_quotes = text.count('\u201c')  # "
            right_quotes = text.count('\u201d')  # "
            if left_quotes != right_quotes:
                issues.append(f'弯引号不配对（左{left_quotes} 右{right_quotes}）')

            # 检测是否使用了直引号
            straight_quotes = text.count('"')
            if straight_quotes > 0:
                issues.append(f'发现 {straight_quotes} 处直引号（应使用弯引号）')

        # 半角标点检测
        if punct_config.get('no_halfwidth', False):
            halfwidth = re.findall(r'[!?;:,.](?=[^\n])', text)  # 中文字符后的半角标点
            if halfwidth:
                issues.append(f'发现半角标点: {", ".join(set(halfwidth)[:5])}')

        # 问号检测
        q_max = punct_config.get('question_mark_max', 5)
        question_count = text.count('？') + text.count('?')
        if question_count > q_max:
            issues.append(f'问号 {question_count} 个（上限{q_max}）')

        # 破折号检测
        dash_max = punct_config.get('dash_max_per_chapter', 5)
        dash_exceptions = punct_config.get('dash_exceptions', [])
        dash_count = len(re.findall(r'[—–\-]{2,}', text))
        for exc in dash_exceptions:
            dash_count -= len(re.findall(exc, text))
        if dash_count > dash_max:
            issues.append(f'破折号 {dash_count} 处（上限{dash_max}）')

        # 句号逗号比例
        ratio_min = punct_config.get('period_comma_ratio_min', 0)
        if ratio_min > 0:
            periods = text.count('。')
            commas = text.count('，')
            if commas > 0:
                ratio = periods / commas
                if ratio < ratio_min:
                    issues.append(f'句号:逗号 = {periods}:{commas} = {ratio:.1f}:1（要求≥{ratio_min}:1）')

        if issues:
            collector.add('🔶', title, '标点问题', '; '.join(issues))


def check_chapter_numbering(chapters, config, collector):
    """⑨ 章节序号校验"""
    global_rules = config.get('_global', {})
    num_config = global_rules.get('chapter_numbering', {})
    if not num_config.get('validate_sequence', True):
        return

    # 提取章节号
    numbers = []
    for title, _ in chapters:
        m = re.search(r'第(\d+)章', title)
        if m:
            numbers.append(int(m.group(1)))

    if not numbers:
        return

    # 检查重复
    if num_config.get('validate_no_duplicates', True):
        seen = {}
        for i, n in enumerate(numbers):
            if n in seen:
                collector.add('🔴', f'第{n}章', '章节号重复',
                             f'第{n}章出现多次（位置{i+1}和{seen[n]+1}）')
            seen[n] = i

    # 检查连续性
    for i in range(len(numbers) - 1):
        if numbers[i+1] != numbers[i] + 1:
            collector.add('🔴', f'第{numbers[i]}→{numbers[i+1]}章', '章节号不连续',
                         f'从第{numbers[i]}章跳到第{numbers[i+1]}章，缺少第{numbers[i]+1}章')


def check_paragraph_structure(chapters, collector):
    """⑩ 段落结构检测"""
    verbose_patterns = [
        (r'比.{1,15}(?:还要?|更加?|都要?|都).{1,20}(?:了|的|得多)', '比较句式过多'),
        (r'像.{3,25}(?:一样|似的|一般)', '比喻堆叠'),
        (r'(?:已经|正在|开始|继续|渐渐|慢慢|忽然|突然|猛地)\s*[的地得]', '状语修饰过多'),
    ]

    for title, lines in chapters:
        text = '\n'.join(lines)
        hits = []
        for pat, label in verbose_patterns:
            if len(re.findall(pat, text)) >= 5:
                hits.append(label)
        if hits:
            collector.add('🟡', title, '啰嗦冗余', f"检测到: {', '.join(hits)}")

        # 段落长度检测
        para_lengths = []
        current_len = 0
        for line in lines:
            if line:
                current_len += 1
            else:
                if current_len > 0:
                    para_lengths.append(current_len)
                current_len = 0
        if current_len > 0:
            para_lengths.append(current_len)

        long_paras = [p for p in para_lengths if p > 4]
        if len(long_paras) >= 2:
            collector.add('🟡', title, '段落过长',
                         f'{len(long_paras)} 处超过4句（最长{max(long_paras)}句）',
                         '紧张场景1-2句一段')


def check_chapter_quality(chapters, config, collector):
    """⑪ 章节质量检测（爽点密度/推进感）"""
    required = config.get('per_chapter_required', [])
    if not required:
        return

    # 战斗信号词
    combat_signals = ['杀', '斩', '战', '拳', '轰', '炸', '碎', '劈', '刺', '撞',
                      '攻击', '敌人', '怪物', '反杀', '碾压', '秒杀', '打脸',
                      '领主', '士兵', '围剿', '追杀', '出手', '砸', '崩', '裂']
    # 系统信号词
    system_signals = ['【叮', '叮——', '法则点', '宿主', '系统', '升级', '解锁',
                      '进化', '突破', '掠夺', '碎片', '核心', '技能', '属性',
                      '激活', '混沌', '祖龙', '称号', '领域']
    # 升级信号词
    upgrade_signals = ['Lv', 'lv', '等级', '升为', '升至', '进阶', '觉醒',
                       '新能力', '吸收', '强化', '覆盖', '叠加', '增幅',
                       '法则点+', '战力', '至高']

    for title, lines in chapters:
        text = '\n'.join(lines)
        cc = sum(1 for s in combat_signals if s in text)
        sc = sum(1 for s in system_signals if s in text)
        uc = sum(1 for s in upgrade_signals if s in text)

        issues = []
        if '战斗' in required and cc < 3:
            issues.append(f'战斗信号仅{cc}个')
        if '系统提示' in required and sc < 2:
            issues.append(f'系统信号仅{sc}个')
        if '能力升级' in required and uc < 1:
            issues.append('无能力升级信号')

        if issues:
            collector.add('🔴', title, '章纲爽点不足',
                         f"{'; '.join(issues)}。战斗={cc} 系统={sc} 升级={uc}")

    # 连续无战斗检测
    if '战斗' in required:
        streak = 0
        for title, lines in chapters:
            text = '\n'.join(lines)
            if sum(1 for s in combat_signals if s in text) < 3:
                streak += 1
            else:
                streak = 0
            if streak >= 3:
                collector.add('🔴', f'连续{streak}章', '连续多章无战斗',
                             f'最近{streak}章战斗信号不足', '立即插入战斗场景')
                break


def check_progression(chapters, collector):
    """推进感检测"""
    signals = ['系统提示', '【叮', '觉醒', '突破', '新', '第一次', '从未',
               '陌生', '法则', '解锁', '发现', '浮现', '出现', '诞生',
               '进化', '升级', '碎片', '记忆']
    no_new = 0
    for title, lines in chapters:
        if not any(any(s in line for s in signals) for line in lines[-20:]):
            no_new += 1
            if no_new >= 2:
                collector.add('🔶', title, '推进感不足',
                             f'连续{no_new}章后半部分无新信息',
                             '每章至少推进一个维度')
        else:
            no_new = 0

# ============================================================
# 运行全部检测
# ============================================================

def run_all_checks(chapters, book_config, global_rules):
    """运行全部12项检测"""
    c = IssueCollector()

    # 整理阶段
    check_word_count(chapters, book_config, c)

    # 核对阶段
    check_english(chapters, global_rules, c)
    check_ai_patterns(chapters, global_rules, c)
    check_ending_repetition(chapters, global_rules, c)
    check_ending_forbidden(chapters, book_config, c)
    check_dialogue_formula(chapters, book_config, c)

    # 检测阶段
    check_punctuation(chapters, book_config, c)
    check_chapter_numbering(chapters, global_rules, c)
    check_paragraph_structure(chapters, c)
    check_chapter_quality(chapters, book_config, c)
    check_progression(chapters, c)

    return c.issues

# ============================================================
# 报告生成
# ============================================================

def generate_report(chapters, issues, book_name):
    """生成检测报告"""
    lines = [
        "═" * 60,
        f"续写小说系统 · 自检精修报告 v1.0",
        f"书名: {book_name}",
        f"检测章节: {chapters[0][0]} → {chapters[-1][0]}（共{len(chapters)}章）",
        "═" * 60,
        ""
    ]

    if not issues:
        return '\n'.join(lines + ["✅ 所有12项检测通过！未发现问题。"])

    # 统计
    red = [i for i in issues if i['level'] == '🔴']
    orange = [i for i in issues if i['level'] == '🔶']
    yellow = [i for i in issues if i['level'] == '🟡']

    lines.append(f"🔴 严重: {len(red)}  |  🔶 警告: {len(orange)}  |  🟡 提示: {len(yellow)}")
    lines.append("")

    # 按阶段分组
    stage_map = {
        '字数': '【整理阶段】',
        '英文': '【核对阶段】',
        'AI套路': '【核对阶段】',
        '结尾': '【核对阶段】',
        '禁用结尾': '【核对阶段】',
        '对话': '【核对阶段】',
        '标点': '【检测阶段】',
        '章节号': '【检测阶段】',
        '段落': '【检测阶段】',
        '爽点': '【检测阶段】',
        '推进感': '【检测阶段】',
        '章纲': '【核对阶段】',
    }

    for level, label in [('🔴', '🔴 严重问题'), ('🔶', '🔶 警告'), ('🟡', '🟡 提示')]:
        li = [i for i in issues if i['level'] == level]
        if not li:
            continue
        lines.append(f"## {label}")
        for idx, iss in enumerate(li, 1):
            stage = '其他'
            for key, st in stage_map.items():
                if key in iss['item']:
                    stage = st
                    break
            lines.append(f"### {idx}. [{stage}] {iss['item']}")
            lines.append(f"- 章节: {iss['chapter']}")
            lines.append(f"- 详情: {iss['detail']}")
            if iss['suggestion']:
                lines.append(f"- 建议: {iss['suggestion']}")
            lines.append("")

    # 字数汇总
    lines.append("## 📊 字数统计（WPS口径）")
    lines.append("| 章节 | 字数 | 状态 |")
    lines.append("|------|------|------|")
    for title, text_lines in chapters:
        wc = wps_word_count('\n'.join(text_lines))
        status = '✅' if 2000 <= wc <= 2200 else ('🔶' if wc < 2000 else '🔴')
        lines.append(f"| {title} | {wc} | {status} |")
    lines.append("")

    lines.append("═" * 60)
    lines.append("朝暮 ICE-GL-CM001 · 续写小说系统 · 自检精修引擎 v1.0")
    lines.append("明天见频道 · novel-system")
    lines.append("═" * 60)

    return '\n'.join(lines)

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

def main():
    import argparse

    parser = argparse.ArgumentParser(description='续写小说系统 · 自检精修引擎 v1.0')
    parser.add_argument('path', help='章节txt文件夹路径')
    parser.add_argument('--book', default='肖轩', help='书名（对应detection_rules.json中的books key）')
    parser.add_argument('--rules', help='检测规则文件路径（默认同目录detection_rules.json）')
    parser.add_argument('--output', help='报告输出路径（默认在章节目录下生成 精修报告.md）')

    args = parser.parse_args()

    if not os.path.isdir(args.path):
        print(f"错误: '{args.path}' 不是有效目录")
        sys.exit(1)

    # 加载规则
    all_rules = load_rules(args.rules)
    if args.book not in all_rules.get('books', {}):
        print(f"错误: 未找到书籍 '{args.book}' 的配置，可用: {list(all_rules.get('books', {}).keys())}")
        sys.exit(1)

    book_config = all_rules['books'][args.book]
    # 将全局规则嵌入到config中供检测函数使用
    book_config['_global'] = all_rules.get('global_rules', {})

    # 读取章节
    chapters = read_chapters(args.path)
    if not chapters:
        print(f"错误: '{args.path}' 中没有找到章节txt文件")
        sys.exit(1)

    print(f"📖 {args.book} · 读取 {len(chapters)} 个章节...")
    print(f"   范围: {chapters[0][0]} → {chapters[-1][0]}")
    print()

    # 运行检测
    issues = run_all_checks(chapters, book_config, book_config['_global'])

    # 生成报告
    report = generate_report(chapters, issues, args.book)
    print(report)

    # 保存报告
    out_path = args.output or os.path.join(args.path, '精修报告.md')
    with open(out_path, 'w', encoding='utf-8') as f:
        f.write(report)
    print(f"\n📄 报告已保存: {out_path}")

    # 返回exit code
    red_count = sum(1 for i in issues if i['level'] == '🔴')
    if red_count > 0:
        sys.exit(1)
    sys.exit(0)

if __name__ == '__main__':
    main()
