defmodule MicrowavepropWeb.LocationResolver do
  @moduledoc """
  Parses user-supplied location input into a normalized lat/lon map.

  Accepted formats:

    * `"32.9, -96.8"` — decimal coordinates (lat, lon)
    * `"EM12kx"` — Maidenhead grid square (4–10 chars)
    * `"W5XYZ"` — amateur callsign (resolved via QRZ + cache)
  """

  alias Microwaveprop.Radio.CallsignClient
  alias Microwaveprop.Radio.Maidenhead

  @type kind :: :coordinates | :grid | :callsign
  @type resolved :: %{
          required(:lat) => float(),
          required(:lon) => float(),
          required(:label) => String.t(),
          required(:kind) => kind(),
          optional(:grid) => String.t() | nil
        }

  @doc """
  Resolves an input string. Returns `{:ok, resolved}`, `{:error, msg}`,
  or `:empty` for nil/blank input — callers decide whether `:empty`
  is an error or a no-op.
  """
  @spec resolve(String.t() | nil) :: {:ok, resolved()} | {:error, String.t()} | :empty
  def resolve(nil), do: :empty

  def resolve(input) when is_binary(input) do
    trimmed = String.trim(input)

    cond do
      trimmed == "" -> :empty
      coordinate_pair?(trimmed) -> resolve_coordinates(trimmed)
      maidenhead?(trimmed) -> resolve_grid(trimmed)
      true -> resolve_callsign(trimmed)
    end
  end

  @doc "True when `input` parses as a `lat, lon` decimal pair."
  @spec coordinate_pair?(String.t()) :: boolean()
  def coordinate_pair?(input) do
    case String.split(input, ",", parts: 2) do
      [a, b] ->
        match?({_, _}, Float.parse(String.trim(a))) and
          match?({_, _}, Float.parse(String.trim(b)))

      _ ->
        false
    end
  end

  @doc "True when `input` looks like a Maidenhead grid square (4-10 chars, AA00 prefix)."
  @spec maidenhead?(String.t()) :: boolean()
  def maidenhead?(input) do
    len = String.length(input)
    len >= 4 and len <= 10 and Regex.match?(~r/^[A-Ra-r]{2}[0-9]{2}/i, input)
  end

  defp resolve_coordinates(input) do
    [lat_s, lon_s] = String.split(input, ",", parts: 2)
    {lat, _} = Float.parse(String.trim(lat_s))
    {lon, _} = Float.parse(String.trim(lon_s))

    {:ok,
     %{
       lat: lat,
       lon: lon,
       label: "#{Float.round(lat, 3)}, #{Float.round(lon, 3)}",
       kind: :coordinates
     }}
  end

  defp resolve_grid(input) do
    case Maidenhead.to_latlon(input) do
      {:ok, {lat, lon}} ->
        {:ok, %{lat: lat, lon: lon, label: String.upcase(input), kind: :grid}}

      :error ->
        {:error, "Invalid grid square: #{input}"}
    end
  end

  defp resolve_callsign(input) do
    case CallsignClient.locate(input) do
      {:ok, info} ->
        {:ok,
         %{
           lat: info.lat,
           lon: info.lon,
           label: String.upcase(info.callsign),
           grid: info.gridsquare,
           kind: :callsign
         }}

      {:error, reason} ->
        {:error, "Could not find #{input}: #{reason}"}
    end
  end
end
