lib/utils/map_case.ex

defmodule Kadena.Utils.MapCase do
  @moduledoc """
  `Utils.MapCase` functions to convert the keys of a map to camel or snake case
  """

  @type key :: atom() | String.t()
  @type opt :: :to_camel | :to_snake
  @type string_value :: String.t()
  @type value :: any()
  @type words :: list(string_value())
  @type valid_map :: {:ok, map()}
  @type valid_format :: string_value() | map()

  @spec to_camel(map :: map()) :: valid_map()
  def to_camel(map) when is_map(map), do: {:ok, format(map, :to_camel)}

  @spec to_camel!(map :: map()) :: map()
  def to_camel!(map) when is_map(map), do: format(map, :to_camel)

  @spec to_snake(map :: map()) :: valid_map()
  def to_snake(map) when is_map(map), do: {:ok, format(map, :to_snake)}

  @spec to_snake!(map :: map()) :: map()
  def to_snake!(map) when is_map(map), do: format(map, :to_snake)

  @spec format(map :: map(), type :: opt()) :: map()
  defp format(%Decimal{} = decimal, _type), do: decimal

  defp format(%{} = map, type) do
    map
    |> Enum.map(fn {k, v} -> {format_key(k, type), format_value(v, type)} end)
    |> Enum.into(%{})
  end

  defp format(value, _type), do: value

  @spec format_key(key :: key(), type :: opt()) :: string_value()
  defp format_key(key, type) when is_atom(key),
    do: key |> Atom.to_string() |> format_key(type)

  defp format_key(key, :to_camel) when is_binary(key) do
    if String.contains?(key, "_") do
      [first_word | rest] = String.split(key, "_")
      first_word <> camelize(rest)
    else
      key
    end
  end

  defp format_key(key, :to_snake) when is_binary(key), do: Macro.underscore(key)

  @spec format_value(value :: value(), type :: opt()) :: valid_format()
  defp format_value(value, type) when is_map(value), do: format(value, type)

  defp format_value(value, type) when is_list(value),
    do: Enum.map(value, fn map -> format(map, type) end)

  defp format_value(value, _type), do: value

  @spec camelize(words :: words()) :: string_value()
  defp camelize([]), do: ""
  defp camelize([word | rest]), do: Macro.camelize(word) <> camelize(rest)
end