"""상태 변형(쓰러짐·의식 없음·죽음)의 **입력 그림**을 고른다 — 한 자리 (2026-09-20).

실측(컨트리로드 2판): 상태 변형 단계가 「그 인물의 가장 최근 합성」을 입력으로 써서
찰리(로봇)의 의식 없음·죽음 변형이 **외투를 입고** 만들어졌다 — 그 상태가 나오는
샷 5개에서 찰리는 옷이 없다(O00·배정 없음). 상태 변형은 합성보다 먼저 붙으므로
(state_variant > 의상 합성 > 기본) 그 샷에 틀린 옷이 실린다. 현우 의식 없음도 O02
에서 만들어졌는데 쓰는 샷은 O01 이었다.

규칙: 그 (인물, 상태)가 나오는 **선택 샷**에서 그 인물에게 **명시로 배정된** 옷을 센다.
- 옷 없음(O00)이 다수 → 기본 그림(`BASE`)
- 한 옷이 다수 → 그 옷(아웃룩 id) — 그 옷의 합성에서 만든다
- 동수·명시 배정 없음 → None — 종전 규칙(가장 최근 합성)으로 둔다
★「배정 없음」은 **표를 던지지 않는다** — 그 샷에서 무슨 옷인지 모르는 것을
 「옷 없음」으로 세면, 옷이 명시된 샷을 이긴다(실측: 현우 중상 — 배정 없음 3 ·
 O02 2 → 기본 그림으로 바뀌어 O02 샷 둘에 틀린 옷이 실릴 뻔했다).
"""
from __future__ import annotations

from collections import Counter
from typing import Any, Iterable, Optional, Set

BASE = "__base__"


def pick_state_variant_source(
    outlooks_in_shots: Iterable[Optional[str]], o00_ids: Set[str],
) -> Optional[str]:
    """샷별 배정 옷(아웃룩 id, 없으면 None) → `BASE` | 아웃룩 id | None(동수·재료 없음)."""
    votes: Counter = Counter()
    for ol in outlooks_in_shots:
        if not ol:
            continue          # 배정 없음 — 모르는 것은 세지 않는다
        votes[BASE if ol in o00_ids else ol] += 1
    if not votes:
        return None
    top = votes.most_common()
    if len(top) > 1 and top[0][1] == top[1][1]:
        return None
    return top[0][0]


def source_asset_for(db: Any, project_id: str, char_id: str,
                     want: Optional[str]) -> Any:
    """`pick_state_variant_source` 의 답 → 입력으로 쓸 ImageAsset (없으면 None).

    - `BASE` → 그 인물의 대표 기본 그림(합성·아웃룩·상태 변형 제외)
    - 아웃룩 id → 그 (인물, 옷) 합성 중 **지금 기본 그림으로 그린 것**, 없으면 가장 최근
    """
    if want is None:
        return None
    from app.core.step_lock import parse_iso
    from app.models.project import ImageAsset

    base = (
        db.query(ImageAsset)
        .filter(ImageAsset.project_id == project_id,
                ImageAsset.entity_id == char_id,
                ImageAsset.asset_type == "reference",
                ImageAsset.is_primary == 1,
                ~ImageAsset.prompt_used.like("[composite:%"),
                ~ImageAsset.prompt_used.like("[outlook_id:%"),
                ~ImageAsset.prompt_used.like("[state_variant%"))
        .first()
    )
    if want == BASE:
        return base
    rows = (
        db.query(ImageAsset)
        .filter(ImageAsset.project_id == project_id,
                ImageAsset.asset_type == "reference",
                ImageAsset.prompt_used.like(f"[composite:{char_id}:{want}%"))
        .all()
    )
    if not rows:
        return None
    cur = base.id if base is not None else ""

    def _rank(a: Any):
        # 합성 키 끝의 기본 그림 id 가 지금 대표와 같으면 먼저(합성 로더와 같은 뜻)
        tier = 0 if cur and f":{cur}]" in (a.prompt_used or "") else 1
        ts = parse_iso(getattr(a, "created_at", None))
        return (tier, -(ts.timestamp() if ts else 0))

    return sorted(rows, key=_rank)[0]


def existing_source_differs(existing_input_ids: Any, want_source_id: Optional[str]) -> bool:
    """이미 있는 상태 변형의 입력(`input_image_ids` 첫 칸)이 지금 규칙이 고른 입력과
    **다르면** True — 내리고 다시 만든다. 규칙이 못 골랐거나(None) 옛 자산에 입력
    기록이 없으면 False(모르는 것을 다시 사지 않는다)."""
    import json as _json

    if not want_source_id:
        return False
    try:
        ids = (_json.loads(existing_input_ids) if isinstance(existing_input_ids, str)
               else list(existing_input_ids or []))
    except (TypeError, ValueError):
        return False
    first = ids[0] if ids else None
    return bool(first) and first != want_source_id
