defmodule Phantom.ResourceTemplate do
  @moduledoc """
  The Model Context Protocol (MCP) provides a standardized way for
  servers to expose resources to clients. Resources allow servers to
  share data that provides context to language models, such as files,
  database schemas, or application-specific information. Each resource
  is uniquely identified by a URI.

  https://modelcontextprotocol.io/specification/2025-03-26/server/resources
  """

  import Phantom.Utils

  @enforce_keys ~w[name handler function path router scheme uri uri_template]a
  defstruct [
    :name,
    :description,
    :function,
    :completion_function,
    :handler,
    :mime_type,
    :path,
    :router,
    :scheme,
    :size,
    :uri,
    :uri_template,
    meta: %{}
  ]

  @type t :: %__MODULE__{
          name: String.t(),
          description: String.t(),
          function: atom(),
          completion_function: atom(),
          handler: module(),
          mime_type: String.t(),
          path: String.t(),
          router: module(),
          scheme: String.t(),
          size: pos_integer(),
          meta: map(),
          uri: URI.t(),
          uri_template: String.t()
        }

  @type json :: %{
          required(:uri) => String.t(),
          required(:name) => String.t(),
          optional(:description) => String.t(),
          optional(:mimeType) => String.t(),
          optional(:size) => pos_integer()
        }

  @spec build(map() | Keyword.t()) :: t()
  @doc """
  Build a resource_template spec

  The `Phantom.Router.resource/3` macro will build these specs.

  - `:name` - The name of the resource template.
  - `:uri` - The URI template of the resource in the style of `Plug.Router`, including the scheme.
    For example, you can define a path like `"myapp:///some/path/:project_id/:id` which
    will be parsed to include path params `%{"project_id" => _, "id" => _}`. The scheme can be
    `"https"`, `"git"`, `"file"`, or custom, eg `"myapp"`.
  - `:description` - The description of the resource and when to use it.
  - `:handler` - The module to call.
  - `:function` - The function to call on the handler module.
  - `:completion_function` - The function to call on the handler module that will provide possible completion results.
  - `:mime_type` - the MIME type of the results.
  - `:router` - The Router module that will capture the URIs and route resources by URI to functions.
    This is constructed by the `Phantom.Router.resource/3` macro automatically as
    `MyApp.MyMCPRouter.ResourceRouter.{Scheme}`. The module does not need to exist at the time of
    building it-- it will be generated when added by `Phantom.Cache.add_resource_template/2`
    or by the `Phantom.Router.resource/3` macro.

  """
  def build(attrs) do
    uri =
      case attrs[:uri] do
        %URI{} = uri -> {:ok, uri}
        uri when is_binary(uri) -> URI.new(uri)
      end

    uri =
      case uri do
        {:ok, %{path: path, scheme: scheme} = uri}
        when is_binary(path) and is_binary(scheme) ->
          uri

        {:ok, uri} ->
          raise "Provided an invalid URI.\nResource URIs must contain a path and a scheme.\nProvided: #{URI.to_string(uri)}"

        {:error, invalid} ->
          raise "Provided an invalid URI.\nProvided: #{inspect(attrs[:uri])}\nError at: #{inspect(invalid)}"
      end

    struct!(
      __MODULE__,
      attrs
      |> Map.new()
      |> Map.merge(%{
        name: attrs[:name] || to_string(attrs[:function]),
        scheme: attrs[:scheme] || uri.scheme,
        path: attrs[:path] || uri.path,
        uri_template: "#{uri.scheme}://#{to_uri_6570(uri.path)}"
      })
    )
  end

  @spec to_json(t()) :: json()
  @doc """
  Represent a ResourceTemplate spec as json when listing the available resources to clients.
  """
  def to_json(%__MODULE__{} = resource) do
    remove_nils(%{
      uriTemplate: resource.uri_template,
      name: resource.name,
      size: resource.size,
      description: resource.description,
      mimeType: resource.mime_type
    })
  end

  defp to_uri_6570(str) do
    # this is not a total 6570-compliant URI template.
    String.replace(str, ~r/:\w*/, fn ":" <> var -> "{#{var}}" end)
  end
end
