//! rmcp-clipboard: MCP server for system clipboard access
//!
//! Read and write the system clipboard. Cross-platform via arboard.

use arboard::Clipboard;
use rmcp::{
    handler::server::{router::tool::ToolRouter, wrapper::Parameters, ServerHandler},
    model::*,
    ErrorData as McpError,
    ServiceExt,
};
use schemars::JsonSchema;
use serde::Deserialize;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

// === Parameter Types ===

#[derive(Debug, Deserialize, JsonSchema)]
pub struct WriteClipboardParams {
    #[schemars(description = "Text to write to the clipboard")]
    pub text: String,
}

// === Server ===

#[derive(Debug)]
pub struct ClipboardServer {
    pub tool_router: ToolRouter<Self>,
}

impl Default for ClipboardServer {
    fn default() -> Self {
        Self::new()
    }
}

impl ClipboardServer {
    pub fn new() -> Self {
        Self {
            tool_router: Self::tool_router(),
        }
    }
}

#[rmcp::tool_router]
impl ClipboardServer {
    #[rmcp::tool(description = "Read the current contents of the system clipboard")]
    pub async fn read_clipboard(&self) -> Result<CallToolResult, McpError> {
        let mut clipboard = Clipboard::new().map_err(|e| {
            McpError::internal_error(format!("Failed to access clipboard: {}", e), None)
        })?;

        match clipboard.get_text() {
            Ok(text) => {
                if text.is_empty() {
                    Ok(CallToolResult::success(vec![Content::text(
                        "Clipboard is empty".to_string(),
                    )]))
                } else {
                    let preview = if text.len() > 500 {
                        format!("{}... ({} chars total)", &text[..500], text.len())
                    } else {
                        text.clone()
                    };
                    Ok(CallToolResult::success(vec![Content::text(format!(
                        "Clipboard contents:\n{}",
                        preview
                    ))]))
                }
            }
            Err(e) => Ok(CallToolResult::success(vec![Content::text(format!(
                "Could not read clipboard (may contain non-text data): {}",
                e
            ))])),
        }
    }

    #[rmcp::tool(description = "Write text to the system clipboard")]
    pub async fn write_clipboard(
        &self,
        Parameters(params): Parameters<WriteClipboardParams>,
    ) -> Result<CallToolResult, McpError> {
        let mut clipboard = Clipboard::new().map_err(|e| {
            McpError::internal_error(format!("Failed to access clipboard: {}", e), None)
        })?;

        clipboard.set_text(&params.text).map_err(|e| {
            McpError::internal_error(format!("Failed to write to clipboard: {}", e), None)
        })?;

        let preview = if params.text.len() > 100 {
            format!("{}... ({} chars)", &params.text[..100], params.text.len())
        } else {
            params.text.clone()
        };

        Ok(CallToolResult::success(vec![Content::text(format!(
            "Copied to clipboard: {}",
            preview
        ))]))
    }

    #[rmcp::tool(description = "Clear the system clipboard")]
    pub async fn clear_clipboard(&self) -> Result<CallToolResult, McpError> {
        let mut clipboard = Clipboard::new().map_err(|e| {
            McpError::internal_error(format!("Failed to access clipboard: {}", e), None)
        })?;

        clipboard.clear().map_err(|e| {
            McpError::internal_error(format!("Failed to clear clipboard: {}", e), None)
        })?;

        Ok(CallToolResult::success(vec![Content::text(
            "Clipboard cleared".to_string(),
        )]))
    }
}

#[rmcp::tool_handler]
impl ServerHandler for ClipboardServer {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            protocol_version: ProtocolVersion::V_2024_11_05,
            capabilities: ServerCapabilities::builder().enable_tools().build(),
            server_info: Implementation::from_build_env(),
            instructions: Some(
                "System clipboard access. Read, write, or clear the clipboard.".into(),
            ),
        }
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::registry()
        .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")))
        .with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr))
        .init();

    tracing::info!("Starting rmcp-clipboard server");

    let server = ClipboardServer::new();
    let service = server.serve(rmcp::transport::stdio()).await?;
    service.waiting().await?;

    tracing::info!("rmcp-clipboard server stopped");
    Ok(())
}
