defmodule Microwaveprop.Rover.Prominence do
  @moduledoc """
  Local terrain prominence per rover cell: how far the cell sits above
  the average elevation of its 8 immediate neighbors at SRTM tile scale.

  Used by the rover scorer to favor *broad* hilltops over isolated SRTM
  voxels that just happen to be tall — a hill big enough to actually
  park on and run a station, not a tree poking through the data.
  """

  alias Microwaveprop.Rover.Elevation

  @ring_offset_deg 0.012
  @ring_offsets [
    {@ring_offset_deg, 0.0},
    {-@ring_offset_deg, 0.0},
    {0.0, @ring_offset_deg},
    {0.0, -@ring_offset_deg},
    {@ring_offset_deg, @ring_offset_deg},
    {@ring_offset_deg, -@ring_offset_deg},
    {-@ring_offset_deg, @ring_offset_deg},
    {-@ring_offset_deg, -@ring_offset_deg}
  ]

  @type latlon :: {float(), float()}

  @doc """
  Returns `%{ {lat, lon} => prominence_m | nil }` for each cell.

  `prominence_m = cell_elev - mean(ring_elevs)`. ~1.3 km ring radius
  matches the scale of the kind of hill a rover wants to park on.
  """
  @spec prominence_map([map()], keyword()) :: %{latlon() => integer() | nil}
  def prominence_map(cells, opts \\ []) do
    elev_lookup = Keyword.get(opts, :elev_lookup, &Elevation.lookup_many/1)

    cell_points = Enum.map(cells, fn c -> {c.lat, c.lon} end)

    ring_points =
      cells
      |> Enum.flat_map(&ring_for/1)
      |> Enum.uniq()

    elev = elev_lookup.(cell_points ++ ring_points)

    Map.new(cells, fn cell ->
      key = {cell.lat, cell.lon}
      cell_elev = Map.get(elev, key)

      ring =
        cell
        |> ring_for()
        |> Enum.map(&Map.get(elev, &1))
        |> Enum.reject(&is_nil/1)

      prominence =
        if is_integer(cell_elev) and ring != [],
          do: cell_elev - round(Enum.sum(ring) / length(ring))

      {key, prominence}
    end)
  end

  defp ring_for(%{lat: lat, lon: lon}) do
    Enum.map(@ring_offsets, fn {dlat, dlon} -> {lat + dlat, lon + dlon} end)
  end
end
