Skip to main content

lib/jido/chat/ingress_result.ex

defmodule Jido.Chat.IngressResult do
  @moduledoc """
  Transport-agnostic typed inbound routing result.

  Supports both request-based inputs (for example webhook HTTP requests)
  and event-based inputs (for example polling or gateway listeners).
  """

  alias Jido.Chat.{EventEnvelope, WebhookRequest, WebhookResponse, Wire}

  @schema Zoi.struct(
            __MODULE__,
            %{
              chat: Zoi.any(),
              adapter_name: Zoi.atom() |> Zoi.nullish(),
              event: Zoi.any() |> Zoi.nullish(),
              response: Zoi.any() |> Zoi.nullish(),
              request: Zoi.any() |> Zoi.nullish(),
              mode: Zoi.enum([:request, :event]) |> Zoi.default(:event),
              metadata: Zoi.map() |> Zoi.default(%{})
            },
            coerce: true
          )

  @type mode :: :request | :event
  @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 IngressResult."
  def schema, do: @schema

  @doc "Creates a typed ingress result."
  @spec new(map()) :: t()
  def new(attrs) when is_map(attrs), do: Jido.Chat.Schema.parse!(__MODULE__, @schema, attrs)

  @doc "Serializes ingress result into a plain map with type marker."
  @spec to_map(t()) :: map()
  def to_map(%__MODULE__{} = result) do
    %{
      chat: serialize_chat(result.chat),
      adapter_name: result.adapter_name,
      event: serialize_event(result.event),
      response: serialize_response(result.response),
      request: serialize_request(result.request),
      mode: result.mode,
      metadata: Wire.to_plain(result.metadata)
    }
    |> Wire.to_plain()
    |> Map.put("__type__", "ingress_result")
  end

  @doc "Builds ingress result from serialized data."
  @spec from_map(map()) :: t()
  def from_map(map) when is_map(map) do
    new(%{
      chat: deserialize_chat(map[:chat] || map["chat"]),
      adapter_name: map[:adapter_name] || map["adapter_name"],
      event: deserialize_event(map[:event] || map["event"]),
      response: deserialize_response(map[:response] || map["response"]),
      request: deserialize_request(map[:request] || map["request"]),
      mode: map[:mode] || map["mode"] || :event,
      metadata: map[:metadata] || map["metadata"] || %{}
    })
  end

  defp serialize_chat(%Jido.Chat{} = chat), do: Jido.Chat.to_map(chat)
  defp serialize_chat(other), do: Wire.to_plain(other)

  defp serialize_event(%EventEnvelope{} = envelope), do: EventEnvelope.to_map(envelope)
  defp serialize_event(other), do: Wire.to_plain(other)

  defp serialize_response(%WebhookResponse{} = response), do: WebhookResponse.to_map(response)
  defp serialize_response(other), do: Wire.to_plain(other)

  defp serialize_request(%WebhookRequest{} = request), do: WebhookRequest.to_map(request)
  defp serialize_request(other), do: Wire.to_plain(other)

  defp deserialize_chat(map) when is_map(map) do
    case map[:__type__] || map["__type__"] do
      "chat" -> Jido.Chat.from_map(map)
      _ -> map
    end
  end

  defp deserialize_chat(other), do: other

  defp deserialize_event(map) when is_map(map) do
    case map[:__type__] || map["__type__"] do
      "event_envelope" -> EventEnvelope.from_map(map)
      _ -> map
    end
  end

  defp deserialize_event(other), do: other

  defp deserialize_response(map) when is_map(map) do
    case map[:__type__] || map["__type__"] do
      "webhook_response" -> WebhookResponse.from_map(map)
      _ -> map
    end
  end

  defp deserialize_response(other), do: other

  defp deserialize_request(map) when is_map(map) do
    case map[:__type__] || map["__type__"] do
      "webhook_request" -> WebhookRequest.from_map(map)
      _ -> map
    end
  end

  defp deserialize_request(other), do: other
end