defmodule Microwaveprop.Propagation.Region do
  @moduledoc """
  Classifies CONUS grid points into climatological regions for
  band-specific seasonal scoring adjustments.

  The meteorologist's April 2026 review noted that the existing
  uniform seasonal scoring is wrong: Gulf coast August is drier and
  better for propagation than June/July, while Iowa August is peak
  corn evapotranspiration with brutally high dewpoints. Same month,
  opposite effect.

  This module provides:
  - `for_point/2` — classifies a lat/lon into one of ~8 regions
  - `seasonal_adjustment/2` — returns a multiplier (0.7-1.3) that
    the scorer applies on top of the band's `seasonal_base` score.

  Region boundaries are deliberately simple bounding boxes rather
  than precise climate zone shapefiles. The scoring impact of getting
  a boundary pixel wrong is a few points out of 100; the impact of
  having no regional adjustment at all is the entire seasonal factor
  being wrong for half the country.
  """

  @regions [
    {:gulf_coast, {25.0, 32.0}, {-100.0, -80.0}},
    {:southeast, {30.0, 37.0}, {-90.0, -75.0}},
    {:southern_plains, {32.0, 38.0}, {-105.0, -93.0}},
    {:corn_belt, {38.0, 48.0}, {-100.0, -82.0}},
    {:northeast, {38.0, 48.0}, {-82.0, -67.0}},
    {:desert_southwest, {30.0, 38.0}, {-120.0, -105.0}},
    {:pacific_northwest, {42.0, 50.0}, {-125.0, -115.0}},
    {:mountain_west, {38.0, 48.0}, {-115.0, -100.0}}
  ]

  @doc """
  Classify a lat/lon point into a climatological region.

  Returns an atom like `:gulf_coast`, `:corn_belt`, etc.
  Returns `:other` for points outside any defined region (including
  non-CONUS).
  """
  @spec for_point(float, float) :: atom
  def for_point(lat, lon) do
    Enum.find_value(@regions, :other, fn {name, {lat_min, lat_max}, {lon_min, lon_max}} ->
      if lat >= lat_min and lat < lat_max and lon >= lon_min and lon < lon_max do
        name
      end
    end)
  end

  # Regional seasonal adjustments: multipliers on the band's seasonal_base
  # score by month. 1.0 = no change; > 1.0 = better than the base suggests;
  # < 1.0 = worse. Only months/regions where the uniform base is known to
  # be wrong get non-1.0 values.
  #
  # These are hand-tuned starting points based on the meteorologist's
  # qualitative guidance. Phase 9 recalibration will refine them from
  # backtest data.
  @seasonal_adjustments %{
    gulf_coast: %{
      # Gulf August is drier than June/July → better propagation
      6 => 0.95,
      7 => 0.95,
      8 => 1.15,
      9 => 1.10
    },
    corn_belt: %{
      # Iowa July/August: corn evapotranspiration → high dewpoints → worse
      6 => 1.05,
      7 => 0.85,
      8 => 0.80,
      9 => 0.95
    },
    southeast: %{
      # Similar to Gulf but less extreme
      7 => 0.97,
      8 => 1.08
    },
    southern_plains: %{
      # TX panhandle drier than Gulf but wetter than desert
      8 => 1.05
    },
    desert_southwest: %{
      # Monsoon July/Aug brings moisture → briefly good ducting
      7 => 1.10,
      8 => 1.10,
      9 => 1.05
    },
    pacific_northwest: %{
      # Marine layer summer inversions
      6 => 1.15,
      7 => 1.20,
      8 => 1.20,
      9 => 1.10
    }
  }

  @doc """
  Seasonal score multiplier for a given region and month (1-12).

  Returns a float in [0.7, 1.3]. The scorer multiplies the band's
  `seasonal_base[month]` by this value. Unknown regions or months
  without an adjustment return 1.0.
  """
  @spec seasonal_adjustment(atom, integer) :: float
  def seasonal_adjustment(region, month) do
    @seasonal_adjustments
    |> Map.get(region, %{})
    |> Map.get(month, 1.0)
  end
end
