"""floor_plan_render — Phase 7 Step 4.

도면 PNG를 gpt-image-2로 생성. depends_on_fp가 있으면 multi-image edit, 없으면 generate.
Phase 5 chain_bg_render의 `render_node_image` 패턴을 참고해 도면 전용으로 단순화.
"""
from __future__ import annotations

import logging
from dataclasses import dataclass
from pathlib import Path
from typing import Any, List

from app.core.image_call_budget import (
    ImageCallBudgetExceeded,
    reserve_current_call,
)
from app.modules.llm.gpt_image_primitive import call_gpt_image_bytes

logger = logging.getLogger(__name__)


@dataclass
class FloorPlanRenderResult:
    fp_id: str = ""
    status: str = "failed"  # "ok" | "failed" | "rejected_path"
    png_path: str = ""
    attempts: int = 0
    error: str = ""
    ref_used: str = "text_only"  # "text_only" | "ref" | "refs_<N>"


def render_one_floor_plan(
    *,
    openai_client: Any,
    image_model: str,
    prompt: str,
    out_path: Path,
    ref_paths: List[Path],
    fp_id: str = "",
    size: str = "1024x1024",
    quality: str = "high",
    max_attempts: int = 3,
) -> FloorPlanRenderResult:
    """단일 도면 PNG 생성.

    - ref_paths가 비어있으면 ``client.images.generate`` (text-only).
    - ref_paths가 1개이면 ``client.images.edit`` with single image.
    - ref_paths가 2개 이상이면 ``client.images.edit`` with multi-image (ExitStack).

    Returns FloorPlanRenderResult — caller가 status/png_path 등을 자체 dict에 mapping.
    실패 시 status='failed' + error 채워서 반환 (예외 미전파).
    """
    valid_refs = [p for p in ref_paths if p is not None and p.exists()]
    res = FloorPlanRenderResult(
        fp_id=fp_id,
        status="failed",
        ref_used=(
            "text_only"
            if not valid_refs
            else "ref"
            if len(valid_refs) == 1
            else f"refs_{len(valid_refs)}"
        ),
    )

    for attempt in range(1, max_attempts + 1):
        res.attempts = attempt
        try:
            # gpt-image 호출 + b64 decode 는 primitive wrapper 로 위임(생성물 capture
            # 동시 수행, scope 미배선이면 no-op). reserve/검증/write/retry 는 여기 유지.
            call_kwargs = {
                "model": image_model,
                "size": size,
                "quality": quality,
                "n": 1,
            }
            if len(valid_refs) >= 2:
                reserve_current_call(source="floor_plan_render.edit_multi")
                png = call_gpt_image_bytes(
                    openai_client,
                    mode="edit",
                    prompt=prompt,
                    ref_paths=valid_refs,
                    call_kwargs=call_kwargs,
                    capture_role="floor_plan",
                    capture_metadata={
                        "budget_source": "floor_plan_render.edit_multi",
                        "ref_count": len(valid_refs),
                    },
                )
            elif len(valid_refs) == 1:
                reserve_current_call(source="floor_plan_render.edit_single")
                png = call_gpt_image_bytes(
                    openai_client,
                    mode="edit",
                    prompt=prompt,
                    ref_paths=valid_refs,
                    call_kwargs=call_kwargs,
                    capture_role="floor_plan",
                    capture_metadata={
                        "budget_source": "floor_plan_render.edit_single",
                        "ref_count": 1,
                    },
                )
            else:
                reserve_current_call(source="floor_plan_render.generate")
                png = call_gpt_image_bytes(
                    openai_client,
                    mode="generate",
                    prompt=prompt,
                    ref_paths=None,
                    call_kwargs=call_kwargs,
                    capture_role="floor_plan",
                    capture_metadata={
                        "budget_source": "floor_plan_render.generate",
                        "ref_count": 0,
                    },
                )

            if not png:
                raise RuntimeError("empty b64 response")
            out_path.write_bytes(png)
            res.status = "ok"
            res.png_path = str(out_path)
            res.error = ""
            return res
        except ImageCallBudgetExceeded:
            raise
        except Exception as exc:
            res.error = str(exc)[:200]
            logger.error(
                "render_one_floor_plan %s attempt %d/%d: %s",
                fp_id,
                attempt,
                max_attempts,
                exc,
            )
            if attempt >= max_attempts:
                return res

    return res
