defmodule AprsmeWeb.MapLive.Navigation do
  @moduledoc """
  Handles geolocation, callsign tracking, and map navigation functionality.
  """

  import Phoenix.Component, only: [assign: 3]

  alias Aprsme.Packets
  alias AprsmeWeb.Live.Shared.ParamUtils
  alias AprsmeWeb.MapLive.UrlParams
  alias Phoenix.LiveView
  alias Phoenix.LiveView.Socket

  require Logger

  @doc """
  Determine map location based on URL parameters and IP geolocation.
  Returns {map_center, zoom, should_skip_initial_url_update}.
  """
  @spec determine_map_location(map(), map()) :: {map(), integer(), boolean()}
  def determine_map_location(params, session) do
    {url_center, url_zoom} = UrlParams.parse_map_params(params)
    has_explicit_url_params = UrlParams.has_explicit_url_params?(params)
    geo = session["ip_geolocation"]

    Logger.info(
      "determine_map_location: session ip_geolocation=#{inspect(geo)}, has_explicit_url_params=#{has_explicit_url_params}"
    )

    resolve_location(geo, has_explicit_url_params, url_center, url_zoom)
  end

  # Explicit URL params always win over geolocation.
  defp resolve_location(_geo, true, url_center, url_zoom) do
    Logger.info("Using URL params over geolocation: center=#{inspect(url_center)}, zoom=#{url_zoom}")
    {url_center, url_zoom, false}
  end

  # Valid geolocation + no explicit URL params → use the geolocation.
  defp resolve_location(%{"lat" => lat, "lng" => lng}, false, _url_center, _url_zoom)
       when is_number(lat) and is_number(lng) do
    Logger.info("Using IP geolocation: lat=#{lat}, lng=#{lng}, zoom=11")
    {%{lat: lat, lng: lng}, 11, true}
  end

  # No usable geolocation → fall back to URL/defaults.
  defp resolve_location(_geo, has_explicit_url_params, url_center, url_zoom) do
    Logger.info("No IP geolocation in session, using defaults")
    {url_center, url_zoom, !has_explicit_url_params}
  end

  @doc """
  Handle callsign tracking by finding the latest packet for the callsign.
  Returns {final_map_center, final_map_zoom}.
  """
  @spec handle_callsign_tracking(binary(), map(), integer(), boolean()) :: {map(), integer()}
  def handle_callsign_tracking("", map_center, map_zoom, _), do: {map_center, map_zoom}
  def handle_callsign_tracking(_, map_center, map_zoom, true), do: {map_center, map_zoom}

  def handle_callsign_tracking(tracked_callsign, map_center, map_zoom, false) do
    case Packets.get_latest_packet_for_callsign(tracked_callsign) do
      %{lat: lat, lon: lon} when is_number(lat) and is_number(lon) ->
        {%{lat: lat, lng: lon}, 12}

      _ ->
        {map_center, map_zoom}
    end
  rescue
    # Handle database connection errors gracefully (especially in tests).
    _error -> {map_center, map_zoom}
  end

  @doc """
  Handle callsign search with validation.
  """
  @spec handle_callsign_search(binary(), Socket.t()) :: {:noreply, Socket.t()}
  def handle_callsign_search("", socket), do: {:noreply, socket}

  def handle_callsign_search(callsign, socket) do
    dispatch_callsign_search(ParamUtils.valid_callsign?(callsign), callsign, socket)
  end

  defp dispatch_callsign_search(true, callsign, socket) do
    {:noreply, LiveView.push_navigate(socket, to: "/#{callsign}")}
  end

  defp dispatch_callsign_search(false, _callsign, socket) do
    {:noreply, LiveView.put_flash(socket, :error, "Invalid callsign format")}
  end

  @doc """
  Update map center and zoom to specific location.
  """
  @spec update_and_zoom_to_location(Socket.t(), float(), float(), integer()) :: Socket.t()
  def update_and_zoom_to_location(socket, lat, lng, zoom) do
    socket
    |> assign(:map_center, %{lat: lat, lng: lng})
    |> assign(:map_zoom, zoom)
    |> LiveView.push_event("zoom_to_location", %{lat: lat, lng: lng, zoom: zoom})
  end

  @doc """
  Zoom to current location stored in socket.
  """
  @spec zoom_to_current_location(Socket.t()) :: Socket.t()
  def zoom_to_current_location(socket) do
    LiveView.push_event(socket, "zoom_to_location", %{
      lat: socket.assigns.map_center.lat,
      lng: socket.assigns.map_center.lng,
      zoom: socket.assigns.map_zoom
    })
  end
end
