"""구간을 어떻게 나누나 — **한 곳**. ★호출 0 · 결정적.

## 왜 따로 두나

같은 묶기 규칙이 **다섯 곳**에 적혀 있었다 (2026-08-31 실측) —

    app/core/steps/beat_shot_steps.py       172 · 430
    app/modules/pipeline/entity_lister.py    84 · 358
    tools/grounding_audit/                   cc_c_preflight · cc_b_verify · segment_bound

상수 `3000` 도 `beat_shot_steps` 와 `entity_lister` **두 곳**에 있다. 여섯 번째를
만들면 안 된다 — 한쪽만 고쳐지면 **preflight 가 세는 구간과 실제로 굽는 구간이
갈리고**, 그러면 잠금과 장부가 안 맞는다.

★기존 다섯 곳은 **지금 안 건드린다**(D 활성화 경계에서 같이 본다). 대신
C(c) 쪽은 전부 이 함수를 쓰고, 시험이 **생산 관례와 같은 묶음을 내는지**
맞댄다.
"""
from __future__ import annotations

from typing import Any, Dict, List, Sequence

#: ★생산 관례. 두 곳에 적힌 값이라 **읽어서** 쓴다 — 여기 다시 적지 않는다.
from app.modules.pipeline.entity_lister import BUNDLE_TARGET

CHUNK_PLAN_CONTRACT_VERSION = "1.202608311800"


def assert_bundle_target_agrees() -> None:
    """★상수가 두 곳에 있다 — 갈리면 **선다**.

    `beat_shot_steps` 와 `entity_lister` 가 각자 `3000` 을 적어 뒀다. 한쪽만
    고치면 beat/shot 이 나눈 씬과 C(c) 가 나눈 구간이 달라지는데, 그것은
    **샷 catalog 와 원문 구간이 어긋난다**는 뜻이다.
    """
    from app.core.steps.beat_shot_steps import BUNDLE_TARGET as OTHER

    if OTHER != BUNDLE_TARGET:
        raise AssertionError(
            f"묶음 상한이 두 곳에서 다르다 — entity_lister {BUNDLE_TARGET} · "
            f"beat_shot_steps {OTHER}. 샷 catalog 와 원문 구간이 어긋난다")


def bundle_scenes(scene_texts: Sequence[Dict[str, Any]],
                  *, key: str = "length") -> List[List[int]]:
    """씬을 **글자 수 상한**으로 묶는다. ★생산 관례와 같은 규칙.

    Args:
        scene_texts: `{"idx": int, <key>: int}` 목록. 순서가 곧 씬 순서다.
        key: 길이가 든 칸 이름. 생산 코드가 `length` 와 `len` 두 이름을 쓴다.

    Returns:
        구간마다 `idx` 목록. **빈 구간은 안 낸다** — 한 씬이 상한보다 커도
        그 씬 하나로 구간을 만든다(자르지 않는다).

    ★씬 본문을 **자르지 않는다.** 상한을 넘으면 구간을 더 나눌 뿐이다.
    """
    out: List[List[int]] = []
    cur: List[int] = []
    n = 0
    for s in scene_texts:
        ln = int(s.get(key) or 0)
        if cur and n + ln > BUNDLE_TARGET:
            out.append(cur)
            cur, n = [], 0
        cur.append(int(s["idx"]))
        n += ln
    if cur:
        out.append(cur)
    return out
