defmodule Microwaveprop.Rover.LinkMargin do
  @moduledoc """
  Per-link SNR-margin math for the rover planner.

  Maps a propagation grid score (0-100) into a predicted SNR using the
  linear calibration `(score − 50) × 0.5`, then subtracts the mode's
  required SNR threshold to yield "dB above decode".
  """

  alias Microwaveprop.Propagation.ModeThresholds
  alias Microwaveprop.Propagation.ScoresFile

  @spec score_to_db(integer() | nil) :: float() | nil
  def score_to_db(nil), do: nil
  def score_to_db(score) when is_integer(score), do: (score - 50) * 0.5

  @doc """
  Pure helper: compute the link margin from an already-resolved
  grid score and a mode atom. Used by tests and `Rover.Compute`,
  which fetches the score itself.
  """
  @spec link_margin_from_score(integer() | nil, ModeThresholds.mode()) :: float() | nil
  def link_margin_from_score(nil, _mode), do: nil

  def link_margin_from_score(score, mode) when is_integer(score) do
    score_to_db(score) - ModeThresholds.required_snr_db(mode)
  end

  @doc """
  Look up the score for the candidate cell and return the link margin.
  """
  @spec link_margin_db(
          non_neg_integer(),
          DateTime.t(),
          {float(), float()},
          {float(), float()},
          ModeThresholds.mode()
        ) :: float() | nil
  def link_margin_db(band_mhz, %DateTime{} = valid_time, {c_lat, c_lon}, {_s_lat, _s_lon}, mode) do
    case ScoresFile.read_point(band_mhz, valid_time, c_lat, c_lon) do
      nil -> nil
      score -> link_margin_from_score(score, mode)
    end
  end
end
