//! Open-form client/protocol identifiers and tool category enum.

use serde::{Deserialize, Serialize};
use std::sync::{Arc, OnceLock};

/// Open newtype identifying the upstream client family (Claude, Codex, etc.).
///
/// Use the provided constants for known clients; use [`ClientKind::custom`]
/// for anything else. Comparison is by inner string equality.
///
/// The well-known constructors (`claude`, `codex`, `gemini`, `opencode`)
/// return cached values backed by a process-wide `OnceLock`, so calling them
/// repeatedly is a cheap `Arc` clone — no per-call heap allocation.
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct ClientKind(Arc<str>);

impl ClientKind {
    pub fn claude() -> Self {
        static CELL: OnceLock<ClientKind> = OnceLock::new();
        CELL.get_or_init(|| Self(Arc::from("claude"))).clone()
    }
    pub fn codex() -> Self {
        static CELL: OnceLock<ClientKind> = OnceLock::new();
        CELL.get_or_init(|| Self(Arc::from("codex"))).clone()
    }
    pub fn gemini() -> Self {
        static CELL: OnceLock<ClientKind> = OnceLock::new();
        CELL.get_or_init(|| Self(Arc::from("gemini"))).clone()
    }
    pub fn opencode() -> Self {
        static CELL: OnceLock<ClientKind> = OnceLock::new();
        CELL.get_or_init(|| Self(Arc::from("opencode"))).clone()
    }

    pub fn custom(name: impl Into<Arc<str>>) -> Self { Self(name.into()) }
    pub fn as_str(&self) -> &str { &self.0 }
}

/// Open newtype identifying the wire protocol (ACP, OpenCode, native).
///
/// As with [`ClientKind`], the well-known constructors return cached values
/// backed by a process-wide `OnceLock`.
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct ProtocolKind(Arc<str>);

impl ProtocolKind {
    pub fn acp() -> Self {
        static CELL: OnceLock<ProtocolKind> = OnceLock::new();
        CELL.get_or_init(|| Self(Arc::from("acp"))).clone()
    }
    pub fn opencode() -> Self {
        static CELL: OnceLock<ProtocolKind> = OnceLock::new();
        CELL.get_or_init(|| Self(Arc::from("opencode"))).clone()
    }
    pub fn native() -> Self {
        static CELL: OnceLock<ProtocolKind> = OnceLock::new();
        CELL.get_or_init(|| Self(Arc::from("native"))).clone()
    }

    pub fn custom(name: impl Into<Arc<str>>) -> Self { Self(name.into()) }
    pub fn as_str(&self) -> &str { &self.0 }
}

/// Coarse category of a tool. Connectors use this to pick wire-level hints
/// (e.g. ACP `ToolKind::Edit` triggers diff rendering on the client).
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolKind {
    Read,
    Edit,
    Search,
    Execute,
    Fetch,
    Think,
    Other,
}

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

    #[test]
    fn client_kind_constants_are_distinct() {
        assert_ne!(ClientKind::claude(), ClientKind::codex());
        assert_eq!(ClientKind::claude(), ClientKind::claude());
    }

    #[test]
    fn client_kind_custom_round_trips() {
        let k = ClientKind::custom("aider");
        assert_eq!(k.as_str(), "aider");
        assert_eq!(k, ClientKind::custom("aider"));
    }

    #[test]
    fn protocol_kind_distinct() {
        assert_ne!(ProtocolKind::acp(), ProtocolKind::opencode());
    }

    #[test]
    fn tool_kind_serde_round_trip() {
        let json = serde_json::to_string(&ToolKind::Edit).unwrap();
        assert_eq!(json, "\"edit\"");
        let back: ToolKind = serde_json::from_str(&json).unwrap();
        assert_eq!(back, ToolKind::Edit);
    }

    #[test]
    fn well_known_client_kinds_are_cached() {
        let a = ClientKind::claude();
        let b = ClientKind::claude();
        assert_eq!(a, b);
        // Proves the cache works: same Arc, no per-call allocation.
        assert!(Arc::ptr_eq(&a.0, &b.0));

        // Sanity-check another well-known kind too.
        let c1 = ClientKind::codex();
        let c2 = ClientKind::codex();
        assert!(Arc::ptr_eq(&c1.0, &c2.0));

        // Custom values are *not* cached — each call allocates fresh.
        let x = ClientKind::custom("aider");
        let y = ClientKind::custom("aider");
        assert_eq!(x, y);
        assert!(!Arc::ptr_eq(&x.0, &y.0));
    }

    #[test]
    fn well_known_protocol_kinds_are_cached() {
        let a = ProtocolKind::acp();
        let b = ProtocolKind::acp();
        assert_eq!(a, b);
        assert!(Arc::ptr_eq(&a.0, &b.0));
    }
}
