use async_trait::async_trait;

// Re-export canonical types from dirigent_protocol
pub use dirigent_protocol::inspector::{NodeId, NodeKind, NodeMetadata, NodeState};

/// Trait for components that support rich introspection.
///
/// Implementing this trait allows a component to provide detailed status reports
/// when queried. The `inspect()` method runs in the component's own async context
/// (introspective), while `current_state()` is synchronous for quick polling.
#[async_trait]
pub trait Inspectable: Send + Sync {
    /// Return a detailed status report as JSON.
    ///
    /// This is an introspective operation: it runs inside the node's context
    /// and can access internal state that isn't part of the standard metadata.
    async fn inspect(&self) -> serde_json::Value;

    /// Return the current lifecycle state.
    ///
    /// This should be a cheap, synchronous operation returning the node's
    /// current state without blocking.
    fn current_state(&self) -> NodeState;
}

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

    #[test]
    fn test_node_id_child() {
        let root = NodeId::new("dirigent");
        let child = root.child("connectors");
        assert_eq!(child.as_str(), "dirigent/connectors");

        let grandchild = child.child("acp-claude");
        assert_eq!(grandchild.as_str(), "dirigent/connectors/acp-claude");
    }

    #[test]
    fn test_node_id_parent() {
        let id = NodeId::new("dirigent/connectors/acp-claude");
        assert_eq!(id.parent().unwrap().as_str(), "dirigent/connectors");
        assert_eq!(
            id.parent().unwrap().parent().unwrap().as_str(),
            "dirigent"
        );
        assert!(NodeId::new("dirigent").parent().is_none());
    }

    #[test]
    fn test_node_id_name() {
        assert_eq!(NodeId::new("dirigent/connectors/acp").name(), "acp");
        assert_eq!(NodeId::new("dirigent").name(), "dirigent");
    }

    #[test]
    fn test_node_id_from_str() {
        let id: NodeId = "dirigent/system/host".into();
        assert_eq!(id.as_str(), "dirigent/system/host");
    }

    #[test]
    fn test_node_metadata_builder() {
        let meta = NodeMetadata::new(NodeKind::Connector, "My Connector")
            .with_state(NodeState::Running)
            .with_property("base_url", serde_json::json!("http://localhost:3000"));

        assert_eq!(meta.kind, NodeKind::Connector);
        assert_eq!(meta.label, "My Connector");
        assert_eq!(meta.state, NodeState::Running);
        assert_eq!(
            meta.properties.get("base_url").unwrap(),
            &serde_json::json!("http://localhost:3000")
        );
    }

    #[test]
    fn test_node_metadata_serialization() {
        let meta = NodeMetadata::new(NodeKind::Process, "stdio-transport")
            .with_state(NodeState::Busy("processing message".into()))
            .with_property("pid", serde_json::json!(12345));

        let json = serde_json::to_string(&meta).unwrap();
        let deserialized: NodeMetadata = serde_json::from_str(&json).unwrap();

        assert_eq!(deserialized.kind, NodeKind::Process);
        assert_eq!(deserialized.label, "stdio-transport");
        assert_eq!(
            deserialized.state,
            NodeState::Busy("processing message".into())
        );
    }

    #[test]
    fn test_node_state_display() {
        assert_eq!(NodeState::Running.to_string(), "Running");
        assert_eq!(
            NodeState::Error("timeout".into()).to_string(),
            "Error(timeout)"
        );
    }

    #[test]
    fn test_node_kind_display() {
        assert_eq!(NodeKind::Root.to_string(), "Root");
        assert_eq!(NodeKind::Custom("agent".into()).to_string(), "Custom(agent)");
    }
}
