defmodule Aurora.Ctx.Pagination do
  @moduledoc """
  Provides a structured approach to handling pagination state in database queries and data
  retrieval operations.

  This module defines a struct that encapsulates all necessary pagination information including
  options used for the initial records request, current page position, page size, total counts, and
  the actual data entries. It supports configurable defaults and safe initialization
  with automatic fallback to sensible defaults when invalid values are provided.

  ## Configuration

  Default pagination settings can be configured in your `config.exs`:

  ```elixir
  config :aurora_ctx, :pagination,
    page: 1,
    per_page: 40
  ```

  If not configured, defaults to `page: 1`, `per_page: 40`.


  ## Fields

  * `repo_module` - Repository module used for database operations
  * `schema_module` - Schema module representing the data being paginated
  * `entries_count` - Total number of records available
  * `pages_count` - Total number of pages based on entries_count and per_page
  * `page` - Current page number (default: 1)
  * `per_page` - Number of entries per page (default: 40)
  * `opts` - Additional query options passed to database operations
  * `entries` - List of records for the current page
  """

  @type t :: %__MODULE__{
          repo_module: module() | nil,
          schema_module: module() | nil,
          entries_count: non_neg_integer() | nil,
          pages_count: pos_integer() | nil,
          page: pos_integer(),
          per_page: pos_integer(),
          opts: keyword(),
          entries: list()
        }

  defstruct [
    :repo_module,
    :schema_module,
    :entries_count,
    :pages_count,
    page: 1,
    per_page: 40,
    opts: [],
    entries: []
  ]

  @page 1
  @per_page 40
  @default_pagination Application.compile_env(:aurora_ctx, :pagination, %{})

  @doc """
  Creates a new pagination struct with safe defaults from the application configuration.
  Delegates to new/1 with the configured defaults.

  Any invalid configuration values will be replaced with pagination defaults.

  ## Returns

  Returns a `t()` struct with default values applied.

  """
  @spec new() :: t()
  def new, do: new(@default_pagination)

  @doc """
  Creates a new pagination struct with the given attributes.

  Validates and normalizes input attributes, ensuring that `page` and `per_page` are positive
  integers. Invalid or missing values are replaced with safe defaults.

  ## Parameters

  - attrs (`map()` | `keyword()`) - Pagination attributes
    - `:page` (`pos_integer()`) - Page number (default: 1)
    - `:per_page` (`pos_integer()`) - Items per page (default: 40)
    - `:opts` (`keyword()`) - Additional query options for database operations
    - `:entries` (`list()`) - Initial list of entries for the current page

  ## Returns

  Returns a `t()` struct with validated and normalized values.

  """
  @spec new(map() | keyword()) :: t()
  def new(attrs) when is_list(attrs), do: attrs |> Enum.into(%{}) |> new()

  def new(attrs) when is_map(attrs) do
    %__MODULE__{
      page: get_positive_integer(attrs, :page, @page),
      per_page: get_positive_integer(attrs, :per_page, @per_page),
      opts: Map.get(attrs, :opts, []),
      entries: Map.get(attrs, :entries, [])
    }
  end

  ## PRIVATE

  # Extracts a positive integer value from a map, falling back to default if invalid
  @spec get_positive_integer(map(), atom(), integer()) :: integer()
  defp get_positive_integer(map, key, default) do
    case Map.get(map, key, default) do
      value when is_integer(value) and value > 0 -> value
      _ -> default
    end
  end
end
