"""诊断侧树读取适配：把嵌套 tree.json 展平成诊断消费的扁平 nodes dict。

诊断编排（core/evolution/diagnose.py）期望 tree_data 形如
{"nodes": {node_id: {card, level, time_range}}}，但 TRM5 建树产物
store/videos/<vid>/tree.json 是嵌套 {"metadata","roots":[...]}。本模块递归展平，
接通 TRM4→TRM5 迁移时断掉的 ground_truth 加载环。

不走 TreeIndex 对象层：仅 L1Node 有 to_dict（app/tree/index.py:260），L2/L3 为其内部闭包，
且 to_dict 输出无 level、L3 用 timestamp 无 time_range。直接遍历 json 更省且零改建树模块。
"""

from __future__ import annotations

import json
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from pathlib import Path


def load_tree_nodes(store_dir: Path, video_id: str) -> dict[str, Any]:
    """加载单视频 tree.json 并展平成扁平 nodes dict。

    参数:
        store_dir: store 根目录（含 videos/<video_id>/tree.json）。
        video_id: 视频标识。

    返回:
        {"nodes": {node_id: {"card": dict, "level": int, "time_range": list}}}。

    异常:
        FileNotFoundError: tree.json 不存在（沿用 factory.py fail-loud 先例）。
        ValueError: 树无有效 roots、节点缺 id、或展平后 nodes 为空。

    关键实现:
        level 由遍历深度赋值（root=1/child=2/孙=3），不解析 node_id——node_id 累积式
        （..._L1_..._L2_..._L3_）用正则首匹配会把 L2/L3 误判成 1。
        L3 无 time_range，用 timestamp 合成 [t, t]。
    """
    tree_path = store_dir / "videos" / video_id / "tree.json"
    if not tree_path.exists():
        raise FileNotFoundError(f"树索引文件不存在: {tree_path}（诊断需真实树，P5 fail loud）")
    tree = json.loads(tree_path.read_text(encoding="utf-8"))
    roots = tree.get("roots")
    if not isinstance(roots, list) or not roots:
        raise ValueError(f"树无有效 roots: {tree_path}")

    nodes: dict[str, Any] = {}

    def _walk(node: dict[str, Any], level: int) -> None:
        node_id = node.get("id")
        if not isinstance(node_id, str) or not node_id:
            raise ValueError(f"节点缺 id: {tree_path}")
        time_range = node.get("time_range")
        if time_range is None:
            ts = node.get("timestamp")
            time_range = [ts, ts] if ts is not None else [0, 0]
        nodes[node_id] = {
            "card": node.get("card", {}),
            "level": level,
            "time_range": time_range,
        }
        for child in node.get("children", []) or []:
            _walk(child, level + 1)

    for root in roots:
        _walk(root, 1)

    if not nodes:
        raise ValueError(f"展平后 nodes 为空: {tree_path}")
    return {"nodes": nodes}


def load_tree_data_for_videos(store_dir: Path, video_ids: list[str]) -> dict[str, Any]:
    """按一组 video_id 去重加载展平树，供诊断按 video 注入。

    参数:
        store_dir: store 根目录。
        video_ids: 视频标识列表（可含重复，内部按首次出现顺序去重）。

    返回:
        {video_id: {"nodes": {...}}}。

    异常:
        同 load_tree_nodes（任一视频树缺失/无效即 fail-loud）。
    """
    return {vid: load_tree_nodes(store_dir, vid) for vid in dict.fromkeys(video_ids)}
