defmodule AprsmeWeb.MapLive.RfPath do
  @moduledoc """
  Handles RF path parsing and visualization for APRS packets.
  """

  alias Aprsme.Packets
  alias AprsmeWeb.Live.Shared.ParamUtils

  # APRS aliases that are not real stations
  @path_aliases ~w(WIDE WIDE1 WIDE2 WIDE3 WIDE4 WIDE5 WIDE6 WIDE7
    RELAY TRACE ECHO HOP DMR)

  @doc """
  Parse RF path string to extract digipeater/igate stations.

  APRS-IS path format: [RF digipeaters...],qA[R/S/O/C],[igate]

  - Everything before the qA* element = RF path (digipeaters)
  - The element after qA* = the igate that received/gated the packet
  - TCPIP/NOGATE paths are Internet-only and return empty
  - WIDE*, RELAY, TRACE, etc. are aliases, not real stations
  """
  @spec parse_rf_path(binary()) :: list(binary())
  def parse_rf_path(""), do: []

  def parse_rf_path(path) when is_binary(path) do
    if String.contains?(path, "TCPIP") or String.contains?(path, "NOGATE") do
      []
    else
      elements = path |> String.split(",") |> Enum.map(&String.trim/1)

      {rf_elements, igate_elements} = split_at_q_construct(elements)

      (rf_elements ++ igate_elements)
      |> Enum.map(&extract_callsign_from_path_element/1)
      |> Enum.filter(&(&1 != ""))
      |> Enum.reject(&path_alias?/1)
      |> Enum.uniq()
    end
  end

  def parse_rf_path(_), do: []

  @doc """
  Get positions for path stations from the database.
  """
  @spec get_path_station_positions(list(binary()), Phoenix.LiveView.Socket.t()) :: list(map())
  def get_path_station_positions(path_stations, _socket) do
    path_stations
    |> Enum.take(10)
    |> Packets.get_latest_positions_for_callsigns()
  end

  defp split_at_q_construct(elements) do
    q_index = Enum.find_index(elements, &String.starts_with?(&1, "qA"))

    case q_index do
      nil ->
        # No q-construct found — treat all elements as RF path
        {elements, []}

      idx ->
        rf_elements = Enum.slice(elements, 0, idx)
        # The element after qA* is the igate
        igate_elements = Enum.slice(elements, (idx + 1)..-1//1)
        {rf_elements, igate_elements}
    end
  end

  defp path_alias?(callsign) do
    # Check base callsign (without SSID) against known aliases
    base = callsign |> String.split("-") |> hd()
    # Also filter out aliases with numeric suffixes like WIDE1, WIDE2, TRACE3, HOP7
    stripped = String.replace(base, ~r/\d+$/, "")

    base in @path_aliases or stripped in @path_aliases
  end

  defp extract_callsign_from_path_element(element) do
    # Remove asterisk (used flag) and extract callsign
    element
    |> String.replace("*", "")
    |> String.trim()
    |> String.upcase()
    |> validate_path_callsign()
  end

  defp validate_path_callsign(callsign) do
    if ParamUtils.valid_callsign?(callsign) do
      callsign
    else
      ""
    end
  end
end
