defmodule Mobilizon.Service.Address do
  @moduledoc """
  Module to render an `Mobilizon.Addresses.Address` struct to a string
  """

  alias Mobilizon.Addresses.Address, as: AddressModel

  @type address :: %{name: String.t(), alternative_name: String.t()}

  @spec render_address(AddressModel.t()) :: String.t() | nil
  def render_address(%AddressModel{} = address) do
    %{name: name, alternative_name: alternative_name} = render_names(address)

    cond do
      defined?(alternative_name) && defined?(name) ->
        "#{name}, #{alternative_name}"

      defined?(name) ->
        name

      defined?(alternative_name) ->
        alternative_name

      true ->
        nil
    end
  end

  @spec render_address!(AddressModel.t()) :: String.t() | no_return
  def render_address!(%AddressModel{} = address) do
    case render_address(address) do
      nil ->
        raise ArgumentError, message: "Invalid address"

      address ->
        address
    end
  end

  @spec render_names(AddressModel.t()) :: address()
  def render_names(%AddressModel{type: nil} = address) do
    render_names(%AddressModel{address | type: "house"})
  end

  def render_names(%AddressModel{
        type: type,
        description: description,
        postal_code: postal_code,
        locality: locality,
        country: country
      })
      when type in ["house", "street", "secondary"] do
    %{
      name: description,
      alternative_name: [postal_code, locality, country] |> Enum.filter(& &1) |> Enum.join(", ")
    }
  end

  def render_names(%AddressModel{
        type: type,
        description: description,
        postal_code: postal_code,
        locality: locality,
        country: country
      })
      when type in ["zone", "city", "administrative"] do
    %{
      name: if(defined?(postal_code), do: "#{description} (#{postal_code})", else: description),
      alternative_name:
        [locality, country]
        |> Enum.filter(&(&1 && &1 != description))
        |> Enum.join(", ")
    }
  end

  def render_names(%AddressModel{
        description: description,
        street: street,
        region: region,
        locality: locality,
        country: country
      }) do
    alternative_name =
      cond do
        defined?(street) ->
          if defined?(locality), do: "#{street} (#{locality})", else: street

        defined?(locality) ->
          "#{locality}, #{region}, #{country}"

        defined?(region) ->
          "#{region}, #{country}"

        defined?(country) ->
          country

        true ->
          nil
      end

    %{name: description, alternative_name: alternative_name}
  end

  defp defined?(string) when is_binary(string), do: String.trim(string) != ""
  defp defined?(_), do: false
end
