//! Compile-time built-in tool registration macro.
//!
//! ```ignore
//! crate::tools! { ReadTool, GrepTool, EditTool }
//! ```
//!
//! Produces:
//! - `pub const ALL_BUILT_IN_TOOL_NAMES: &[&str] = &[...];`
//! - `pub fn built_in_tools() -> impl Iterator<Item = std::sync::Arc<dyn AnyTool>>`
//! - A compile-time uniqueness check on `T::NAME`.

#[macro_export]
macro_rules! tools {
    ($($tool:ty),* $(,)?) => {
        pub const ALL_BUILT_IN_TOOL_NAMES: &[&str] = &[
            $( <$tool as $crate::tool::Tool>::NAME, )*
        ];

        const _: () = {
            const fn str_eq(a: &str, b: &str) -> bool {
                let a = a.as_bytes();
                let b = b.as_bytes();
                if a.len() != b.len() { return false; }
                let mut i = 0;
                while i < a.len() {
                    if a[i] != b[i] { return false; }
                    i += 1;
                }
                true
            }
            const NAMES: &[&str] = ALL_BUILT_IN_TOOL_NAMES;
            let mut i = 0;
            while i < NAMES.len() {
                let mut j = i + 1;
                while j < NAMES.len() {
                    if str_eq(NAMES[i], NAMES[j]) {
                        panic!("Duplicate built-in tool name");
                    }
                    j += 1;
                }
                i += 1;
            }
        };

        pub fn built_in_tools() -> Vec<std::sync::Arc<dyn $crate::tool::AnyTool>> {
            vec![
                $(
                    {
                        let t: std::sync::Arc<$tool> = std::sync::Arc::new(<$tool>::default());
                        <$tool as $crate::tool::Tool>::erase(t)
                    },
                )*
            ]
        }
    };
}

#[cfg(test)]
mod tests {
    use crate::tool::{Tool, ToolContext, ToolEventSink, ToolInput, ToolKind};
    use async_trait::async_trait;
    use schemars::JsonSchema;
    use serde::{Deserialize, Serialize};
    use std::sync::Arc;

    #[derive(Default)]
    struct AlphaTool;
    #[derive(Default)]
    struct BetaTool;

    #[derive(Serialize, Deserialize, JsonSchema)]
    struct Empty {}

    macro_rules! impl_tool {
        ($t:ty, $name:literal) => {
            #[async_trait]
            impl Tool for $t {
                type Input = Empty;
                type Output = Empty;
                const NAME: &'static str = $name;
                fn kind() -> ToolKind { ToolKind::Other }
                async fn run(
                    self: Arc<Self>, _i: ToolInput<Empty>,
                    _e: ToolEventSink, _c: &ToolContext,
                ) -> Result<Empty, Empty> { Ok(Empty {}) }
            }
        };
    }
    impl_tool!(AlphaTool, "alpha");
    impl_tool!(BetaTool,  "beta");

    crate::tools! { AlphaTool, BetaTool }

    #[test]
    fn macro_lists_names() {
        assert_eq!(ALL_BUILT_IN_TOOL_NAMES, &["alpha", "beta"]);
    }

    #[test]
    fn macro_constructs_erased_tools() {
        let v = built_in_tools();
        assert_eq!(v.len(), 2);
        assert_eq!(v[0].name(), "alpha");
        assert_eq!(v[1].name(), "beta");
    }
}
