defmodule OpenApiSpex.PathItem do
  @moduledoc """
  Defines the `OpenApiSpex.PathItem.t` type.
  """

  alias OpenApiSpex.{Operation, Parameter, PathItem, Reference, Server}

  defstruct [
    :"$ref",
    :summary,
    :description,
    :get,
    :put,
    :post,
    :delete,
    :options,
    :head,
    :patch,
    :trace,
    :servers,
    :parameters,
    :extensions
  ]

  @typedoc """
  [Path Item Object](https://swagger.io/specification/#pathItemObject)

  Describes the operations available on a single path.
  A Path Item MAY be empty, due to ACL constraints.
  The path itself is still exposed to the documentation viewer
  but they will not know which operations and parameters are available.
  """
  @type t :: %__MODULE__{
          "$ref": String.t() | nil,
          summary: String.t() | nil,
          description: String.t() | nil,
          get: Operation.t() | nil,
          put: Operation.t() | nil,
          post: Operation.t() | nil,
          delete: Operation.t() | nil,
          options: Operation.t() | nil,
          head: Operation.t() | nil,
          patch: Operation.t() | nil,
          trace: Operation.t() | nil,
          servers: [Server.t()] | nil,
          parameters: [Parameter.t() | Reference.t()] | nil,
          extensions: %{String.t() => any()} | nil
        }

  @typedoc """
  Represents a route from in a Plug/Phoenix application.
  Eg from the generated `__routes__` function in a Phoenix.Router module.
  """
  @type route ::
          %{optional(:path) => String.t(), verb: atom, plug: atom, opts: any}
          | %{optional(:path) => String.t(), verb: atom, plug: atom, plug_opts: any}

  @doc """
  Builds a PathItem struct from a list of routes that share a path.
  """
  @spec from_routes([route]) :: nil | t
  def from_routes(routes) do
    Enum.each(routes, fn route ->
      Code.ensure_loaded(route.plug)
    end)

    routes
    |> Enum.filter(&function_exported?(&1.plug, :open_api_operation, 1))
    |> from_valid_routes()
  end

  @spec from_valid_routes([route]) :: nil | t
  defp from_valid_routes([]), do: nil

  defp from_valid_routes(routes) do
    routes
    |> Enum.map(fn route ->
      case Operation.from_route(route) do
        nil -> nil
        op -> {route.verb, op}
      end
    end)
    |> Enum.filter(& &1)
    |> case do
      [] -> nil
      attrs -> struct(PathItem, attrs)
    end
  end
end
