defmodule MicrowavepropWeb.SkewtLocationResolver do
  @moduledoc """
  Turn a single search-bar string ("EM12kp", "W5ISP", or
  "Plano, TX") into a `{lat, lon, label}` triple for the Skew-T
  page. Tries the cheapest classification first (grid → callsign →
  geocoder) so a typo on a grid square doesn't burn a Google API
  request before the user has a chance to correct it.
  """

  alias Microwaveprop.CallsignLocation
  alias Microwaveprop.Geocoder
  alias Microwaveprop.Radio.Maidenhead

  @grid_regex ~r/^[A-Ra-r]{2}[0-9]{2}([A-Xa-x]{2}([0-9]{2}([A-Xa-x]{2})?)?)?$/
  @callsign_regex ~r/^[A-Z0-9]{1,3}[0-9][A-Z]{1,4}$/

  @type classification :: :grid | :callsign | :address

  @type result :: %{
          lat: float(),
          lon: float(),
          label: String.t(),
          source: classification()
        }

  @spec classify(String.t()) :: classification()
  def classify(query) when is_binary(query) do
    cleaned = query |> String.trim() |> String.upcase()

    cond do
      Regex.match?(@grid_regex, cleaned) -> :grid
      Regex.match?(@callsign_regex, cleaned) -> :callsign
      true -> :address
    end
  end

  @spec resolve(String.t()) :: {:ok, result()} | {:error, String.t()}
  def resolve(query) when is_binary(query) do
    cleaned = String.trim(query)

    case classify(cleaned) do
      :grid -> resolve_grid(cleaned)
      :callsign -> resolve_callsign(cleaned)
      :address -> resolve_address(cleaned)
    end
  end

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

      :error ->
        {:error, "could not parse '#{query}' as a Maidenhead grid square"}
    end
  end

  defp resolve_callsign(query) do
    upper = String.upcase(query)

    case CallsignLocation.lookup(upper) do
      {:ok, %{latitude: lat, longitude: lon, gridsquare: grid, name: name}} ->
        label =
          [upper, name, grid]
          |> Enum.reject(&(is_nil(&1) or &1 == ""))
          |> Enum.uniq()
          |> Enum.join(" — ")

        {:ok, %{lat: lat, lon: lon, label: label, source: :callsign}}

      {:error, reason} when is_binary(reason) ->
        {:error, "callsign lookup failed: #{reason}"}

      {:error, _} ->
        {:error, "callsign lookup failed"}
    end
  end

  defp resolve_address(query) do
    case Geocoder.geocode(query) do
      {:ok, %{lat: lat, lon: lon}} ->
        {:ok, %{lat: lat, lon: lon, label: query, source: :address}}

      {:error, reason} when is_binary(reason) ->
        {:error, "geocode failed: #{reason}"}

      {:error, _} ->
        {:error, "geocode failed"}
    end
  end
end
