//! Macro support types for ergonomic MCP server development
//!
//! This module provides types that work with the `#[mcp_tool]`, `#[mcp_prompt]`,
//! `#[mcp_resource]`, `#[mcp_resource_template]`, and `#[mcp_router]` attribute macros
//! to enable SDK-style ergonomics while preserving mcphost-rs features.
//!
//! ## Quick Start - One Handler Per File
//!
//! ```rust,ignore
//! use mcp_host::prelude::*;
//! use mcp_host::macros::*;
//! use schemars::JsonSchema;
//! use serde::Deserialize;
//!
//! #[derive(Deserialize, JsonSchema)]
//! struct CalculateParams {
//!     x: f64,
//!     y: f64,
//! }
//!
//! struct MyServer;
//!
//! // tools/calculate.rs
//! impl MyServer {
//!     #[mcp_tool(name = "calculate")]
//!     async fn calculate(&self, ctx: Ctx, params: Parameters<CalculateParams>) -> ToolResult {
//!         let _ = ctx;
//!         Ok(ToolOutput::text(format!("Result: {}", params.0.x + params.0.y)))
//!     }
//! }
//!
//! // tools.rs
//! fn tools_router() -> mcp_host::registry::router::McpToolRouter<MyServer> {
//!     mcp_host::registry::router::McpToolRouter::new().with_tool(
//!         MyServer::calculate_tool_info(),
//!         MyServer::calculate_handler,
//!         None,
//!     )
//! }
//! ```
//!
//! ## Quick Start - Prompt Handler
//!
//! ```rust,ignore
//! use mcp_host::prelude::*;
//! use mcp_host::macros::*;
//! use serde_json::Value;
//!
//! struct MyServer;
//!
//! impl MyServer {
//!     #[mcp_prompt(name = "greeting")]
//!     async fn greeting(&self, ctx: Ctx, args: Value) -> PromptResult {
//!         let _ = ctx;
//!         let name = args.get("name").and_then(|v| v.as_str()).unwrap_or("World");
//!         prompt_with_description(
//!             "A friendly greeting",
//!             vec![user_message(format!("Please greet {name} warmly"))],
//!         )
//!     }
//! }
//! ```
//!
//! ## Quick Start - Resource Handlers
//!
//! ```rust,ignore
//! use mcp_host::prelude::*;
//! use mcp_host::macros::*;
//!
//! struct MyServer;
//!
//! impl MyServer {
//!     #[mcp_resource(uri = "config:///app", name = "app_config", mime_type = "application/json")]
//!     async fn app_config(&self, ctx: Ctx) -> ResourceResult {
//!         let _ = ctx;
//!         Ok(vec![text_resource("config:///app", "{\"version\": \"1.0\"}")])
//!     }
//!
//!     #[mcp_resource_template(uri_template = "file:///{path}", name = "files")]
//!     async fn read_file(&self, ctx: Ctx) -> ResourceResult {
//!         let path = ctx.get_uri_param("path").ok_or(ResourceError::InvalidUri("missing path".into()))?;
//!         let content = std::fs::read_to_string(path)?;
//!         Ok(vec![text_resource(format!("file:///{path}"), content)])
//!     }
//! }
//! ```

mod ctx;
mod parameters;
mod result;

pub use ctx::{Ctx, FromExecutionContext};
pub use parameters::Parameters;
pub use result::{
    IntoToolResult, PromptResult, ResourceResult, ToolResult, assistant_image_message,
    assistant_message, blob_resource, prompt_messages, prompt_with_description, structured, text,
    text_resource, text_resource_with_mime, texts, user_image_message, user_message,
};

// Re-export ToolOutput and ResourceContent for convenience
pub use crate::content::resource::ResourceContent;
pub use crate::registry::tools::ToolOutput;

// Re-export schemars for convenience
pub use schemars;

/// Generate JSON Schema for a type implementing `JsonSchema`
///
/// This is used by the `#[mcp_tool]` macro to generate input schemas at compile time.
pub fn schema_for<T: schemars::JsonSchema>() -> serde_json::Value {
    let schema = schemars::schema_for!(T);
    let value = serde_json::to_value(schema).unwrap_or_else(|_| {
        serde_json::json!({
            "type": "object",
            "properties": {}
        })
    });

    // Handle unit type () which schemars serializes as {"type": "null"}
    // MCP spec expects empty object schema for tools with no parameters
    if value.get("type") == Some(&serde_json::json!("null")) {
        serde_json::json!({
            "type": "object",
            "properties": {},
            "required": []
        })
    } else {
        value
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_unit_type_schema_is_empty_object() {
        // Unit type () should produce MCP-compliant empty object schema
        // not {"type": "null"}
        let schema = schema_for::<()>();

        assert_eq!(
            schema.get("type"),
            Some(&serde_json::json!("object")),
            "Unit type should produce object schema, not null"
        );
        assert!(
            schema.get("properties").is_some(),
            "Schema should have properties field"
        );
        assert_eq!(
            schema.get("required"),
            Some(&serde_json::json!([])),
            "Schema should have empty required array"
        );
    }

    #[test]
    fn test_parameters_unit_type_schema() {
        // Parameters<()> should also produce MCP-compliant empty object schema
        let schema = schema_for::<Parameters<()>>();

        assert_eq!(
            schema.get("type"),
            Some(&serde_json::json!("object")),
            "Parameters<()> should produce object schema"
        );
    }
}
