//! BAML Runtime for Rust
//!
//! This crate provides the runtime support for BAML-generated Rust code.
//! Users should not import from this crate directly - instead, use the
//! generated `baml_client` crate which re-exports necessary types.
//!
//! # Derive Macros
//!
//! Use `#[derive(BamlEncode)]` and `#[derive(BamlDecode)]` to automatically
//! implement serialization for your types:
//!
//! ```ignore
//! use baml::{BamlEncode, BamlDecode};
//!
//! #[derive(BamlEncode, BamlDecode)]
//! #[baml(name = "Person")]
//! struct Person {
//!     name: String,
//!     #[baml(name = "years_old")]
//!     age: i64,
//! }
//! ```

mod args;
mod async_stream;
mod client_registry;
mod codec;
mod error;
mod ffi;
mod known_types;
pub mod platform;
mod raw_objects;
mod runtime;
mod stream;
mod types;

// Proto module generated by prost-build
mod proto {
    #![allow(clippy::empty_structs_with_brackets)]
    pub(crate) mod baml_cffi_v1 {
        include!(concat!(env!("OUT_DIR"), "/baml.cffi.v1.rs"));
    }
}

use std::ffi::CString;

// Public API - re-exported through baml_client
pub use args::{CancellationToken, FunctionArgs};
// New dynamic type exports
pub use async_stream::AsyncStreamingCall;
// Re-export derive macros
pub use baml_macros::{BamlDecode, BamlEncode, BamlSerde};
pub use client_registry::ClientRegistry;
pub use codec::{
    decode_enum, decode_field, encode_class, encode_class_dynamic, encode_enum, BamlClass,
    BamlDecode, BamlEncode, BamlEnum, BamlSerializeMapKey, BamlValue, DynamicClass, DynamicEnum,
    DynamicUnion, FromBamlValue, FromBamlValueRef, KnownTypes,
};
pub use error::BamlError;
pub use raw_objects::{
    // Media types
    Audio,
    // TypeBuilder types
    ClassBuilder,
    ClassPropertyBuilder,
    // Collector types
    Collector,
    EnumBuilder,
    EnumValueBuilder,
    FunctionLog,
    // HTTP types
    HTTPBody,
    HTTPRequest,
    HTTPResponse,
    Image,
    // LLM call types
    LLMCall,
    LLMCallKind,
    LLMStreamCall,
    LogType,
    Pdf,
    // SSE types
    SSEResponse,
    // Timing types
    StreamTiming,
    Timing,
    TypeBuilder,
    TypeDef,
    Usage,
    Video,
};
pub use runtime::{BamlRuntime, StaticRuntimeType};
pub use stream::{StreamEvent, StreamingCall};
pub use types::{Check, CheckStatus, Checked, StreamState, StreamingState};

/// Internal module for derive macro support.
///
/// This module exposes proto types needed by the generated derive macro code.
/// Do not use directly - use the derive macros instead.
#[doc(hidden)]
pub mod __internal {
    use crate::{codec::traits::BamlClass, error::BamlError};
    pub use crate::{
        ffi::callbacks,
        proto::baml_cffi_v1::*,
        raw_objects::{BamlMediaRepr, BamlMediaReprContent},
    };
    /// re-export
    pub use serde;

    /// Decode a class from a `CffiValueHolder`.
    /// Used by derive macros to implement `BamlDecode` for structs.
    pub fn decode_class<T: BamlClass>(holder: &CffiValueHolder) -> Result<T, BamlError> {
        match &holder.value {
            Some(cffi_value_holder::Value::ClassValue(class)) => T::from_class_value(class),
            other => Err(BamlError::internal(format!(
                "expected class {}, got {:?}",
                T::TYPE_NAME,
                other.as_ref().map(variant_name)
            ))),
        }
    }

    /// Extract the inner value and variant name from a `UnionVariantValue`.
    /// Returns (`variant_name`, `inner_value`) or an error if the holder is not
    /// a `UnionVariantValue`.
    pub fn extract_union_variant_with_name<'a>(
        type_name: &str,
        holder: &'a CffiValueHolder,
    ) -> Result<(&'a str, &'a CffiValueHolder), BamlError> {
        match &holder.value {
            Some(cffi_value_holder::Value::UnionVariantValue(union)) => {
                let inner = union
                    .value
                    .as_ref()
                    .map(std::convert::AsRef::as_ref)
                    .ok_or_else(|| BamlError::internal("union variant missing inner value"))?;
                Ok((union.value_option_name.as_str(), inner))
            }
            _ => Err(BamlError::internal(format!(
                "expected union variant of type {type_name}, got {holder:?}"
            ))),
        }
    }

    fn variant_name(v: &cffi_value_holder::Value) -> &'static str {
        match v {
            cffi_value_holder::Value::NullValue(_) => "null",
            cffi_value_holder::Value::StringValue(_) => "string",
            cffi_value_holder::Value::IntValue(_) => "int",
            cffi_value_holder::Value::FloatValue(_) => "float",
            cffi_value_holder::Value::BoolValue(_) => "bool",
            cffi_value_holder::Value::ClassValue(_) => "class",
            cffi_value_holder::Value::EnumValue(_) => "enum",
            cffi_value_holder::Value::LiteralValue(_) => "literal",
            cffi_value_holder::Value::ObjectValue(_) => "object",
            cffi_value_holder::Value::ListValue(_) => "list",
            cffi_value_holder::Value::MapValue(_) => "map",
            cffi_value_holder::Value::UnionVariantValue(_) => "union",
            cffi_value_holder::Value::CheckedValue(_) => "checked",
            cffi_value_holder::Value::StreamingStateValue(_) => "streaming_state",
        }
    }
}

/// Call baml-cli with the given arguments
/// Returns the exit code
pub fn invoke_cli(args: &[&str]) -> i32 {
    // Convert args to C strings
    let c_args: Vec<CString> = args
        .iter()
        .map(|s| CString::new(*s).expect("invalid arg"))
        .collect();

    // Create array of pointers
    let c_arg_ptrs: Vec<*const libc::c_char> = c_args
        .iter()
        .map(|s| s.as_ptr())
        .chain(std::iter::once(std::ptr::null())) // null terminator
        .collect();

    #[allow(unsafe_code, clippy::print_stderr)]
    unsafe {
        match ffi::invoke_runtime_cli(c_arg_ptrs.as_ptr()) {
            Ok(code) => code,
            Err(e) => {
                // CLI errors should be printed to stderr
                eprintln!("Failed to load BAML library: {e}");
                1
            }
        }
    }
}

/// Get the BAML library version
pub fn version() -> String {
    match ffi::version() {
        Ok(v) => v,
        Err(_) => "unknown".to_string(),
    }
}
