Skip to main content

lib/jido/chat/telegram/callback_query.ex

defmodule Jido.Chat.Telegram.CallbackQuery do
  @moduledoc """
  Typed callback query payload for Telegram action-style updates.
  """

  alias Jido.Chat.Author
  alias Jido.Chat.Wire

  @schema Zoi.struct(
            __MODULE__,
            %{
              id: Zoi.string(),
              data: Zoi.string() |> Zoi.nullish(),
              from: Zoi.struct(Author) |> Zoi.nullish(),
              chat_id: Zoi.any() |> Zoi.nullish(),
              message_id: Zoi.any() |> Zoi.nullish(),
              inline_message_id: Zoi.string() |> Zoi.nullish(),
              raw: 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 CallbackQuery."
  def schema, do: @schema

  @doc "Creates a typed callback query struct."
  @spec new(map()) :: t()
  def new(attrs) when is_map(attrs) do
    attrs
    |> normalize_from()
    |> then(&Jido.Chat.Schema.parse!(__MODULE__, @schema, &1))
  end

  @doc "Serializes callback query into a plain map with type marker."
  @spec to_map(t()) :: map()
  def to_map(%__MODULE__{} = callback_query) do
    callback_query
    |> Map.from_struct()
    |> Wire.to_plain()
    |> Map.put("__type__", "telegram_callback_query")
  end

  @doc "Builds callback query from serialized map data."
  @spec from_map(map()) :: t()
  def from_map(map) when is_map(map), do: new(map)

  defp normalize_from(attrs) do
    from = attrs[:from] || attrs["from"]

    author =
      case from do
        %Author{} = author ->
          author

        map when is_map(map) ->
          Author.new(%{
            user_id: to_string(map[:id] || map["id"] || "unknown"),
            user_name: map[:username] || map["username"] || to_string(map[:id] || map["id"] || "unknown"),
            full_name: map[:first_name] || map["first_name"],
            is_bot: map[:is_bot] || map["is_bot"] || false
          })

        _ ->
          nil
      end

    if is_nil(author), do: attrs, else: Map.put(attrs, :from, author)
  end
end