"""Generic DAG → level batches helper.

Phase 5.3 background_chain_render의 `_compute_chain_bg_levels`를 일반화.
Phase 7 floor_plan_render / background_render에서 재사용.

각 level[i]는 parent가 모두 이전 level에서 완료(또는 renderable_set 외부)
상태인 노드들. 같은 level 내 노드끼리는 독립 → ThreadPool 병렬 안전.
"""
from __future__ import annotations

import logging
from typing import Any, Dict, List, Set

logger = logging.getLogger(__name__)


def compute_dag_levels(
    order: List[str],
    items: Dict[str, Dict[str, Any]],
    renderable_set: Set[str],
    parent_field: str = "parent_id",
) -> List[List[str]]:
    """`order` 순서를 보존하며 DAG를 level batch로 분할.

    Args:
        order: 노드 IDs (planner 결정 순서)
        items: id → node dict (parent_field key 보유)
        renderable_set: 실제로 렌더 대상인 IDs. 이 집합 밖 노드는 부모 부재로 간주.
        parent_field: 노드 dict의 부모 ID 필드명. 기본 "parent_id".

    Returns:
        level batches (input order 보존). cycle 검출 시 잔여 노드를 마지막 level로 flush.
    """
    levels: List[List[str]] = []
    completed: Set[str] = set()
    remaining = [nid for nid in order if nid in renderable_set]

    while remaining:
        ready: List[str] = []
        not_ready: List[str] = []
        for nid in remaining:
            node = items.get(nid, {})
            pid = (node.get(parent_field) or "").strip()
            if not pid or pid in completed or pid not in renderable_set:
                ready.append(nid)
            else:
                not_ready.append(nid)
        if not ready:
            logger.warning(
                "compute_dag_levels: unresolved parent dependencies — flushing %d nodes as final level",
                len(not_ready),
            )
            ready = not_ready
            not_ready = []
        levels.append(ready)
        completed.update(ready)
        remaining = not_ready
    return levels
