"""SceneStillCheckpointLoader — 씬 스틸 동기화에 필요한 모든 체크포인트를 CheckpointBundle로 수집.

의존 체크포인트: scene_detail, scene_dependency, shot_dependency,
shot_validator, shot_selection, scene_director, shot_director,
scene_summary, shot_cinematography, scene_cinematography.

Pure 로직 — `_load_cp` callable만 주입받아 DB/settings 독립적.
"""
from __future__ import annotations

from typing import Any, Callable, Dict, Optional

from app.services.checkpoint_sync._base import is_cp_syncable
from app.services.checkpoint_sync._scene_still_contracts import CheckpointBundle

CheckpointLoaderFn = Callable[[str], Optional[Dict[str, Any]]]


class SceneStillCheckpointLoader:
    def __init__(self, load_cp: CheckpointLoaderFn):
        self._load_cp = load_cp

    def load(self) -> CheckpointBundle:
        bundle = CheckpointBundle()

        # scene_detail — 메인 루프 입력
        # M2 Fix 2 단일 표준: is_cp_syncable로 partial 허용 + 빈 데이터 cascade 가드 통합.
        # partial(일부 씬 실패, 나머지 완료)도 sync 허용 — 1~2 씬 fail이 64 shot 차단하지 않음
        # (2026-05-01 사고). 빈 scenes(cascade 직후)는 is_cp_syncable이 False 반환.
        # Codex P1-2: partial cp는 sd_partial=True로 표시 — Writer가 stale 마킹/dep 초기화 skip.
        sd_cp = self._load_cp("scene_detail")
        bundle.sd_completed = is_cp_syncable(sd_cp, ["scenes"])
        if bundle.sd_completed:
            # P2-2: completed cp는 data.scenes 키 자체가 없을 수도 있음 (빈 zero rows).
            bundle.scenes = (sd_cp.get("data") or {}).get("scenes") or []
            bundle.sd_partial = (sd_cp.get("status") == "partial")

        # scene_dependency (legacy fallback)
        dep_cp = self._load_cp("scene_dependency")
        if dep_cp:
            _dep_raw = dep_cp.get("data", {}).get("dependencies", [])
            bundle.dep_map = (
                {str(d["scene_index"]): d for d in _dep_raw}
                if isinstance(_dep_raw, list)
                else _dep_raw
            )

        # shot_dependency
        shot_dep_cp = self._load_cp("shot_dependency")
        if shot_dep_cp:
            deps = shot_dep_cp.get("data", {}).get("dependencies")
            if deps:
                bundle.shot_deps = deps
            bundle.shot_dep_completed = shot_dep_cp.get("status") == "completed"

        # shot_validator + shot_selection — shot_info + selected flags
        shot_cp = self._load_cp("shot_validator")
        sel_cp = self._load_cp("shot_selection")
        if shot_cp and shot_cp.get("data", {}).get("scenes"):
            sel_map: Dict[int, set] = {}
            if sel_cp and sel_cp.get("data", {}).get("scenes"):
                for s in sel_cp["data"]["scenes"]:
                    sel_map[s["scene_index"]] = set(s.get("selected_shot_indices", []))
            for sc in shot_cp["data"]["scenes"]:
                si = sc["scene_index"]
                all_shots = list(sc.get("shots", []))
                bundle.shot_info_by_scene[si] = all_shots
                sel_indices = sel_map.get(si)
                if sel_indices is None:
                    bundle.selected_flag_by_scene[si] = {sh["shot_index"] for sh in all_shots}
                else:
                    bundle.selected_flag_by_scene[si] = set(sel_indices)

        # scene_director — 씬 단위 VE/audio/hall
        dir_cp = self._load_cp("scene_director")
        if dir_cp and dir_cp.get("data", {}).get("scenes"):
            for ds in dir_cp["data"]["scenes"]:
                _si = ds.get("scene_index")
                bundle.scene_director_ve[_si] = ds.get("present_entity_ids", [])
                bundle.scene_director_audio[_si] = ds.get("audio_entity_ids", [])
                bundle.scene_director_hall[_si] = ds.get("hallucination_entity_ids", [])

        # shot_director — (scene, shot) 단위 VE
        shot_dir_cp = self._load_cp("shot_director")
        if shot_dir_cp and shot_dir_cp.get("data", {}).get("scenes"):
            for sc in shot_dir_cp["data"]["scenes"]:
                _si = sc["scene_index"]
                for sh in sc.get("shots", []):
                    bundle.shot_director_ve_map[(_si, sh["shot_index"])] = sh.get(
                        "visible_entity_ids", []
                    )

        # scene_summary — shot-level 전파
        summary_cp = self._load_cp("scene_summary")
        if summary_cp and summary_cp.get("status") == "completed":
            bundle.scene_summaries = summary_cp.get("data", {}).get("summaries", [])

        # shot_cinematography
        shot_cine_cp = self._load_cp("shot_cinematography")
        if shot_cine_cp and shot_cine_cp.get("data", {}).get("shots"):
            bundle.shot_cine_shots = shot_cine_cp["data"]["shots"]

        # scene_cinematography (legacy fallback)
        cine_cp = self._load_cp("scene_cinematography")
        if cine_cp and cine_cp.get("status") == "completed":
            bundle.scene_cine_scenes = cine_cp.get("data", {}).get("scenes", [])

        return bundle
