defmodule AprsmeWeb.Live.SharedPacketHandler do
  @moduledoc """
  Shared functionality for handling packet updates in LiveView modules.
  Provides common patterns for packet filtering, enrichment, and processing.
  """

  alias Aprsme.Callsign
  alias Aprsme.DeviceCache
  alias Aprsme.EncodingUtils

  @doc """
  Handles incoming packet updates with filtering and processing.

  ## Options
    * `:filter_fn` - Function to determine if packet should be processed (required)
    * `:process_fn` - Function to process matching packets and update socket (required)
    * `:enrich_packet` - Whether to enrich packet with device info (default: true)
  """
  def handle_packet_update(packet, socket, opts) do
    filter_fn = Keyword.fetch!(opts, :filter_fn)
    process_fn = Keyword.fetch!(opts, :process_fn)
    enrich_packet? = Keyword.get(opts, :enrich_packet, true)

    dispatch_packet_update(filter_fn.(packet, socket), packet, socket, process_fn, enrich_packet?)
  end

  defp dispatch_packet_update(false, _packet, socket, _process_fn, _enrich?), do: {:noreply, socket}

  defp dispatch_packet_update(true, packet, socket, process_fn, enrich?) do
    packet |> maybe_enrich(enrich?) |> process_fn.(socket)
  end

  defp maybe_enrich(packet, true), do: packet |> sanitize_packet() |> enrich_with_device_info()
  defp maybe_enrich(packet, false), do: packet

  @doc """
  Checks if packet sender matches the given callsign.
  """
  def packet_matches_callsign?(packet, callsign) do
    packet_sender = Map.get(packet, "sender") || Map.get(packet, :sender, "")
    Callsign.matches?(packet_sender, callsign)
  end

  @doc """
  Checks if packet contains weather data.
  """
  def has_weather_data?(packet) do
    Enum.any?(EncodingUtils.weather_fields(), fn field ->
      value = Map.get(packet, field) || Map.get(packet, to_string(field))
      not is_nil(value)
    end)
  end

  @doc """
  Sanitizes packet strings to handle encoding issues.
  """
  def sanitize_packet(packet) do
    EncodingUtils.sanitize_packet(packet)
  end

  @doc """
  Enriches packet with device information.
  """
  def enrich_with_device_info(packet) do
    device_identifier = get_device_identifier(packet)

    device = lookup_device_info(device_identifier)

    packet
    |> Map.put(:device_model, device && device.model)
    |> Map.put(:device_vendor, device && device.vendor)
    |> Map.put(:device_contact, device && device.contact)
    |> Map.put(:device_class, device && device.class)
  end

  @doc """
  Batch enrich multiple packets with device info for better performance.
  """
  def enrich_packets_with_device_info(packets) when is_list(packets) do
    # Get unique device identifiers from all packets
    device_identifiers =
      packets
      |> Enum.map(&get_device_identifier/1)
      |> Enum.reject(&nil_or_empty?/1)
      |> Enum.uniq()

    # Batch lookup devices (DeviceCache is already optimized with caching)
    device_map =
      Map.new(device_identifiers, fn identifier -> {identifier, DeviceCache.lookup_device(identifier)} end)

    # Enrich packets using the cached device map
    Enum.map(packets, fn packet ->
      device_identifier = get_device_identifier(packet)
      device = Map.get(device_map, device_identifier)

      packet
      |> Map.put(:device_model, device && device.model)
      |> Map.put(:device_vendor, device && device.vendor)
      |> Map.put(:device_contact, device && device.contact)
      |> Map.put(:device_class, device && device.class)
    end)
  end

  defp get_device_identifier(packet) do
    Map.get(packet, :device_identifier) || Map.get(packet, "device_identifier")
  end

  defp lookup_device_info(nil), do: nil
  defp lookup_device_info(""), do: nil
  defp lookup_device_info(identifier), do: DeviceCache.lookup_device(identifier)

  defp nil_or_empty?(nil), do: true
  defp nil_or_empty?(""), do: true
  defp nil_or_empty?(_), do: false

  @doc """
  Creates a filter function that checks both callsign and weather data.
  """
  def callsign_and_weather_filter(callsign) do
    fn packet, _socket ->
      packet_matches_callsign?(packet, callsign) and has_weather_data?(packet)
    end
  end

  @doc """
  Creates a filter function that only checks callsign.
  """
  def callsign_filter(callsign) do
    fn packet, _socket ->
      packet_matches_callsign?(packet, callsign)
    end
  end
end
