Skip to main content

lib/jido/chat/telegram/inline_keyboard.ex

defmodule Jido.Chat.Telegram.InlineKeyboard do
  @moduledoc """
  Typed inline keyboard wrapper for Telegram reply_markup payloads.
  """

  alias Jido.Chat.Telegram.InlineKeyboardButton

  @schema Zoi.struct(
            __MODULE__,
            %{
              rows:
                Zoi.array(Zoi.array(Zoi.struct(InlineKeyboardButton)))
                |> 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 InlineKeyboard."
  def schema, do: @schema

  @doc "Creates a typed inline keyboard from rows."
  @spec new(map()) :: t()
  def new(attrs) when is_map(attrs) do
    attrs
    |> normalize_rows()
    |> then(&Jido.Chat.Schema.parse!(__MODULE__, @schema, &1))
  end

  @doc "Converts inline keyboard to Telegram reply_markup wire shape."
  @spec to_reply_markup(t()) :: map()
  def to_reply_markup(%__MODULE__{} = keyboard) do
    %{
      "inline_keyboard" =>
        Enum.map(keyboard.rows, fn row ->
          Enum.map(row, &InlineKeyboardButton.to_wire/1)
        end)
    }
  end

  defp normalize_rows(attrs) do
    rows = attrs[:rows] || attrs["rows"] || []

    normalized =
      Enum.map(rows, fn
        row when is_list(row) ->
          Enum.map(row, fn
            %InlineKeyboardButton{} = button -> button
            map when is_map(map) -> InlineKeyboardButton.new(map)
            other -> other
          end)

        other ->
          other
      end)

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