Skip to main content

lib/jido/chat/message_page.ex

defmodule Jido.Chat.MessagePage do
  @moduledoc """
  Canonical page of normalized messages for thread/channel history.
  """

  alias Jido.Chat.{Incoming, Message}

  @schema Zoi.struct(
            __MODULE__,
            %{
              messages: Zoi.array(Zoi.struct(Message)) |> Zoi.default([]),
              next_cursor: Zoi.string() |> Zoi.nullish(),
              direction: Zoi.enum([:forward, :backward]) |> Zoi.nullish(),
              metadata: Zoi.map() |> Zoi.default(%{})
            },
            coerce: true
          )

  @type t :: unquote(Zoi.type_spec(@schema))

  @enforce_keys Zoi.Struct.enforce_keys(@schema)
  defstruct Zoi.Struct.struct_fields(@schema)

  @doc "Returns the Zoi schema for MessagePage."
  def schema, do: @schema

  @doc "Creates a canonical message page and normalizes message entries."
  def new(attrs) when is_map(attrs) do
    attrs
    |> normalize_messages()
    |> then(&Jido.Chat.Schema.parse!(__MODULE__, @schema, &1))
  end

  defp normalize_messages(attrs) do
    adapter_name = attrs[:adapter_name] || attrs["adapter_name"]
    thread_id = attrs[:thread_id] || attrs["thread_id"]
    messages = attrs[:messages] || attrs["messages"] || []

    normalized =
      Enum.map(messages, fn
        %Message{} = message ->
          message

        %Incoming{} = incoming ->
          Message.from_incoming(incoming, adapter_name: adapter_name, thread_id: thread_id)

        map when is_map(map) ->
          if Map.has_key?(map, :external_room_id) || Map.has_key?(map, "external_room_id") do
            map
            |> Incoming.new()
            |> Message.from_incoming(adapter_name: adapter_name, thread_id: thread_id)
          else
            Message.new(map)
          end

        other ->
          other
      end)

    Map.put(attrs, :messages, normalized)
  end
end