Documentation
//! Utilities for Resource templates

#[cfg(feature = "server")]
use crate::app::handler::{FromHandlerParams, GenericHandler, Handler, HandlerParams};
#[cfg(feature = "server")]
use crate::error::Error;
#[cfg(feature = "server")]
use futures_util::future::BoxFuture;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt::Debug;
#[cfg(feature = "server")]
use std::sync::Arc;

use crate::types::{
    Annotations, Cursor, Icon, IntoResponse, Page, RequestId, Response, resource::Uri,
};

#[cfg(feature = "server")]
use crate::types::{FromRequest, ReadResourceRequestParams, ReadResourceResult, Request};

/// Represents a known resource template that the server is capable of reading.
///
/// See the [schema](https://github.com/modelcontextprotocol/specification/blob/main/schema/) for details
#[derive(Clone, Serialize, Deserialize)]
pub struct ResourceTemplate {
    /// The URI template that identifies this resource template.
    #[serde(rename = "uriTemplate")]
    pub uri_template: Uri,

    /// A human-readable name for this resource template.
    pub name: String,

    /// Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
    /// even by those unfamiliar with domain-specific terminology.
    ///
    /// If not provided, the name should be used for display (except for Tool,
    /// where `annotations.title` should be given precedence over using `name`, if present).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,

    /// A description of what this resource template represents.
    pub descr: Option<String>,

    /// The MIME type of this resource template, if known.
    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
    pub mime: Option<String>,

    /// Optional annotations for the resource template.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub annotations: Option<Annotations>,

    /// Optional set of sized icons that the client can display in a user interface.
    ///
    /// Clients that support rendering icons **MUST** support at least the following MIME types:
    /// - `image/png` - PNG images (safe, universal compatibility)
    /// - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)
    ///
    /// Clients that support rendering icons **SHOULD** also support:
    /// - `image/svg+xml` - SVG images (scalable but requires security precautions)
    /// - `image/webp` - WebP images (modern, efficient format)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub icons: Option<Vec<Icon>>,

    /// Metadata reserved by MCP for protocol-level metadata.
    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
    pub meta: Option<Value>,

    /// A list of roles that are allowed to read the resource
    #[serde(skip)]
    #[cfg(feature = "http-server")]
    pub(crate) roles: Option<Vec<String>>,

    /// A list of permissions that are allowed to read the resource
    #[serde(skip)]
    #[cfg(feature = "http-server")]
    pub(crate) permissions: Option<Vec<String>>,
}

/// Sent from the client to request a list of resource templates the server has.
///
/// See the [schema](https://github.com/modelcontextprotocol/specification/blob/main/schema/) for details
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ListResourceTemplatesRequestParams {
    /// An opaque token representing the current pagination position.
    /// If provided, the server should return results starting after this cursor.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cursor: Option<Cursor>,
}

/// The server's response to a resources/templates/list request from the client.
///
/// See the [schema](https://github.com/modelcontextprotocol/specification/blob/main/schema/) for details
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ListResourceTemplatesResult {
    /// A list of resource templates that the server offers.
    #[serde(rename = "resourceTemplates")]
    pub templates: Vec<ResourceTemplate>,

    /// An opaque token representing the pagination position after the last returned result.
    ///
    /// When a paginated result has more data available, the `next_cursor`
    /// field will contain `Some` token that can be used in subsequent requests
    /// to fetch the next page. When there are no more results to return, the `next_cursor` field
    /// will be `None`.
    #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
    pub next_cursor: Option<Cursor>,
}

impl IntoResponse for ListResourceTemplatesResult {
    #[inline]
    fn into_response(self, req_id: RequestId) -> Response {
        match serde_json::to_value(self) {
            Ok(v) => Response::success(req_id, v),
            Err(err) => Response::error(req_id, err.into()),
        }
    }
}

impl From<Vec<ResourceTemplate>> for ListResourceTemplatesResult {
    #[inline]
    fn from(templates: Vec<ResourceTemplate>) -> Self {
        Self {
            next_cursor: None,
            templates,
        }
    }
}

impl From<Page<'_, ResourceTemplate>> for ListResourceTemplatesResult {
    #[inline]
    fn from(page: Page<'_, ResourceTemplate>) -> Self {
        Self {
            next_cursor: page.next_cursor,
            templates: page.items.to_vec(),
        }
    }
}

#[cfg(feature = "server")]
impl FromHandlerParams for ListResourceTemplatesRequestParams {
    #[inline]
    fn from_params(params: &HandlerParams) -> Result<Self, Error> {
        let req = Request::from_params(params)?;
        Self::from_request(req)
    }
}

impl ListResourceTemplatesResult {
    /// Creates a new [`ListResourceTemplatesResult`]
    #[inline]
    pub fn new() -> Self {
        Default::default()
    }
}

/// Represents a function that reads a resource
#[cfg(feature = "server")]
pub(crate) struct ResourceFunc<F, R, Args>
where
    F: GenericHandler<Args, Output = R>,
    R: TryInto<ReadResourceResult>,
    Args: TryFrom<ReadResourceRequestParams, Error = Error>,
{
    func: F,
    _marker: std::marker::PhantomData<Args>,
}

#[cfg(feature = "server")]
impl<F, R, Args> ResourceFunc<F, R, Args>
where
    F: GenericHandler<Args, Output = R>,
    R: TryInto<ReadResourceResult>,
    Args: TryFrom<ReadResourceRequestParams, Error = Error>,
{
    /// Creates a new [`ResourceFunc`] wrapped into [`Arc`]
    pub(crate) fn new(func: F) -> Arc<Self> {
        let func = Self {
            func,
            _marker: std::marker::PhantomData,
        };
        Arc::new(func)
    }
}

#[cfg(feature = "server")]
impl<F, R, Args> Handler<ReadResourceResult> for ResourceFunc<F, R, Args>
where
    F: GenericHandler<Args, Output = R>,
    R: TryInto<ReadResourceResult>,
    R::Error: Into<Error>,
    Args: TryFrom<ReadResourceRequestParams, Error = Error> + Send + Sync,
{
    #[inline]
    fn call(&self, params: HandlerParams) -> BoxFuture<'_, Result<ReadResourceResult, Error>> {
        let HandlerParams::Resource(params) = params else {
            unreachable!()
        };
        Box::pin(async move {
            //let mut iter = params.args.into_iter().flatten().next();
            let args = Args::try_from(params)?;
            self.func.call(args).await.try_into().map_err(Into::into)
        })
    }
}

impl Debug for ResourceTemplate {
    #[inline]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ResourceTemplate")
            .field("uri_template", &self.uri_template)
            .field("name", &self.name)
            .field("title", &self.title)
            .field("descr", &self.descr)
            .field("mime", &self.mime)
            .field("annotations", &self.annotations)
            .field("meta", &self.meta)
            .finish()
    }
}

#[cfg(feature = "server")]
impl ResourceTemplate {
    /// Creates a new [`ResourceTemplate`]
    #[inline]
    pub fn new<U: Into<Uri>, S: Into<String>>(uri: U, name: S) -> Self {
        Self {
            uri_template: uri.into(),
            name: name.into(),
            title: None,
            mime: None,
            descr: None,
            annotations: None,
            meta: None,
            icons: None,
            #[cfg(feature = "http-server")]
            roles: None,
            #[cfg(feature = "http-server")]
            permissions: None,
        }
    }

    /// Sets a title for a resource template
    pub fn with_title(&mut self, title: impl Into<String>) -> &mut Self {
        self.title = Some(title.into());
        self
    }

    /// Sets a description for a resource template
    pub fn with_description(&mut self, description: &str) -> &mut Self {
        self.descr = Some(description.into());
        self
    }

    /// Sets a MIME type for all matching resources
    pub fn with_mime(&mut self, mime: &str) -> &mut Self {
        self.mime = Some(mime.into());
        self
    }

    /// Sets annotations for the resource template
    pub fn with_annotations<F>(&mut self, config: F) -> &mut Self
    where
        F: FnOnce(Annotations) -> Annotations,
    {
        self.annotations = Some(config(Default::default()));
        self
    }

    /// Sets a list of roles that are allowed to read the resource
    #[cfg(feature = "http-server")]
    pub fn with_roles<T, I>(&mut self, roles: T) -> &mut Self
    where
        T: IntoIterator<Item = I>,
        I: Into<String>,
    {
        self.roles = Some(roles.into_iter().map(Into::into).collect());
        self
    }

    /// Sets a list of permissions that are allowed to read the resource
    #[cfg(feature = "http-server")]
    pub fn with_permissions<T, I>(&mut self, permissions: T) -> &mut Self
    where
        T: IntoIterator<Item = I>,
        I: Into<String>,
    {
        self.permissions = Some(permissions.into_iter().map(Into::into).collect());
        self
    }

    /// Sets the [`ResourceTemplate`] icons
    pub fn with_icons(&mut self, icons: impl IntoIterator<Item = Icon>) -> &mut Self {
        self.icons = Some(icons.into_iter().collect());
        self
    }
}

#[cfg(test)]
mod tests {}