#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
offline_bg.validate — ③ 方向闭环校验。产线必需，不是可选项。

🔴 为什么必需：venice / 扩散模型按语义控光照方向不可靠（同一 prompt 两轮结果相反）。
   即便用渐变图注入，也存在被原图自带强光照压过去的情况。所以：
   生成 → 客观测量方向 → 不符就换参数重来。

🔴 判据必须是「相对原图的增量」，不能用绝对亮度差：
   v8 的原背景本身 lr_diff=+45.05（提示词里窗户在左），若重打光失败退化成原图，
   绝对判据会把它当成"左光成功"。实测增量 left +81.0 / right −166.0，
   阈值 30 取较小裕度的 ~37%。
"""
from typing import Dict, List, Optional

import cv2
import numpy as np

from .config import DEFAULT, OfflineBGConfig
from .validated import lr_brightness_diff

# side → (判据轴, 期望符号)
EXPECT = {
    "left":   ("lr", +1),
    "right":  ("lr", -1),
    "top":    ("tb", +1),
    "bottom": ("tb", -1),
}


def tb_brightness_diff(img: np.ndarray) -> float:
    """上半 − 下半 平均亮度差。与 validated.lr_brightness_diff 同构的纵轴版本。"""
    g = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY).astype(np.float32)
    return float(g[:g.shape[0] // 2].mean() - g[g.shape[0] // 2:].mean())


def axis_diff(img: np.ndarray, axis: str) -> float:
    return lr_brightness_diff(img) if axis == "lr" else tb_brightness_diff(img)


def score_direction(src: np.ndarray, out: np.ndarray, side: str,
                    cfg: OfflineBGConfig = DEFAULT) -> Dict:
    """给一次重打光结果打分。signed_delta 越大越好，ok=True 才算方向真的变了。

    src/out 必须同尺寸（都在 work_size 上比）。
    """
    if side == "front":
        # front 目标是"抹平方向性"，判据取两轴不对称度的下降量
        a0 = abs(lr_brightness_diff(src)) + abs(tb_brightness_diff(src))
        a1 = abs(lr_brightness_diff(out)) + abs(tb_brightness_diff(out))
        signed = a0 - a1
        return {"side": side, "axis": "sym", "src_diff": round(a0, 2), "out_diff": round(a1, 2),
                "delta": round(signed, 2), "signed_delta": round(signed, 2),
                "ok": bool(signed >= cfg.min_delta),
                "reason": "不对称度下降量未达阈值" if signed < cfg.min_delta else "",
                "validated_criterion": False}   # front 判据未经实测标定

    if side not in EXPECT:
        raise ValueError(f"未知光照方向: {side}")
    axis, sign = EXPECT[side]
    s = axis_diff(src, axis)
    o = axis_diff(out, axis)
    delta = o - s
    signed = delta * sign                       # 期望方向上的增量，越大越好

    ok, reason = True, ""
    if signed < cfg.min_delta:
        ok, reason = False, f"方向增量 {signed:.1f} < 阈值 {cfg.min_delta}"
    elif cfg.require_absolute_sign and o * sign <= 0:
        ok, reason = False, f"增量够但绝对符号不对（out_diff={o:.1f}，期望符号 {sign:+d}）"
    return {"side": side, "axis": axis, "src_diff": round(s, 2), "out_diff": round(o, 2),
            "delta": round(delta, 2), "signed_delta": round(signed, 2),
            "ok": ok, "reason": reason, "validated_criterion": True}


def best_of(scores: List[Dict]) -> Optional[int]:
    """全败时挑一个最接近的（signed_delta 最大）落库，并标 validated=false。
    宁可留一张"方向偏弱"的可用背景，也不要静默把失败品当成功品。"""
    if not scores:
        return None
    return int(max(range(len(scores)), key=lambda i: scores[i]["signed_delta"]))
