"""照明均一性の空間分析モジュール（中心-周辺勾配）."""

import numpy


def calc_distance_map(height: int, width: int) -> numpy.ndarray:
    """各ピクセルの中心からの正規化楕円距離を算出する．

    楕円距離を使用することで、縦横比が異なる画像でも中心を基準に
    等方的なゾーン分類が行えるようにする．

    Args:
        height: 画像の高さ（ピクセル）．
        width: 画像の幅（ピクセル）．

    Returns:
        正規化楕円距離の NumPy 配列（H x W, float64）．値域は 0.0〜1.0．
    """
    cy = height / 2
    cx = width / 2

    # mgrid で全ピクセルの y, x 座標を生成
    y_coords, x_coords = numpy.mgrid[0:height, 0:width]

    # 中心を原点とした楕円距離（縦横それぞれ中心距離で正規化）
    dist = numpy.sqrt(((x_coords - cx) / cx) ** 2 + ((y_coords - cy) / cy) ** 2)

    # 最大値で再正規化して 0.0〜1.0 に揃える
    max_dist = dist.max()
    if max_dist > 0:
        dist = dist / max_dist

    return dist


def classify_zones(
    distance_map: numpy.ndarray,
    center_threshold: float = 0.33,
    periphery_threshold: float = 0.66,
) -> numpy.ndarray:
    """距離マップを 3 ゾーン（中心・中間・周辺）に分類する．

    ゾーン境界を正規化距離の三等分付近に置くことで、
    均一性評価の標準的な領域分割に対応する．

    Args:
        distance_map: 正規化楕円距離の NumPy 配列（H x W, float64）．
        center_threshold: 中心ゾーンの上限距離（デフォルト: 0.33）．
        periphery_threshold: 中間ゾーンの上限距離（デフォルト: 0.66）．

    Returns:
        ゾーンマップの NumPy 配列（H x W, uint8）．
        0=center, 1=middle, 2=periphery．
    """
    zone_map = numpy.zeros(distance_map.shape, dtype=numpy.uint8)
    # 中間ゾーンと周辺ゾーンを順に上書きする
    zone_map[distance_map >= center_threshold] = 1
    zone_map[distance_map >= periphery_threshold] = 2
    return zone_map


def calc_zone_stats(luminance: numpy.ndarray, zone_map: numpy.ndarray) -> dict:
    """ゾーン別の輝度統計と中心-周辺勾配指標を算出する．

    center_periphery_ratio が 1.0 に近いほど均一、
    gradient_magnitude が 0 に近いほど空間的ムラが小さい．

    Args:
        luminance: 輝度画像の NumPy 配列（H x W, float64）．
        zone_map: ゾーンマップの NumPy 配列（H x W, uint8）．
            0=center, 1=middle, 2=periphery．

    Returns:
        ゾーン統計を格納した辞書．キーは:
        "center_mean", "center_std", "middle_mean", "middle_std",
        "periphery_mean", "periphery_std",
        "center_periphery_ratio", "gradient_magnitude"．
    """
    center_pixels = luminance[zone_map == 0]
    middle_pixels = luminance[zone_map == 1]
    periphery_pixels = luminance[zone_map == 2]

    center_mean = float(numpy.mean(center_pixels))
    center_std = float(numpy.std(center_pixels))
    middle_mean = float(numpy.mean(middle_pixels))
    middle_std = float(numpy.std(middle_pixels))
    periphery_mean = float(numpy.mean(periphery_pixels))
    periphery_std = float(numpy.std(periphery_pixels))

    # 中心/周辺比: 照明が中心に集中しているかを示す
    center_periphery_ratio = (
        center_mean / periphery_mean if periphery_mean != 0.0 else float("inf")
    )

    # 勾配量（%）: 中心と周辺の輝度差を中心輝度に対する比率で表す
    gradient_magnitude = (
        (center_mean - periphery_mean) / center_mean * 100
        if center_mean != 0.0
        else float("inf")
    )

    return {
        "center_mean": center_mean,
        "center_std": center_std,
        "middle_mean": middle_mean,
        "middle_std": middle_std,
        "periphery_mean": periphery_mean,
        "periphery_std": periphery_std,
        "center_periphery_ratio": center_periphery_ratio,
        "gradient_magnitude": gradient_magnitude,
    }


def calc_radial_profile(
    luminance: numpy.ndarray,
    distance_map: numpy.ndarray,
    num_bins: int = 20,
) -> numpy.ndarray:
    """放射状輝度プロファイルを算出する．

    距離ビンごとに平均輝度を集計することで、
    中心から周辺にかけての輝度変化の傾向を把握する．

    Args:
        luminance: 輝度画像の NumPy 配列（H x W, float64）．
        distance_map: 正規化楕円距離の NumPy 配列（H x W, float64）．
        num_bins: 距離方向の分割数（デフォルト: 20）．

    Returns:
        放射状プロファイルの NumPy 配列（num_bins x 2, float64）．
        列 0 = ビン中心距離, 列 1 = 平均輝度．
    """
    bin_edges = numpy.linspace(0.0, 1.0, num_bins + 1)
    profile = numpy.zeros((num_bins, 2), dtype=numpy.float64)

    for i in range(num_bins):
        bin_center = (bin_edges[i] + bin_edges[i + 1]) / 2.0
        mask = (distance_map >= bin_edges[i]) & (distance_map < bin_edges[i + 1])

        # 最外ビンは上端を含める
        if i == num_bins - 1:
            mask = distance_map >= bin_edges[i]

        pixels = luminance[mask]
        mean_lum = float(numpy.mean(pixels)) if pixels.size > 0 else 0.0

        profile[i, 0] = bin_center
        profile[i, 1] = mean_lum

    return profile


def calc_radial_min_max_ratio(radial_profile: numpy.ndarray) -> dict:
    """動径プロファイルの最小/最大輝度比を算出する．

    20 点の輪帯平均輝度のうち最小値を最大値で割った比（0〜1）を返す．
    1 に近いほど均一で，中心明・周辺暗の単調減衰では「最外輪帯 / 最内輪帯」に一致する．
    全 20 点の min/max を使うため，非単調プロファイルにもロバストに対応する．

    Args:
        radial_profile: 放射状プロファイルの NumPy 配列（N x 2, float64）．
            列 0 = ビン中心距離, 列 1 = 平均輝度．

    Returns:
        動径 min/max 比を格納した辞書．キーは:
        "radial_min_max_ratio" (float): 最小輝度 / 最大輝度（0〜1）．
            最大輝度が 0 の場合は float("inf")．
        "radial_min_distance" (float): 最小輝度ビンの正規化距離．
        "radial_min" (float): プロファイルの最小輝度値．
        "radial_max" (float): プロファイルの最大輝度値．
    """
    luminances = radial_profile[:, 1]
    distances = radial_profile[:, 0]

    radial_min = float(numpy.min(luminances))
    radial_max = float(numpy.max(luminances))

    # max が 0 のときはゼロ除算を回避して inf を返す（calc_zone_stats の既存パターンに倣う）
    radial_min_max_ratio = (
        radial_min / radial_max if radial_max != 0.0 else float("inf")
    )

    # 最小輝度をとるビンの正規化距離（補助情報）
    min_idx = int(numpy.argmin(luminances))
    radial_min_distance = float(distances[min_idx])

    return {
        "radial_min_max_ratio": radial_min_max_ratio,
        "radial_min_distance": radial_min_distance,
        "radial_min": radial_min,
        "radial_max": radial_max,
    }


def calc_spatial_uniformity(luminance: numpy.ndarray) -> dict:
    """空間的均一性指標を一括算出するファサード関数．

    中心-周辺勾配の定量化に必要な distance_map, zone_map,
    zone_stats, radial_profile を一度の呼び出しで取得できる．

    Args:
        luminance: 輝度画像の NumPy 配列（H x W, float64）．

    Returns:
        空間分析結果を格納した辞書．キーは:
        "zone_stats" (dict), "radial_profile" (numpy.ndarray),
        "zone_map" (numpy.ndarray),
        "radial_min_max_ratio" (float), "radial_min_distance" (float),
        "radial_min" (float), "radial_max" (float)．
    """
    height, width = luminance.shape
    distance_map = calc_distance_map(height, width)
    zone_map = classify_zones(distance_map)
    zone_stats = calc_zone_stats(luminance, zone_map)
    radial_profile = calc_radial_profile(luminance, distance_map)
    radial_ratio_stats = calc_radial_min_max_ratio(radial_profile)

    return {
        "zone_stats": zone_stats,
        "radial_profile": radial_profile,
        "zone_map": zone_map,
        **radial_ratio_stats,
    }
