defmodule AprsmeWeb.MapLive.State do
  @moduledoc """
  Manages initial LiveView socket state assignment for the map view.
  Extracted from Index to reduce module size.
  """

  import Phoenix.Component, only: [assign: 2]
  import Phoenix.LiveView, only: [connected?: 1, get_connect_params: 1]

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

  @doc """
  Assign default values to a fresh socket.
  """
  @spec default(Socket.t(), DateTime.t()) :: Socket.t()
  def default(socket, one_day_ago) do
    assign(socket,
      packets: [],
      visible_packets: %{},
      historical_packets: %{},
      page_title: "APRS Map",
      station_popup_open: false,
      map_bounds: %{
        north: 49.0,
        south: 24.0,
        east: -66.0,
        west: -125.0
      },
      map_center: UrlParams.default_center(),
      map_zoom: UrlParams.default_zoom(),
      packet_age_threshold: one_day_ago,
      map_ready: false,
      historical_loaded: false,
      bounds_update_timer: nil,
      pending_bounds: nil,
      initial_bounds_loaded: false,
      # Loading state management
      historical_loading: false,
      loading_generation: 0,
      pending_batch_tasks: [],
      # Overlay controls
      overlay_callsign: "",
      slideover_open: true,
      # Track when last update occurred for real-time display in map sidebar
      # Updated when packets are processed or map bounds change
      last_update_at: DateTime.utc_now(),
      # Add missing assigns for components
      loading: false,
      connection_status: connection_status(),
      show_all_packets: true
    )
  end

  @doc """
  Finalize mount assigns with computed values.
  Takes the socket after basic setup and an opts map.
  """
  @spec finalize(Socket.t(), map()) :: Socket.t()
  def finalize(socket, %{
        initial_bounds: initial_bounds,
        final_map_center: final_map_center,
        final_map_zoom: final_map_zoom,
        should_skip_initial_url_update: should_skip_initial_url_update,
        tracked_callsign: tracked_callsign,
        deployed_at: deployed_at,
        one_day_ago: one_day_ago
      }) do
    # Don't override trail_duration and historical_hours if they're already set
    trail_duration = Map.get(socket.assigns, :trail_duration, "1")
    historical_hours = Map.get(socket.assigns, :historical_hours, "1")
    packet_age_threshold = Map.get(socket.assigns, :packet_age_threshold, one_day_ago)

    # If tracking a specific callsign, fetch their latest packet and other SSIDs
    {tracked_callsign_latest_packet, other_ssids} =
      if tracked_callsign == "" do
        {nil, []}
      else
        try do
          packet = Packets.get_latest_packet_for_callsign(tracked_callsign)
          ssids = Packets.get_other_ssids(tracked_callsign)
          {packet, ssids}
        rescue
          # Handle database connection errors gracefully (especially in tests)
          DBConnection.OwnershipError ->
            {nil, []}

          _ ->
            {nil, []}
        end
      end

    # Start packet batcher for efficient updates
    {:ok, batcher_pid} = PacketBatcher.start_link(self())
    Process.monitor(batcher_pid)

    # Determine initial slideover state from client viewport width
    slideover_open = slideover_open?(socket)

    assign(socket,
      map_ready: false,
      map_bounds: initial_bounds,
      map_center: final_map_center,
      map_zoom: final_map_zoom,
      should_skip_initial_url_update: should_skip_initial_url_update,
      overlay_callsign: "",
      tracked_callsign: tracked_callsign,
      tracked_callsign_latest_packet: tracked_callsign_latest_packet,
      other_ssids: other_ssids,
      trail_duration: trail_duration,
      historical_hours: historical_hours,
      packet_age_threshold: packet_age_threshold,
      slideover_open: slideover_open,
      deployed_at: deployed_at,
      buffer_timer: nil,
      batcher_pid: batcher_pid,
      station_popup_open: false,
      initial_bounds_loaded: false,
      # Always load historical data on initial page load
      needs_initial_historical_load: true,
      # Loading state management
      historical_loading: false,
      loading_generation: 0,
      pending_batch_tasks: [],
      # Add missing assigns for components
      loading: false,
      connection_status: connection_status(),
      show_all_packets: true
    )
  end

  @doc """
  Determine whether the slideover should be open on initial render.
  """
  @spec slideover_open?(Socket.t()) :: boolean()
  def slideover_open?(socket) do
    if connected?(socket) do
      viewport_width = get_connect_params(socket)["viewport_width"] || 1024
      viewport_width >= 1024
    else
      true
    end
  end

  @doc """
  Get the initial APRS connection status.
  """
  @spec connection_status() :: String.t()
  def connection_status do
    # Check if APRS connection is disabled
    connection_disabled = Application.get_env(:aprsme, :disable_aprs_connection, false)
    if connection_disabled, do: "disconnected", else: "connected"
  end

  @doc """
  Default map center used by UrlParams.
  """
  @spec default_center() :: %{lat: float(), lng: float()}
  def default_center, do: UrlParams.default_center()

  @doc """
  Default map zoom used by UrlParams.
  """
  def default_zoom, do: UrlParams.default_zoom()

  @doc """
  Calculate initial bounds from center and zoom.
  """
  @spec initial_bounds(%{lat: float(), lng: float()}, integer()) :: map()
  def initial_bounds(center, zoom) do
    BoundsUtils.calculate_bounds_from_center_and_zoom(center, zoom)
  end
end
