//! # herolib-derive: Derive Macros for herolib
//!
//! This crate provides procedural macros for herolib:
//!
//! ## Schema & Serialization Macros
//!
//! - **ToSchema** - Generate JSON Schema from Rust structs
//! - **ToHeroScript** - Serialize Rust structs to HeroScript format
//! - **FromHeroScript** - Deserialize HeroScript into Rust structs
//!
//! ## MCP Client Macros
//!
//! - **mcp_client_from_json** - Generate typed MCP client from inline JSON spec
//! - **mcp_client_from_file** - Generate typed MCP client from JSON file
//! - **mcp_tool** - Mark functions as MCP tools
//!
//! ## OpenRPC Client Macros
//!
//! - **openrpc_client!** - Generate typed RPC client from OpenRPC specification
//!
//! ## ToSchema Usage
//!
//! ```ignore
//! use herolib_derive::ToSchema;
//!
//! #[derive(ToSchema)]
//! struct Inner {
//!     value: i32,
//! }
//!
//! #[derive(ToSchema)]
//! struct Outer {
//!     name: String,
//!     inner: Inner,
//! }
//!
//! let schema = Outer::json_schema_pretty();
//! println!("{}", schema);
//! ```
//!
//! ## HeroScript Usage
//!
//! ```ignore
//! use herolib_derive::{ToHeroScript, FromHeroScript};
//!
//! #[derive(ToHeroScript, FromHeroScript, Default)]
//! struct Person {
//!     name: String,
//!     age: u32,
//!     active: bool,
//! }
//!
//! // Serialize to HeroScript
//! let person = Person { name: "John".into(), age: 30, active: true };
//! let hs = person.to_heroscript("person", "define");
//!
//! // Deserialize from HeroScript
//! let script = "!!person.define name:Jane age:25 active:false";
//! let jane = Person::from_heroscript(script).unwrap();
//! ```
//!
//! ## OTOML Usage
//!
//! ```ignore
//! use herolib_derive::Otoml;
//! use serde::{Serialize, Deserialize};
//!
//! #[derive(Otoml, Serialize, Deserialize)]
//! struct Config {
//!     name: String,
//!     port: u32,
//!     debug: bool,
//! }
//!
//! // Serialize to canonical OTOML
//! let config = Config { name: "server".into(), port: 8080, debug: true };
//! let otoml = config.dump_otoml().unwrap();
//!
//! // Deserialize from any valid TOML
//! let parsed = Config::load_otoml(&otoml).unwrap();
//! ```

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{
    Data, DataEnum, DeriveInput, Fields, GenericArgument, Ident, PathArguments, Type,
    parse_macro_input,
};

mod heroscript;
mod mcp;
mod openrpc_client;
mod osis_object;

// ============================================================================
// Schema Derive Macro
// ============================================================================

/// Derive macro for generating JSON Schema from structs.
///
/// This macro adds these methods to the struct:
/// - `json_schema()` - Returns compact JSON Schema with `$schema` header
/// - `json_schema_pretty()` - Returns formatted JSON Schema
/// - `schema_object()` - Returns just the object schema (for embedding)
///
/// # Example
///
/// ```ignore
/// use herolib_derive::ToSchema;
///
/// #[derive(ToSchema)]
/// struct Person {
///     name: String,
///     age: u32,
///     active: bool,
/// }
///
/// let schema = Person::json_schema_pretty();
/// ```
#[proc_macro_derive(ToSchema)]
pub fn to_schema_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let name_str = name.to_string();

    let schema_impl = match &input.data {
        Data::Struct(data_struct) => generate_struct_schema(name, &name_str, &data_struct.fields),
        Data::Enum(data_enum) => generate_enum_schema(name, &name_str, data_enum),
        Data::Union(_) => {
            return syn::Error::new_spanned(&input, "ToSchema does not support unions")
                .to_compile_error()
                .into();
        }
    };

    TokenStream::from(schema_impl)
}

fn generate_struct_schema(name: &Ident, name_str: &str, fields: &Fields) -> TokenStream2 {
    let fields_named = match fields {
        Fields::Named(fields) => &fields.named,
        Fields::Unnamed(_) => {
            return quote! {
                impl #name {
                    pub fn schema_object() -> String {
                        r#"{"type":"array"}"#.to_string()
                    }

                    pub fn json_schema() -> String {
                        format!(
                            r#"{{"$schema":"http://json-schema.org/draft-07/schema#","title":"{}","type":"array"}}"#,
                            #name_str
                        )
                    }

                    pub fn json_schema_pretty() -> String {
                        Self::pretty_print_json(&Self::json_schema())
                    }

                    fn pretty_print_json(json: &str) -> String {
                        let mut result = String::new();
                        let mut indent = 0;
                        let mut in_string = false;
                        let mut prev_char = ' ';

                        for ch in json.chars() {
                            if ch == '"' && prev_char != '\\' {
                                in_string = !in_string;
                            }

                            if in_string {
                                result.push(ch);
                            } else {
                                match ch {
                                    '{' | '[' => {
                                        result.push(ch);
                                        result.push('\n');
                                        indent += 2;
                                        result.push_str(&" ".repeat(indent));
                                    }
                                    '}' | ']' => {
                                        result.push('\n');
                                        indent = indent.saturating_sub(2);
                                        result.push_str(&" ".repeat(indent));
                                        result.push(ch);
                                    }
                                    ',' => {
                                        result.push(ch);
                                        result.push('\n');
                                        result.push_str(&" ".repeat(indent));
                                    }
                                    ':' => {
                                        result.push(ch);
                                        result.push(' ');
                                    }
                                    ' ' | '\n' | '\t' | '\r' => {}
                                    _ => result.push(ch),
                                }
                            }
                            prev_char = ch;
                        }
                        result
                    }
                }
            };
        }
        Fields::Unit => {
            return quote! {
                impl #name {
                    pub fn schema_object() -> String {
                        r#"{"type":"null"}"#.to_string()
                    }

                    pub fn json_schema() -> String {
                        format!(
                            r#"{{"$schema":"http://json-schema.org/draft-07/schema#","title":"{}","type":"null"}}"#,
                            #name_str
                        )
                    }

                    pub fn json_schema_pretty() -> String {
                        Self::pretty_print_json(&Self::json_schema())
                    }

                    fn pretty_print_json(json: &str) -> String {
                        let mut result = String::new();
                        let mut indent = 0;
                        let mut in_string = false;
                        let mut prev_char = ' ';

                        for ch in json.chars() {
                            if ch == '"' && prev_char != '\\' {
                                in_string = !in_string;
                            }

                            if in_string {
                                result.push(ch);
                            } else {
                                match ch {
                                    '{' | '[' => {
                                        result.push(ch);
                                        result.push('\n');
                                        indent += 2;
                                        result.push_str(&" ".repeat(indent));
                                    }
                                    '}' | ']' => {
                                        result.push('\n');
                                        indent = indent.saturating_sub(2);
                                        result.push_str(&" ".repeat(indent));
                                        result.push(ch);
                                    }
                                    ',' => {
                                        result.push(ch);
                                        result.push('\n');
                                        result.push_str(&" ".repeat(indent));
                                    }
                                    ':' => {
                                        result.push(ch);
                                        result.push(' ');
                                    }
                                    ' ' | '\n' | '\t' | '\r' => {}
                                    _ => result.push(ch),
                                }
                            }
                            prev_char = ch;
                        }
                        result
                    }
                }
            };
        }
    };

    // Collect field info for code generation
    let mut field_names: Vec<String> = Vec::new();
    let mut field_schema_exprs: Vec<TokenStream2> = Vec::new();
    let mut required_field_names: Vec<String> = Vec::new();

    for field in fields_named.iter() {
        let field_name = field.ident.as_ref().unwrap().to_string();
        let (schema_expr, is_optional) = type_to_schema_expr(&field.ty);

        field_names.push(field_name.clone());
        field_schema_exprs.push(schema_expr);

        if !is_optional {
            required_field_names.push(field_name);
        }
    }

    // Generate the properties building code
    let property_builders: Vec<TokenStream2> = field_names
        .iter()
        .zip(field_schema_exprs.iter())
        .map(|(name, expr)| {
            quote! {
                props.push(format!(r#""{}": {}"#, #name, #expr));
            }
        })
        .collect();

    // Generate required array
    let required_literals: Vec<TokenStream2> = required_field_names
        .iter()
        .map(|name| quote! { format!(r#""{}""#, #name) })
        .collect();

    quote! {
        impl #name {
            /// Returns the schema object for this type (without $schema header).
            /// Use this when embedding this type's schema in another schema.
            pub fn schema_object() -> String {
                let mut props: Vec<String> = Vec::new();
                #(#property_builders)*

                let required: Vec<String> = vec![#(#required_literals),*];

                format!(
                    r#"{{"type": "object", "properties": {{{}}}, "required": [{}]}}"#,
                    props.join(", "),
                    required.join(", ")
                )
            }

            /// Returns a JSON Schema string for this type with full $schema header.
            pub fn json_schema() -> String {
                let mut props: Vec<String> = Vec::new();
                #(#property_builders)*

                let required: Vec<String> = vec![#(#required_literals),*];

                format!(
                    r#"{{"$schema": "http://json-schema.org/draft-07/schema#", "title": "{}", "type": "object", "properties": {{{}}}, "required": [{}]}}"#,
                    #name_str,
                    props.join(", "),
                    required.join(", ")
                )
            }

            /// Returns a pretty-printed JSON Schema string for this type.
            pub fn json_schema_pretty() -> String {
                Self::pretty_print_json(&Self::json_schema())
            }

            fn pretty_print_json(json: &str) -> String {
                let mut result = String::new();
                let mut indent = 0;
                let mut in_string = false;
                let mut prev_char = ' ';

                for ch in json.chars() {
                    if ch == '"' && prev_char != '\\' {
                        in_string = !in_string;
                    }

                    if in_string {
                        result.push(ch);
                    } else {
                        match ch {
                            '{' | '[' => {
                                result.push(ch);
                                result.push('\n');
                                indent += 2;
                                result.push_str(&" ".repeat(indent));
                            }
                            '}' | ']' => {
                                result.push('\n');
                                indent = indent.saturating_sub(2);
                                result.push_str(&" ".repeat(indent));
                                result.push(ch);
                            }
                            ',' => {
                                result.push(ch);
                                result.push('\n');
                                result.push_str(&" ".repeat(indent));
                            }
                            ':' => {
                                result.push(ch);
                                result.push(' ');
                            }
                            ' ' | '\n' | '\t' | '\r' => {}
                            _ => result.push(ch),
                        }
                    }
                    prev_char = ch;
                }
                result
            }
        }
    }
}

/// Generate JSON Schema for an enum type.
fn generate_enum_schema(name: &Ident, name_str: &str, data_enum: &DataEnum) -> TokenStream2 {
    // Check if all variants are unit variants (simple enum)
    let all_unit_variants = data_enum
        .variants
        .iter()
        .all(|v| matches!(v.fields, Fields::Unit));

    if all_unit_variants {
        // Simple enum with only unit variants - use JSON Schema enum
        let variant_names: Vec<String> = data_enum
            .variants
            .iter()
            .map(|v| v.ident.to_string())
            .collect();

        let variant_literals: Vec<TokenStream2> = variant_names
            .iter()
            .map(|name| quote! { format!(r#""{}""#, #name) })
            .collect();

        quote! {
            impl #name {
                pub fn schema_object() -> String {
                    let variants: Vec<String> = vec![#(#variant_literals),*];
                    format!(r#"{{"enum": [{}]}}"#, variants.join(", "))
                }

                pub fn json_schema() -> String {
                    let variants: Vec<String> = vec![#(#variant_literals),*];
                    format!(
                        r#"{{"$schema": "http://json-schema.org/draft-07/schema#", "title": "{}", "enum": [{}]}}"#,
                        #name_str,
                        variants.join(", ")
                    )
                }

                pub fn json_schema_pretty() -> String {
                    Self::pretty_print_json(&Self::json_schema())
                }

                fn pretty_print_json(json: &str) -> String {
                    let mut result = String::new();
                    let mut indent = 0;
                    let mut in_string = false;
                    let mut prev_char = ' ';

                    for ch in json.chars() {
                        if ch == '"' && prev_char != '\\' {
                            in_string = !in_string;
                        }

                        if in_string {
                            result.push(ch);
                        } else {
                            match ch {
                                '{' | '[' => {
                                    result.push(ch);
                                    result.push('\n');
                                    indent += 2;
                                    result.push_str(&" ".repeat(indent));
                                }
                                '}' | ']' => {
                                    result.push('\n');
                                    indent = indent.saturating_sub(2);
                                    result.push_str(&" ".repeat(indent));
                                    result.push(ch);
                                }
                                ',' => {
                                    result.push(ch);
                                    result.push('\n');
                                    result.push_str(&" ".repeat(indent));
                                }
                                ':' => {
                                    result.push(ch);
                                    result.push(' ');
                                }
                                ' ' | '\n' | '\t' | '\r' => {}
                                _ => result.push(ch),
                            }
                        }
                        prev_char = ch;
                    }
                    result
                }
            }
        }
    } else {
        // Complex enum with data variants - use oneOf
        let mut variant_schema_builders: Vec<TokenStream2> = Vec::new();

        for variant in &data_enum.variants {
            let variant_name = variant.ident.to_string();

            match &variant.fields {
                Fields::Unit => {
                    variant_schema_builders.push(quote! {
                        schemas.push(format!(r#"{{"const": "{}"}}"#, #variant_name));
                    });
                }
                Fields::Unnamed(fields) => {
                    if fields.unnamed.len() == 1 {
                        let inner_ty = &fields.unnamed.first().unwrap().ty;
                        let (inner_expr, _) = type_to_schema_expr(inner_ty);
                        variant_schema_builders.push(quote! {
                            schemas.push(format!(
                                r#"{{"type": "object", "properties": {{"{}": {}}}, "required": ["{}"]}}"#,
                                #variant_name,
                                #inner_expr,
                                #variant_name
                            ));
                        });
                    } else {
                        let item_exprs: Vec<TokenStream2> = fields
                            .unnamed
                            .iter()
                            .map(|f| {
                                let (expr, _) = type_to_schema_expr(&f.ty);
                                expr
                            })
                            .collect();
                        let len = item_exprs.len();
                        variant_schema_builders.push(quote! {
                            let items: Vec<String> = vec![#(#item_exprs),*];
                            schemas.push(format!(
                                r#"{{"type": "object", "properties": {{"{}": {{"type": "array", "items": [{}], "minItems": {}, "maxItems": {}}}}}, "required": ["{}"]}}"#,
                                #variant_name,
                                items.join(", "),
                                #len,
                                #len,
                                #variant_name
                            ));
                        });
                    }
                }
                Fields::Named(fields) => {
                    let field_builders: Vec<TokenStream2> = fields
                        .named
                        .iter()
                        .map(|f| {
                            let field_name = f.ident.as_ref().unwrap().to_string();
                            let (field_expr, _) = type_to_schema_expr(&f.ty);
                            quote! {
                                inner_props.push(format!(r#""{}": {}"#, #field_name, #field_expr));
                            }
                        })
                        .collect();

                    let required_fields: Vec<TokenStream2> = fields
                        .named
                        .iter()
                        .filter_map(|f| {
                            let (_, is_optional) = type_to_schema_expr(&f.ty);
                            if !is_optional {
                                let field_name = f.ident.as_ref().unwrap().to_string();
                                Some(quote! { format!(r#""{}""#, #field_name) })
                            } else {
                                None
                            }
                        })
                        .collect();

                    variant_schema_builders.push(quote! {
                        {
                            let mut inner_props: Vec<String> = Vec::new();
                            #(#field_builders)*
                            let required: Vec<String> = vec![#(#required_fields),*];
                            schemas.push(format!(
                                r#"{{"type": "object", "properties": {{"{}": {{"type": "object", "properties": {{{}}}, "required": [{}]}}}}, "required": ["{}"]}}"#,
                                #variant_name,
                                inner_props.join(", "),
                                required.join(", "),
                                #variant_name
                            ));
                        }
                    });
                }
            }
        }

        quote! {
            impl #name {
                pub fn schema_object() -> String {
                    let mut schemas: Vec<String> = Vec::new();
                    #(#variant_schema_builders)*
                    format!(r#"{{"oneOf": [{}]}}"#, schemas.join(", "))
                }

                pub fn json_schema() -> String {
                    let mut schemas: Vec<String> = Vec::new();
                    #(#variant_schema_builders)*
                    format!(
                        r#"{{"$schema": "http://json-schema.org/draft-07/schema#", "title": "{}", "oneOf": [{}]}}"#,
                        #name_str,
                        schemas.join(", ")
                    )
                }

                pub fn json_schema_pretty() -> String {
                    Self::pretty_print_json(&Self::json_schema())
                }

                fn pretty_print_json(json: &str) -> String {
                    let mut result = String::new();
                    let mut indent = 0;
                    let mut in_string = false;
                    let mut prev_char = ' ';

                    for ch in json.chars() {
                        if ch == '"' && prev_char != '\\' {
                            in_string = !in_string;
                        }

                        if in_string {
                            result.push(ch);
                        } else {
                            match ch {
                                '{' | '[' => {
                                    result.push(ch);
                                    result.push('\n');
                                    indent += 2;
                                    result.push_str(&" ".repeat(indent));
                                }
                                '}' | ']' => {
                                    result.push('\n');
                                    indent = indent.saturating_sub(2);
                                    result.push_str(&" ".repeat(indent));
                                    result.push(ch);
                                }
                                ',' => {
                                    result.push(ch);
                                    result.push('\n');
                                    result.push_str(&" ".repeat(indent));
                                }
                                ':' => {
                                    result.push(ch);
                                    result.push(' ');
                                }
                                ' ' | '\n' | '\t' | '\r' => {}
                                _ => result.push(ch),
                            }
                        }
                        prev_char = ch;
                    }
                    result
                }
            }
        }
    }
}

/// Convert a Rust type to a TokenStream expression that produces the schema string at runtime.
fn type_to_schema_expr(ty: &Type) -> (TokenStream2, bool) {
    match ty {
        Type::Path(type_path) => {
            let segments = &type_path.path.segments;
            if let Some(segment) = segments.last() {
                let type_name = segment.ident.to_string();

                match type_name.as_str() {
                    "String" | "str" => (quote! { r#"{"type": "string"}"#.to_string() }, false),
                    "bool" => (quote! { r#"{"type": "boolean"}"#.to_string() }, false),
                    "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32"
                    | "u64" | "u128" | "usize" => {
                        (quote! { r#"{"type": "integer"}"#.to_string() }, false)
                    }
                    "f32" | "f64" => (quote! { r#"{"type": "number"}"#.to_string() }, false),
                    "Option" => {
                        if let PathArguments::AngleBracketed(args) = &segment.arguments {
                            if let Some(GenericArgument::Type(inner_ty)) = args.args.first() {
                                let (inner_expr, _) = type_to_schema_expr(inner_ty);
                                return (
                                    quote! {
                                        {
                                            let inner = #inner_expr;
                                            if inner.contains(r#""type": "string""#) {
                                                r#"{"type": ["string", "null"]}"#.to_string()
                                            } else if inner.contains(r#""type": "integer""#) {
                                                r#"{"type": ["integer", "null"]}"#.to_string()
                                            } else if inner.contains(r#""type": "number""#) {
                                                r#"{"type": ["number", "null"]}"#.to_string()
                                            } else if inner.contains(r#""type": "boolean""#) {
                                                r#"{"type": ["boolean", "null"]}"#.to_string()
                                            } else if inner.contains(r#""type": "array""#) {
                                                format!(r#"{{"oneOf": [{}, {{"type": "null"}}]}}"#, inner)
                                            } else if inner.contains(r#""type": "object""#) {
                                                format!(r#"{{"oneOf": [{}, {{"type": "null"}}]}}"#, inner)
                                            } else {
                                                format!(r#"{{"oneOf": [{}, {{"type": "null"}}]}}"#, inner)
                                            }
                                        }
                                    },
                                    true,
                                );
                            }
                        }
                        (quote! { r#"{"type": "null"}"#.to_string() }, true)
                    }
                    "Vec" => {
                        if let PathArguments::AngleBracketed(args) = &segment.arguments {
                            if let Some(GenericArgument::Type(inner_ty)) = args.args.first() {
                                let (inner_expr, _) = type_to_schema_expr(inner_ty);
                                return (
                                    quote! {
                                        format!(r#"{{"type": "array", "items": {}}}"#, #inner_expr)
                                    },
                                    false,
                                );
                            }
                        }
                        (quote! { r#"{"type": "array"}"#.to_string() }, false)
                    }
                    "HashMap" | "BTreeMap" => {
                        if let PathArguments::AngleBracketed(args) = &segment.arguments {
                            let mut args_iter = args.args.iter();
                            let _ = args_iter.next();
                            if let Some(GenericArgument::Type(value_ty)) = args_iter.next() {
                                let (value_expr, _) = type_to_schema_expr(value_ty);
                                return (
                                    quote! {
                                        format!(r#"{{"type": "object", "additionalProperties": {}}}"#, #value_expr)
                                    },
                                    false,
                                );
                            }
                        }
                        (quote! { r#"{"type": "object"}"#.to_string() }, false)
                    }
                    _ => {
                        let type_ident = &segment.ident;
                        (quote! { #type_ident::schema_object() }, false)
                    }
                }
            } else {
                (quote! { r#"{"type": "object"}"#.to_string() }, false)
            }
        }
        Type::Reference(type_ref) => type_to_schema_expr(&type_ref.elem),
        Type::Slice(type_slice) => {
            let (inner_expr, _) = type_to_schema_expr(&type_slice.elem);
            (
                quote! { format!(r#"{{"type": "array", "items": {}}}"#, #inner_expr) },
                false,
            )
        }
        Type::Array(type_array) => {
            let (inner_expr, _) = type_to_schema_expr(&type_array.elem);
            (
                quote! { format!(r#"{{"type": "array", "items": {}}}"#, #inner_expr) },
                false,
            )
        }
        Type::Tuple(type_tuple) => {
            let item_exprs: Vec<TokenStream2> = type_tuple
                .elems
                .iter()
                .map(|t| {
                    let (expr, _) = type_to_schema_expr(t);
                    expr
                })
                .collect();
            let len = item_exprs.len();
            (
                quote! {
                    {
                        let items: Vec<String> = vec![#(#item_exprs),*];
                        format!(
                            r#"{{"type": "array", "items": [{}], "minItems": {}, "maxItems": {}}}"#,
                            items.join(", "),
                            #len,
                            #len
                        )
                    }
                },
                false,
            )
        }
        _ => (quote! { r#"{"type": "object"}"#.to_string() }, false),
    }
}

// ============================================================================
// HeroScript Derive Macros
// ============================================================================

/// Derive macro for serializing structs to HeroScript format.
#[proc_macro_derive(ToHeroScript)]
pub fn to_heroscript_derive(input: TokenStream) -> TokenStream {
    heroscript::impl_to_heroscript(input)
}

/// Derive macro for deserializing HeroScript into structs.
#[proc_macro_derive(FromHeroScript)]
pub fn from_heroscript_derive(input: TokenStream) -> TokenStream {
    heroscript::impl_from_heroscript(input)
}

// ============================================================================
// OTOML Derive Macro
// ============================================================================

/// Derive macro for OTOML serialization/deserialization.
///
/// This macro adds two methods to structs that implement `Serialize` and `Deserialize`:
/// - `dump_otoml()` - Serialize to canonical OTOML format (deterministic, sorted keys)
/// - `load_otoml(s)` - Deserialize from any valid TOML string
///
/// # Example
///
/// ```ignore
/// use herolib_derive::Otoml;
/// use serde::{Serialize, Deserialize};
///
/// #[derive(Debug, Serialize, Deserialize, Otoml)]
/// struct Config {
///     name: String,
///     port: u32,
///     debug: bool,
/// }
///
/// let config = Config { name: "server".into(), port: 8080, debug: true };
///
/// // Serialize to canonical OTOML
/// let otoml = config.dump_otoml().unwrap();
/// println!("{}", otoml);
/// // Output:
/// // debug = true
/// // name = "server"
/// // port = 8080
///
/// // Deserialize from TOML
/// let parsed = Config::load_otoml(&otoml).unwrap();
/// ```
///
/// # Requirements
///
/// The struct must also derive `Serialize` and `Deserialize` from serde.
#[proc_macro_derive(Otoml)]
pub fn otoml_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    let expanded = quote! {
        impl #name {
            /// Serializes this value to canonical OTOML format.
            ///
            /// OTOML is a deterministic subset of TOML with:
            /// - Sorted keys (alphabetically)
            /// - Inline tables for nested structures
            /// - Consistent formatting
            /// - "O:\n" prefix header
            pub fn dump_otoml(&self) -> Result<String, herolib_osis::OtomlError> {
                herolib_osis::dump_otoml(self)
            }

            /// Deserializes from an OTOML string.
            ///
            /// The input should start with "O:\n" prefix.
            pub fn load_otoml(s: &str) -> Result<Self, herolib_osis::OtomlError> {
                herolib_osis::load_otoml(s)
            }
        }
    };

    TokenStream::from(expanded)
}

// ============================================================================
// MCP Client Macros
// ============================================================================

#[proc_macro]
pub fn mcp_client_from_json(input: TokenStream) -> TokenStream {
    mcp::impl_mcp_client_from_json(input)
}

#[proc_macro]
pub fn mcp_client_from_file(input: TokenStream) -> TokenStream {
    mcp::impl_mcp_client_from_file(input)
}

#[proc_macro_attribute]
pub fn mcp_tool(attr: TokenStream, item: TokenStream) -> TokenStream {
    mcp::impl_mcp_tool(attr, item)
}

// ============================================================================
// OpenRPC Client Macros
// ============================================================================

/// Generate a typed RPC client from an OpenRPC specification.
///
/// This macro reads an OpenRPC specification and generates a strongly-typed
/// Rust client with methods for each RPC endpoint.
///
/// # Usage
///
/// ## From file path
/// ```ignore
/// use herolib_derive::openrpc_client;
///
/// // Generate client from OpenRPC spec file (path relative to Cargo.toml)
/// openrpc_client!("specs/myservice.openrpc.json");
///
/// // Use the generated client
/// let client = MyServiceClient::connect().await?;
/// let result = client.my_method(MyMethodInput { ... }).await?;
/// ```
///
/// ## From Unix socket (with discover)
/// ```ignore
/// // Generate client that discovers spec from Unix socket at compile time
/// openrpc_client!(socket = "/tmp/myservice.sock");
/// ```
///
/// ## With custom client name
/// ```ignore
/// openrpc_client!("spec.json", name = "CustomClient");
/// ```
///
/// # Generated Code
///
/// For each method in the OpenRPC spec, the macro generates:
/// - Input struct with all parameters
/// - Output struct with result fields
/// - Async method on the client struct
///
/// The generated client supports:
/// - HTTP transport (`connect_http(url)`)
/// - Unix socket transport (`connect_socket(path)`)
/// - Discovery (`client.discover()`)
#[proc_macro]
pub fn openrpc_client(input: TokenStream) -> TokenStream {
    openrpc_client::impl_openrpc_client(input)
}

// ============================================================================
// OsisObject Derive Macro
// ============================================================================

/// Derive macro for implementing `OsisObject` trait.
///
/// This macro generates the `OsisObject` trait implementation for structs
/// that have a `sid: SmartId` field. The `type_name()` defaults to the
/// struct name in snake_case, but can be overridden with an attribute.
///
/// # Attributes
///
/// - `#[osis(type_name = "name")]` - Override the default type name
/// - `#[osis(index = "field1, field2")]` - Specify fields for full-text indexing
///
/// # Example
///
/// ```ignore
/// use herolib_derive::OsisObject;
/// use herolib_osis::sid::SmartId;
/// use serde::{Serialize, Deserialize};
///
/// // Auto-generated type_name: "user"
/// #[derive(Default, Serialize, Deserialize, OsisObject)]
/// struct User {
///     sid: SmartId,
///     name: String,
/// }
///
/// // Custom type_name override
/// #[derive(Default, Serialize, Deserialize, OsisObject)]
/// #[osis(type_name = "members")]
/// struct Member {
///     sid: SmartId,
///     name: String,
/// }
///
/// // With full-text indexing
/// #[derive(Default, Serialize, Deserialize, OsisObject)]
/// #[osis(index = "title, content")]
/// struct Article {
///     sid: SmartId,
///     title: String,
///     content: String,
///     author: String,  // Not indexed
/// }
///
/// // Combined attributes
/// #[derive(Default, Serialize, Deserialize, OsisObject)]
/// #[osis(type_name = "articles", index = "title, content")]
/// struct BlogPost {
///     sid: SmartId,
///     title: String,
///     content: String,
/// }
/// ```
///
/// # Requirements
///
/// - The struct must have a field named `sid` of type `SmartId`
/// - The struct should also derive `Serialize` and `Deserialize` from serde
#[proc_macro_derive(OsisObject, attributes(osis))]
pub fn osis_object_derive(input: TokenStream) -> TokenStream {
    osis_object::impl_osis_object(input)
}

#[cfg(test)]
mod tests {
    // Tests are in a separate test file
}
