//! Protocol version negotiation
//!
//! MCP supports multiple protocol versions. This module handles version negotiation
//! between client and server.

use serde::{Deserialize, Serialize};

/// Latest supported MCP protocol version
pub const LATEST_PROTOCOL_VERSION: &str = "2025-11-25";

// =============================================================================
// CONST STRING MACRO - Copied from rust-sdk
// =============================================================================

/// Macro for creating compile-time constant string types
///
/// This creates a zero-sized type that serializes/deserializes to a specific string value.
/// Useful for JSON schema type fields that must always be a specific constant.
#[macro_export]
macro_rules! const_string {
    ($name:ident = $value:literal) => {
        #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
        pub struct $name;

        impl $name {
            pub const VALUE: &'static str = $value;

            pub fn as_str(&self) -> &'static str {
                Self::VALUE
            }
        }

        impl serde::Serialize for $name {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: serde::Serializer,
            {
                $value.serialize(serializer)
            }
        }

        impl<'de> serde::Deserialize<'de> for $name {
            fn deserialize<D>(deserializer: D) -> Result<$name, D::Error>
            where
                D: serde::Deserializer<'de>,
            {
                let s: String = serde::Deserialize::deserialize(deserializer)?;
                if s == $value {
                    Ok($name)
                } else {
                    Err(serde::de::Error::custom(format!(
                        "expected const string value {:?}, but got {:?}",
                        $value, s
                    )))
                }
            }
        }
    };
}

// Re-export for use in submodules
pub use const_string;

/// JSON-RPC version (always "2.0")
pub const JSON_RPC_VERSION: &str = "2.0";

/// All supported protocol versions in order of preference
///
/// Order matters: first match wins during negotiation
pub const SUPPORTED_PROTOCOL_VERSIONS: &[&str] = &[
    "2025-11-25", // Latest with tasks, elicitation, icons
    "2025-06-18", // Previous stable version
];

/// Default minimum client versions
///
/// Can be overridden by specific implementations
pub const DEFAULT_MIN_CLIENT_VERSIONS: &[(&str, &str)] =
    &[("claude-code", "2.0.56"), ("codex-mcp-client", "0.63.0")];

/// A protocol version with ordering support.
///
/// Ordering is based on position in `SUPPORTED_PROTOCOL_VERSIONS`:
/// later versions are "greater". Unknown versions are treated as less
/// than any known version.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProtocolVersion(pub String);

impl ProtocolVersion {
    /// MCP 2025-06-18
    pub const V_2025_06_18: &'static str = "2025-06-18";
    /// MCP 2025-11-25
    pub const V_2025_11_25: &'static str = "2025-11-25";

    /// Create from a string
    pub fn new(version: impl Into<String>) -> Self {
        Self(version.into())
    }

    /// Get the version string
    pub fn as_str(&self) -> &str {
        &self.0
    }

    /// Check if this version is supported
    pub fn is_supported(&self) -> bool {
        is_supported_protocol_version(&self.0)
    }

    fn position(&self) -> Option<usize> {
        SUPPORTED_PROTOCOL_VERSIONS
            .iter()
            .position(|v| *v == self.0)
    }
}

impl PartialOrd for ProtocolVersion {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        match (self.position(), other.position()) {
            (Some(a), Some(b)) => {
                // Reverse: index 0 is latest (greatest)
                Some(b.cmp(&a))
            }
            _ => None,
        }
    }
}

impl std::fmt::Display for ProtocolVersion {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<&str> for ProtocolVersion {
    fn from(s: &str) -> Self {
        Self(s.to_string())
    }
}

impl From<String> for ProtocolVersion {
    fn from(s: String) -> Self {
        Self(s)
    }
}

/// Check if a protocol version is supported
pub fn is_supported_protocol_version(version: &str) -> bool {
    SUPPORTED_PROTOCOL_VERSIONS.contains(&version)
}

/// Negotiate protocol version between client and server
///
/// Returns the client's version if supported, otherwise returns an error with supported versions
pub fn negotiate_protocol_version(client_version: &str) -> Result<String, Vec<String>> {
    if is_supported_protocol_version(client_version) {
        Ok(client_version.to_string())
    } else {
        Err(SUPPORTED_PROTOCOL_VERSIONS
            .iter()
            .map(|v| v.to_string())
            .collect())
    }
}

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

    #[test]
    fn test_supported_version() {
        assert!(is_supported_protocol_version("2025-11-25"));
        assert!(is_supported_protocol_version("2025-06-18"));
        assert!(!is_supported_protocol_version("2024-01-01"));
    }

    #[test]
    fn test_negotiate_supported_version() {
        assert_eq!(
            negotiate_protocol_version("2025-11-25"),
            Ok("2025-11-25".to_string())
        );
        assert_eq!(
            negotiate_protocol_version("2025-06-18"),
            Ok("2025-06-18".to_string())
        );
    }

    #[test]
    fn test_negotiate_unsupported_version() {
        let result = negotiate_protocol_version("2024-01-01");
        assert!(result.is_err());
        let supported = result.unwrap_err();
        assert_eq!(supported, vec!["2025-11-25", "2025-06-18"]);
    }

    #[test]
    fn test_protocol_version_ordering() {
        let v1 = ProtocolVersion::new("2025-06-18");
        let v2 = ProtocolVersion::new("2025-11-25");
        assert!(v2 > v1);
        assert!(v1 < v2);
        assert_eq!(v1.partial_cmp(&v1), Some(std::cmp::Ordering::Equal));
    }

    #[test]
    fn test_protocol_version_unknown_ordering() {
        let known = ProtocolVersion::new("2025-11-25");
        let unknown = ProtocolVersion::new("2099-01-01");
        // Unknown version can't be compared
        assert_eq!(known.partial_cmp(&unknown), None);
    }

    #[test]
    fn test_protocol_version_constants() {
        assert_eq!(ProtocolVersion::V_2025_06_18, "2025-06-18");
        assert_eq!(ProtocolVersion::V_2025_11_25, "2025-11-25");
    }
}
