"""s0 — 데이터 정찰: 그룹 자동 선택(mixed, 최대 샷) + 샷/fp/passport 인벤토리.

사용: .venv/bin/python s0_recon.py [--group-id GID]
산출: plans/recon.json + plans/subset.json (대표 샷 자동 선발, --shots override)
"""
import argparse
import json
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))
import forest_lib as F  # noqa: E402


def pick_subset(shots: dict, indoor_n: int, outdoor_n: int) -> dict:
    """대표 샷 선발 — 데이터 기준: framing_scale/loc 다양성 최대화.

    실내: framing_scale 값이 서로 다른 샷 우선(wide→close 순), 동률이면
    scene_index 낮은 순. 실외: loc_id 다양성 우선, 다음 framing 다양성.
    """
    def framing(s):
        return ((s.get("staging") or {}).get("framing_scale")) or "?"

    indoor = sorted([(k, v) for k, v in shots.items() if v["is_indoor"]],
                    key=lambda kv: (kv[1]["scene_index"], kv[1]["shot_index"]))
    outdoor = sorted([(k, v) for k, v in shots.items() if not v["is_indoor"]],
                     key=lambda kv: (kv[1]["scene_index"], kv[1]["shot_index"]))

    FRAMING_PREF = ["wide", "full", "medium", "close", "insert"]

    def diverse(pool, n, key_fns):
        """사전식 다양성: key_fns[0]의 새 값 먼저 전부 커버, 다음 key로."""
        chosen = []

        def try_add(pred, limit=1):
            added = 0
            for k, v in pool:
                if len(chosen) >= n or added >= limit:
                    return
                if k not in chosen and pred(v):
                    chosen.append(k)
                    added += 1

        for fn, ordered in key_fns:
            values = ordered if ordered else []
            if not values:  # 등장 순서 기준 distinct 값
                seenv, values = set(), []
                for _, v in pool:
                    x = fn(v)
                    if x not in seenv:
                        seenv.add(x)
                        values.append(x)
            covered = {fn(shots[k]) for k in chosen}
            for val in values:
                if val in covered:
                    continue
                try_add(lambda v, _val=val, _fn=fn: _fn(v) == _val)
        try_add(lambda v: True)  # 부족분은 앞에서
        return chosen

    loc_key = lambda s: ",".join(s["loc_ids"])  # noqa: E731
    return {
        "indoor": diverse(indoor, indoor_n, [(framing, FRAMING_PREF)]),
        "outdoor": diverse(outdoor, outdoor_n,
                           [(loc_key, None), (framing, FRAMING_PREF)]),
    }


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--group-id", default=None)
    ap.add_argument("--indoor-n", type=int, default=2)
    ap.add_argument("--outdoor-n", type=int, default=2)
    ap.add_argument("--shots", nargs="*", default=None,
                    help="explicit shot keys override (Sx_Shoty)")
    args = ap.parse_args()

    recon = F.build_recon(args.group_id)
    F.save_plan("recon", recon)
    shots = recon["shots"]
    n_in = sum(1 for s in shots.values() if s["is_indoor"])
    n_out = len(shots) - n_in
    print(f"group={recon['group_id']} locs="
          f"{[m['loc_id'] for m in recon['members']]} shots={len(shots)} "
          f"(indoor {n_in} / outdoor {n_out})")

    if args.shots:
        subset = {"indoor": [k for k in args.shots if shots[k]["is_indoor"]],
                  "outdoor": [k for k in args.shots if not shots[k]["is_indoor"]]}
    else:
        subset = pick_subset(shots, args.indoor_n, args.outdoor_n)
    F.save_plan("subset", subset)
    for lane in ("indoor", "outdoor"):
        for k in subset[lane]:
            s = shots[k]
            print(f"  [{lane}] {k} loc={s['loc_ids']} "
                  f"framing={(s.get('staging') or {}).get('framing_scale')} "
                  f"chars={s.get('characters')}")
    print(f"fp_by_loc={ {k: [x['fp_id'] for x in v] for k, v in recon['fp_by_loc'].items()} }")
    print(f"passports={sorted(recon['passports'])}")
    print(f"baseline_aerial={recon['baseline_aerial']}")
    F.runlog({"kind": "stage", "stage": "s0_recon", "group": recon["group_id"],
              "subset": subset})


if __name__ == "__main__":
    main()
