lib/util.ex

defmodule Rephex.Util do
  @moduledoc false
  alias Phoenix.LiveView.Socket

  @doc """
      defmodule BehaviourA do
        @callback foo(any(), any()) :: any()

        @optional_callbacks foo: 2
      end

      defmodule BehaviourAImpl do
        @behaviour BehaviourA
      end

      defmodule User do
        def call_foo() do
          {:ok, :but_not_impl} =
            Rephex.Util.call_optional(
              {BehaviourAImpl, :foo, 2},
              [1, 2],
              {:ok, :but_not_impl}
            )
        end
      end
  """
  @spec call_optional(mfa :: mfa(), args :: list(), default :: any()) :: any()
  def call_optional({module, fun_name, arity} = _mfa, args, default)
      when is_atom(module) and
             is_atom(fun_name) and
             is_integer(arity) and
             is_list(args) and
             length(args) == arity do
    cond do
      function_exported?(module, fun_name, arity) -> apply(module, fun_name, args)
      true -> default
    end
  end

  def socket_update_in(%Socket{} = socket, keys, func)
      when is_list(keys) and is_function(func, 1) do
    case keys do
      [single_key] ->
        Phoenix.Component.update(socket, single_key, func)

      [k, rest] ->
        Phoenix.Component.assign(socket, k, %{k => update_in(socket, rest, func)})
    end
  end
end