"""좌표 **모양** — 보고에서 따로 세는 칸. ★집행 규칙이 아니다.

TASKS 「현대·지역 고증 축」 acceptance ⑦: 「보고는 historical / modern-localized
를 **따로 세되** 집행 규칙은 **한 벌**」.

★이름을 「historical / modern」으로 붙이지 않는다 — 그것은 시대 값의 **뜻**을
읽는 일이고, 우리는 글자로 뜻을 판단하지 않는다. 런타임 구조화 칸
(`visual_world_rules.era` / `.region`)이 **비었나 찼나**만 본다. 그것이 보고가
정직하게 말할 수 있는 전부다:

    era+region    시대·지역 둘 다 선언됐다
    region_only   지역만 — 시대를 만들지도 요구하지도 않는 판
    era_only      시대만
    none          둘 다 없다

★집행(판별 두 축 · 살 자격 · 붙일 자격)은 이 모양을 **보지 않는다** —
시험이 그 함수들의 소스에 `era`/`region` 이 없음을 잠근다.
"""
from __future__ import annotations

from typing import Any, Dict, Iterable, Mapping

SHAPE_BOTH = "era+region"
SHAPE_REGION_ONLY = "region_only"
SHAPE_ERA_ONLY = "era_only"
SHAPE_NONE = "none"
SHAPES = (SHAPE_BOTH, SHAPE_REGION_ONLY, SHAPE_ERA_ONLY, SHAPE_NONE)


def shape_of(coordinates: Mapping[str, Any]) -> str:
    """`{"era": …, "region": …}` → 모양. ★값의 뜻은 안 읽는다 — 비었나만."""
    era = str((coordinates or {}).get("era") or "").strip()
    region = str((coordinates or {}).get("region") or "").strip()
    if era and region:
        return SHAPE_BOTH
    if region:
        return SHAPE_REGION_ONLY
    if era:
        return SHAPE_ERA_ONLY
    return SHAPE_NONE


def count_by_shape(items: Iterable[Mapping[str, Any]]) -> Dict[str, int]:
    """좌표 목록 → 모양별 수. ★네 칸이 항상 다 있다(0 이어도) — 「없다」와
    「안 셌다」를 가른다."""
    out = {s: 0 for s in SHAPES}
    for c in items:
        out[shape_of(c)] += 1
    return out
