"""字幕数据迁移脚本：L3Node.subtitle → L3Card.subtitle + L2 聚合。

幂等操作——已迁移的树文件不会被重复修改。

用法:
    conda activate Video-Tree-TRM & python tools/migrate_subtitle_to_card.py [--dry-run]

迁移逻辑:
    1. 扫描 store/videos/*/tree.json
    2. 对每棵树:
       - L3: 将节点级 subtitle 移入 card["subtitle"]，删除节点级 key
       - L2: 聚合子 L3 的 card["subtitle"] 写入 L2 card["subtitle"]
       - 处理边界: None → ""、空字符串、已迁移(card 内已有 subtitle)
    3. 回写 JSON
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

from loguru import logger


def _migrate_tree_dict(tree_dict: dict) -> bool:
    """原地迁移单棵树的 dict 结构，返回是否发生了修改。

    参数:
        tree_dict: tree.json 加载后的字典。

    返回:
        True 表示发生了修改，需要回写。
    """
    modified = False

    for l1 in tree_dict.get("roots", []):
        for l2 in l1.get("children", []):
            l3_subtitles: list[str] = []

            for l3 in l2.get("children", []):
                card = l3.get("card", {})

                # Phase 1: 迁移 L3 节点级 subtitle → card.subtitle
                node_subtitle = l3.get("subtitle")  # 旧位置
                card_subtitle = card.get("subtitle", "")  # 新位置

                if node_subtitle is not None and not card_subtitle:
                    # 旧格式：节点级有值，card 内无值 → 迁移
                    card["subtitle"] = node_subtitle if node_subtitle else ""
                    modified = True
                elif "subtitle" not in card:
                    # 既无节点级也无 card 级 → 初始化为空
                    card["subtitle"] = ""
                    modified = True

                # Phase 2: 清理节点级 subtitle key
                if "subtitle" in l3:
                    del l3["subtitle"]
                    modified = True

                # 收集 L3 字幕用于 L2 聚合
                final_subtitle = card.get("subtitle", "")
                if final_subtitle:
                    l3_subtitles.append(final_subtitle)

            # Phase 3: L2 字幕聚合
            l2_card = l2.get("card", {})
            existing_l2_subtitle = l2_card.get("subtitle", "")
            aggregated = "\n".join(l3_subtitles)

            if "subtitle" not in l2_card or existing_l2_subtitle != aggregated:
                l2_card["subtitle"] = aggregated
                modified = True

    return modified


def migrate_all(
    videos_dir: Path,
    *,
    dry_run: bool = False,
) -> tuple[int, int, int]:
    """扫描并迁移所有树文件。

    参数:
        videos_dir: store/videos 目录路径。
        dry_run: 仅检查不写入。

    返回:
        (total, migrated, skipped) 计数元组。
    """
    tree_files = sorted(videos_dir.glob("*/tree.json"))
    total = len(tree_files)
    migrated = 0
    skipped = 0

    for tree_path in tree_files:
        video_id = tree_path.parent.name
        try:
            with open(tree_path, encoding="utf-8") as f:
                tree_dict = json.load(f)
        except (json.JSONDecodeError, OSError) as exc:
            logger.warning("跳过损坏文件: {} ({})", tree_path, exc)
            skipped += 1
            continue

        changed = _migrate_tree_dict(tree_dict)

        if changed:
            if dry_run:
                logger.info("[dry-run] 需要迁移: {}", video_id)
            else:
                with open(tree_path, "w", encoding="utf-8") as f:
                    json.dump(tree_dict, f, ensure_ascii=False, indent=2)
                logger.info("已迁移: {}", video_id)
            migrated += 1
        else:
            logger.debug("无需迁移: {}", video_id)

    return total, migrated, skipped


def main() -> None:
    """CLI 入口。"""
    parser = argparse.ArgumentParser(
        description="将 L3Node.subtitle 迁移到 L3Card.subtitle + L2 聚合",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="仅检查不写入",
    )
    parser.add_argument(
        "--videos-dir",
        type=Path,
        default=Path("store/videos"),
        help="视频树目录 (默认: store/videos)",
    )
    args = parser.parse_args()

    if not args.videos_dir.is_dir():
        logger.error("目录不存在: {}", args.videos_dir)
        sys.exit(1)

    total, migrated, skipped = migrate_all(
        args.videos_dir,
        dry_run=args.dry_run,
    )
    action = "需要迁移" if args.dry_run else "已迁移"
    logger.info(
        "迁移完成: 总计={}, {}={}, 跳过={}",
        total,
        action,
        migrated,
        skipped,
    )


if __name__ == "__main__":
    main()
