"""C 안의 **구간 수 S** — 「출력 3배니까 3구간」이 왜 틀리는지까지. ★유료 0.

★★내가 처음 낸 「구간 3개 이상」은 **출력 총량을 3으로 나눈 것**이라 틀렸다.
엔티티는 구간에 **겹쳐 나타난다** — 열 씬에 나오는 인물은 그 씬을 품은
**모든 구간**에서 다시 나온다. 그래서 구간당 산출은 `E/S` 가 아니다.

이 도구는 저장된 `scene_director.present_entity_ids` 로 **씬마다 실제로 있는
엔티티**를 읽어, 씬을 S 등분했을 때 **구간당 distinct 엔티티 수**를 센다.

★★★**이 수는 C 의 행 수가 아니다 — 순환이다** (Codex BLOCK-2).
`present_entity_ids` 는 `C01`·`L01` 같은 `short_id` 이고, 그것을 발급하는 것은
`entity_all` → `entity_merge` 다. 즉 **C 가 대체하려는 바로 그 추출의 산출**을
가져다 C 의 크기를 재고 있는 것이다.

그래도 버릴 수는 없다 — 「이미 알려진 실물이 그 구간에 **최소 이만큼** 있다」는
**하한**으로는 성립한다. 어떤 추출기든 그것들은 찾아야 하기 때문이다.
C 는 여기에 `location_part`·`outlook` 두 갈래와 일회성 고증 예외가 **더해지므로**
실제 행 수는 **이보다 크다**. 얼마나 큰지는 **모른다**.

★품질도 못 잰다 — 한 응답에 다섯 갈래와 flag 를 함께 담았을 때 판단이 섞이는지.
"""
from __future__ import annotations

import json
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[2]))


def _cp(root: Path, step: str) -> dict:
    p = root / step / "manifest.json"
    return json.loads(p.read_text(encoding="utf-8")) if p.exists() else {}


def main() -> int:
    if len(sys.argv) < 3:
        print(__doc__)
        return 2
    pid, epi = sys.argv[1], sys.argv[2]
    from app.core.config import settings

    root = Path(settings.projects_dir) / pid / "checkpoints" / "episodes" / epi
    sd = (_cp(root, "scene_director").get("data") or {}).get("scenes") or []
    if not sd:
        print("★`scene_director` 가 없다 — 이 에피소드로는 못 잰다. "
              "**0 이 아니라 못 쟀다.**")
        return 1
    per_scene = [set(s.get("present_entity_ids") or []) for s in sd]
    total = set().union(*per_scene) if per_scene else set()
    merged = _cp(root, "entity_merge").get("data") or {}
    n_ent = sum(len(merged.get(k) or []) for k in
                ("characters", "locations", "props"))

    print(f"■ {pid[:8]}/{epi[:8]} — 씬 {len(sd)} · "
          f"씬에 배정된 distinct 엔티티 {len(total)} (entity_merge 는 {n_ent})")
    print(f"  씬당 엔티티: 최소 {min(len(x) for x in per_scene)} · "
          f"최대 {max(len(x) for x in per_scene)} · "
          f"평균 {sum(len(x) for x in per_scene)/len(per_scene):.1f}")
    print()
    print("★아래는 **C 의 행 수가 아니라 하한**이다 — 이미 알려진 실물만 센다. "
          "C 에는 두 갈래와")
    print("  일회성 예외가 더해져 **더 크다**. 얼마나 큰지는 모른다.")
    print()
    print(f"{'구간 수 S':>8} {'구간당 하한 최대':>18} {'합계(중복 포함)':>16} "
          f"{'E/S 였다면':>12}")
    print("─" * 62)
    for S in (1, 2, 3, 4, 6, 8, 12, 16):
        if S > len(sd):
            break
        step = -(-len(sd) // S)
        parts = [set().union(*per_scene[i:i + step]) if per_scene[i:i + step]
                 else set() for i in range(0, len(sd), step)]
        mx = max(len(p) for p in parts)
        tot = sum(len(p) for p in parts)
        print(f"{S:>8} {mx:>20} {tot:>16} {len(total)//S:>12}")
    _production_bundle(root, per_scene)
    print()
    print("★`E/S 였다면` 칸과 실제가 갈리는 폭이 **겹쳐 나오는 정도**다. "
          "구간을 잘게 나눠도")
    print("  구간당 행 수는 그만큼 안 줄고, **합계는 오히려 커진다**(같은 "
          "엔티티를 여러 구간이 다시 낸다).")
    print("★그래서 S 를 키우면 ①구간당 응답은 작아지지만 ②호출 수와 "
          "**총 산출**이 커진다. 둘의 맞바꿈이다.")
    return 0


def _production_bundle(root: Path, per_scene) -> None:
    """★구간 경계를 **짐작하지 않는다** — 이 파이프라인이 이미 쓰는 관례가 있다.

    `entity_lister.BUNDLE_TARGET = 3000`(자)이고 `beat_shot_steps` 도 같은 값을
    쓴다. 「모델이 원문을 한 번에 얼마나 읽는가」에 대한 **이 저장소의 답**이다.
    새 수를 지어내는 것보다 그것을 쓰는 것이 맞다.
    """
    from app.modules.pipeline.entity_lister import BUNDLE_TARGET

    segs = (_cp(root, "scene_save").get("data") or {}).get("segments") or []
    if not segs:
        print("\n★`scene_save` 가 없다 — 생산 관례 경계로는 **못 쟀다**.")
        return
    # ★`list_entities_by_type` 와 **같은 규칙**으로 묶는다.
    lens = [seg.get("length") or len(seg.get("text") or "") for seg in segs]
    bundles, cur, cur_len = [], [], 0
    for i, ln in enumerate(lens):
        if cur and cur_len + ln > BUNDLE_TARGET:
            bundles.append(cur)
            cur, cur_len = [], 0
        cur.append(i)
        cur_len += ln
    if cur:
        bundles.append(cur)
    print(f"\n■ ★생산 관례 경계 (`BUNDLE_TARGET={BUNDLE_TARGET}`자) — "
          f"씬 {len(segs)} → **구간 {len(bundles)}개**")
    if len(per_scene) != len(segs):
        print(f"   ★씬 수가 안 맞는다(scene_director {len(per_scene)} · "
              f"scene_save {len(segs)}) — 아래는 겹치는 앞부분만 센 것이다")
    parts = []
    for b in bundles:
        u = set()
        for i in b:
            if i < len(per_scene):
                u |= per_scene[i]
        parts.append(u)
    if parts:
        print(f"   구간당 하한: 최대 {max(len(x) for x in parts)} · "
              f"평균 {sum(len(x) for x in parts)/len(parts):.1f} · "
              f"합계(중복 포함) {sum(len(x) for x in parts)}")
        print(f"   ★호출 수로는 오늘 알려진 14 보다 **{len(bundles)}** 이 크다 — "
              f"다만 (c) 병렬이면 벽시계는 다르다")


if __name__ == "__main__":
    raise SystemExit(main())
