defmodule Microwaveprop.Qrz.Record do
  @moduledoc """
  Typed struct representing the QRZ.com callsign fields this app
  actually consumes. The full upstream XML has ~50 fields; the rest
  are preserved in the raw `data` column on `Microwaveprop.Qrz.Callsign`
  and can be read directly when needed.
  """

  @derive Jason.Encoder

  @type t :: %__MODULE__{
          call: String.t() | nil,
          fname: String.t() | nil,
          name: String.t() | nil,
          grid: String.t() | nil,
          addr1: String.t() | nil,
          addr2: String.t() | nil,
          state: String.t() | nil,
          zip: String.t() | nil,
          country: String.t() | nil,
          lat: float() | nil,
          lon: float() | nil
        }

  defstruct [:call, :fname, :name, :grid, :addr1, :addr2, :state, :zip, :country, :lat, :lon]

  @string_fields %{
    "call" => :call,
    "fname" => :fname,
    "name" => :name,
    "grid" => :grid,
    "addr1" => :addr1,
    "addr2" => :addr2,
    "state" => :state,
    "zip" => :zip,
    "country" => :country
  }

  @float_fields %{
    "lat" => :lat,
    "lon" => :lon
  }

  @spec from_map(map()) :: t()
  def from_map(data) when is_map(data) do
    %__MODULE__{}
    |> parse_fields(data, @string_fields, &parse_string/1)
    |> parse_fields(data, @float_fields, &parse_float/1)
  end

  defp parse_fields(record, data, field_map, parser) do
    Enum.reduce(field_map, record, fn {xml_key, struct_key}, acc ->
      case Map.get(data, xml_key) do
        nil -> acc
        value -> Map.put(acc, struct_key, parser.(value))
      end
    end)
  end

  defp parse_string(value) when is_binary(value), do: value
  defp parse_string(_), do: nil

  defp parse_float(value) when is_binary(value) do
    case Float.parse(value) do
      {float, _} -> float
      :error -> nil
    end
  end

  defp parse_float(_), do: nil
end
