lib/schemas/traits/trait.ex

defmodule Flagsmith.Schemas.Traits.Trait do
  use TypedEctoSchema
  import Ecto.Changeset

  @type from_types :: __MODULE__.t() | map() | list(__MODULE__.t() | map())

  @moduledoc """
  Ecto schema representing a Flagsmith trait definition.
  """

  @derive {Jason.Encoder, only: [:trait_key, :trait_value]}

  @primary_key {:id, :id, autogenerate: false}
  typed_embedded_schema do
    field(:trait_key, :string)
    field(:trait_value, __MODULE__.Value)
  end

  @doc false
  @spec changeset(map()) :: Ecto.Changeset.t()
  @spec changeset(__MODULE__.t(), map()) :: Ecto.Changeset.t()
  def changeset(struct \\ %__MODULE__{}, params) do
    struct
    |> cast(params, [:trait_value, :trait_key, :id])
    |> validate_required([:trait_value, :trait_key])
  end

  @doc false
  @spec extract_trait_value(String.t(), list(__MODULE__.t())) ::
          {:ok, term()} | {:error, :not_found}
  def extract_trait_value(key, traits) do
    case Enum.find(traits, fn %{trait_key: t_key} -> key == t_key end) do
      %__MODULE__{trait_value: %{value: value}} -> {:ok, value}
      _ -> {:error, :not_found}
    end
  end

  @doc false
  @spec from(from_types()) :: list(__MODULE__.t()) | __MODULE__.t()
  def from(traits) when is_list(traits),
    do: Enum.map(traits, &from/1)

  def from(%__MODULE__{} = trait), do: trait

  def from(%{} = params) do
    %__MODULE__{}
    |> cast(params, [:trait_value, :trait_key, :id])
    |> validate_required([:trait_value])
    |> apply_changes()
  end

  @doc false
  @spec into_update_map(list(__MODULE__.t())) :: map()
  def into_update_map(traits) when is_list(traits) do
    traits
    |> Enum.reduce(%{}, fn %{trait_key: key, trait_value: value}, acc ->
      Map.put(acc, key, value)
    end)
  end
end