"""Final-asset lineage annotate — persist-all Wave 1.

이미 전용 등록 경로(reference_phase1/2/3, image_steps state_variant,
scene_persistence, floor_plan_render, background_render 등)로 ``image_asset`` 에
insert 되는 **최종물**에, 캔버스 lineage 컬럼을 **보강(annotate)** 한다.

★중복 0 원칙(Codex 합의): capture sink 의 신규 insert 와 섞지 않는다. 본 함수는
기존 ImageAsset row(또는 commit 전 in-memory 객체)에 컬럼만 채우며,
``is_intermediate`` / ``asset_type`` 을 건드리지 않는다 (최종물은 영원히
is_intermediate=False 유지). commit 은 호출자가 한다.

설정 컬럼: pipeline_role / stage / input_image_ids(UUID-only) /
generation_call_id / pipeline_metadata_json(merge).
"""

from __future__ import annotations

import json
import logging
from typing import Any, Dict, List, Optional

logger = logging.getLogger(__name__)


def annotate_generated_asset(
    asset: Any,
    *,
    pipeline_role: str,
    stage: Optional[str] = None,
    input_image_ids: Optional[List[str]] = None,
    generation_call_id: Optional[str] = None,
    pipeline_metadata: Optional[Dict[str, Any]] = None,
) -> Any:
    """최종 ImageAsset 객체에 lineage/role 컬럼을 보강한다 (non-fatal, no commit).

    Args:
        asset: ImageAsset ORM 인스턴스 (commit 전/후 무관).
        pipeline_role: 캔버스 색/필터용 역할 (reference_face / reference_outlook /
            reference_composite / character_state_variant / scene_still /
            floor_plan / background_render 등).
        stage: 파이프라인 stage 라벨 (없으면 pipeline_role 재사용).
        input_image_ids: 입력 이미지 **asset UUID** 리스트(구조키/경로/라벨 금지).
            None=lineage 미상(루트), []=입력 없음을 명시. None/빈 문자열 원소는 제거.
        generation_call_id: llm_call_log 연결용 call id.
        pipeline_metadata: 추가 메타(기존 pipeline_metadata_json 에 merge).

    Returns:
        같은 asset (체이닝용). 예외는 삼키지 않고 로깅 후 asset 반환(non-fatal).
    """
    try:
        asset.pipeline_role = pipeline_role
        asset.stage = stage if stage is not None else pipeline_role

        if input_image_ids is not None:
            cleaned = [str(x) for x in input_image_ids if x is not None and str(x).strip()]
            asset.input_image_ids = json.dumps(cleaned)

        if generation_call_id is not None:
            asset.generation_call_id = generation_call_id

        if pipeline_metadata:
            existing: Dict[str, Any] = {}
            raw = getattr(asset, "pipeline_metadata_json", None)
            if raw:
                try:
                    parsed = json.loads(raw)
                    if isinstance(parsed, dict):
                        existing = parsed
                except (ValueError, TypeError):
                    existing = {}
            existing.update(pipeline_metadata)
            asset.pipeline_metadata_json = json.dumps(existing, ensure_ascii=False)
    except Exception:  # pragma: no cover - non-fatal annotate
        logger.warning(
            "annotate_generated_asset failed (non-fatal) role=%s", pipeline_role,
            exc_info=True,
        )
    return asset
