//! Perception Module - Multimodal Embedding and Reasoning
//!
//! This module provides the core abstractions for:
//! - **Perception Engines**: Convert various inputs (text, audio, images) into embeddings
//! - **Reasoning Engines**: Connect to LLMs for higher-level cognitive tasks
//!
//! # Architecture
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────┐
//! │                    Perception Module                         │
//! ├─────────────────────────────────────────────────────────────┤
//! │                                                              │
//! │  ┌──────────────────┐      ┌──────────────────┐             │
//! │  │ PerceptionEngine │      │  ReasoningEngine │             │
//! │  │     (Trait)      │      │     (Trait)      │             │
//! │  └────────┬─────────┘      └────────┬─────────┘             │
//! │           │                          │                       │
//! │  ┌────────┴─────────┐      ┌────────┴─────────┐             │
//! │  │  CandleEngine    │      │   HttpApiClient  │             │
//! │  │  (BERT-based)    │      │  (Ollama/OpenAI) │             │
//! │  └──────────────────┘      └──────────────────┘             │
//! │           │                                                  │
//! │  ┌────────┴─────────┐                                       │
//! │  │ EmbeddingGemma   │  ← NEW: High-quality multilingual     │
//! │  │  (Gemma3-based)  │        embedding model                │
//! │  └──────────────────┘                                       │
//! │                                                              │
//! └─────────────────────────────────────────────────────────────┘
//! ```
//!
//! # Usage
//!
//! ## With Built-in Candle Engine (requires `candle` feature)
//!
//! ```ignore
//! use aifastdb_core::perception::{PerceptionConfig, create_perception_engine};
//!
//! let config = PerceptionConfig::minilm();
//! let engine = create_perception_engine(&config)?;
//!
//! let embedding = engine.conceptualize("Hello, world!")?;
//! ```
//!
//! ## With EmbeddingGemma (requires `candle` feature)
//!
//! ```ignore
//! use aifastdb_core::perception::embedding_gemma::{
//!     EmbeddingGemmaConfig, EmbeddingGemmaModel, TaskType
//! };
//!
//! let config = EmbeddingGemmaConfig::default();
//! let model = EmbeddingGemmaModel::new(config, vb)?;
//!
//! // Generate embeddings with automatic task prefix
//! let embeddings = model.encode(&input_ids, &attention_mask, None)?;
//!
//! // Use Matryoshka truncation for smaller dimensions
//! let embeddings_256 = model.encode(&input_ids, &attention_mask, Some(256))?;
//! ```
//!
//! ## With External Reasoning (Ollama)
//!
//! ```ignore
//! use aifastdb_core::perception::{ReasoningConfig, create_reasoning_engine};
//!
//! let config = ReasoningConfig::ollama("gemma3:27b");
//! let engine = create_reasoning_engine(&config)?;
//!
//! let result = engine.understand_intent("What's the weather like?")?;
//! ```

// Sub-modules
mod config;
mod error;
mod traits;
pub mod batcher;

// Feature-gated implementations
#[cfg(feature = "candle")]
mod candle;

// EmbeddingGemma module (requires candle feature)
#[cfg(feature = "candle")]
pub mod embedding_gemma;

// Whisper audio embedding module (requires audio feature)
#[cfg(feature = "audio")]
pub mod whisper;

// CLIP multimodal embedding module
pub mod clip;

// Multimodal embedding pipeline (auto-embed + cache integration)
pub mod multimodal_pipeline;

#[cfg(feature = "http-client")]
mod http_client;

// Re-exports
pub use config::{PerceptionConfig, PerceptionModuleConfig, ReasoningConfig};
pub use error::{PerceptionError, PerceptionResult, ReasoningError, ReasoningResult};
pub use traits::{
    CompositeEngine, DummyPerceptionEngine, Message, MessageRole, Modality, PerceptionEngine,
    PerceptionResult as PerceptionOutput, ReasoningEngine, ReasoningResult as ReasoningOutput,
    TokenUsage,
};

// Feature-gated exports
#[cfg(feature = "candle")]
pub use candle::CandleEmbeddingEngine;

#[cfg(feature = "http-client")]
pub use http_client::HttpApiClient;

// CLIP multimodal exports
pub use clip::{ClipEmbeddingEngine, ClipModelConfig};

// Whisper audio exports
#[cfg(feature = "audio")]
pub use whisper::{WhisperEmbeddingEngine, WhisperModelConfig};

// Multimodal pipeline exports
pub use multimodal_pipeline::{
    embed_multimodal_record,
    embed_multimodal_record_cached,
    embed_multimodal_record_with_key,
    embed_multimodal_record_cached_with_key,
    embed_multimodal_batch,
    EmbedResult,
    BatchEmbedResult,
    AUDIO_EMBEDDING_KEY,
    IMAGE_EMBEDDING_KEY,
    WHISPER_CACHE_MODALITY,
};

// Batcher exports
pub use batcher::{
    EmbeddingBatcher,
    EmbeddingBatcherConfig,
    EmbeddingBatcherMetrics,
    EmbeddingBatcherError,
    EmbeddingRequest,
    EmbeddingResponse,
    start_embedding_worker,
};

use std::sync::Arc;

// ============================================================================
// Factory Functions
// ============================================================================

/// Create a perception engine from configuration
///
/// Returns a boxed PerceptionEngine trait object based on the config.
/// If the required feature is not enabled, returns an error.
pub fn create_perception_engine(
    config: &PerceptionConfig,
) -> Result<Box<dyn PerceptionEngine>, PerceptionError> {
    match config.engine_type.as_str() {
        "none" | "dummy" => {
            let dim = config.dimension.unwrap_or(384);
            Ok(Box::new(DummyPerceptionEngine::new(dim)))
        }
        #[cfg(feature = "candle")]
        "candle" => {
            let engine = CandleEmbeddingEngine::from_config(config)?;
            Ok(Box::new(engine))
        }
        #[cfg(not(feature = "candle"))]
        "candle" => Err(PerceptionError::FeatureNotEnabled(
            "candle feature is required for Candle engine".to_string(),
        )),
        // EmbeddingGemma engine (requires candle feature)
        #[cfg(feature = "candle")]
        "embedding_gemma" => {
            let engine = embedding_gemma::EmbeddingGemmaEngine::from_config(config)?;
            Ok(Box::new(engine))
        }
        #[cfg(not(feature = "candle"))]
        "embedding_gemma" => Err(PerceptionError::FeatureNotEnabled(
            "candle feature is required for EmbeddingGemma engine".to_string(),
        )),
        // Whisper audio embedding engine (requires audio feature)
        #[cfg(feature = "audio")]
        "whisper" => {
            let engine = WhisperEmbeddingEngine::from_perception_config(config)?;
            Ok(Box::new(engine))
        }
        #[cfg(not(feature = "audio"))]
        "whisper" => Err(PerceptionError::FeatureNotEnabled(
            "audio feature is required for Whisper engine".to_string(),
        )),
        other => Err(PerceptionError::ConfigError(format!(
            "Unknown engine type: {}",
            other
        ))),
    }
}

/// Create a reasoning engine from configuration
///
/// Returns a boxed ReasoningEngine trait object based on the config.
#[cfg(feature = "http-client")]
pub fn create_reasoning_engine(
    config: &ReasoningConfig,
) -> Result<Box<dyn ReasoningEngine>, ReasoningError> {
    let client = HttpApiClient::from_config(config)?;
    Ok(Box::new(client))
}

#[cfg(not(feature = "http-client"))]
pub fn create_reasoning_engine(
    _config: &ReasoningConfig,
) -> Result<Box<dyn ReasoningEngine>, ReasoningError> {
    Err(ReasoningError::RequestError(
        "http-client feature is required for reasoning engine".to_string(),
    ))
}

/// Create a composite engine with both perception and reasoning
pub fn create_composite_engine(
    config: &PerceptionModuleConfig,
) -> Result<CompositeEngine, PerceptionError> {
    let perception = if let Some(ref p_config) = config.perception {
        create_perception_engine(p_config)?
    } else {
        Box::new(DummyPerceptionEngine::new(384))
    };

    let mut engine = CompositeEngine::new(perception);

    if let Some(ref r_config) = config.reasoning {
        match create_reasoning_engine(r_config) {
            Ok(reasoning) => {
                engine = engine.with_reasoning(reasoning);
            }
            Err(e) => {
                tracing::warn!("Failed to create reasoning engine: {}", e);
            }
        }
    }

    Ok(engine)
}

/// Create a shared perception engine wrapped in Arc
pub fn create_shared_perception_engine(
    config: &PerceptionConfig,
) -> Result<Arc<dyn PerceptionEngine>, PerceptionError> {
    let engine = create_perception_engine(config)?;
    Ok(Arc::from(engine))
}

// ============================================================================
// Utility Functions
// ============================================================================

/// Normalize a vector to unit length (L2 normalization)
pub fn normalize_embedding(embedding: &mut [f32]) {
    let norm: f32 = embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
    if norm > 0.0 {
        for x in embedding {
            *x /= norm;
        }
    }
}

/// Calculate cosine similarity between two embeddings
pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
    if a.len() != b.len() {
        return 0.0;
    }
    let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
    let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
    let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
    if norm_a > 0.0 && norm_b > 0.0 {
        dot / (norm_a * norm_b)
    } else {
        0.0
    }
}

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

    #[test]
    fn test_normalize_embedding() {
        let mut embedding = vec![3.0, 4.0];
        normalize_embedding(&mut embedding);
        let norm: f32 = embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
        assert!((norm - 1.0).abs() < 1e-5);
    }

    #[test]
    fn test_cosine_similarity() {
        let a = vec![1.0, 0.0];
        let b = vec![1.0, 0.0];
        assert!((cosine_similarity(&a, &b) - 1.0).abs() < 1e-5);

        let c = vec![1.0, 0.0];
        let d = vec![0.0, 1.0];
        assert!(cosine_similarity(&c, &d).abs() < 1e-5);
    }

    #[test]
    fn test_create_dummy_engine() {
        let config = PerceptionConfig::default();
        let engine = create_perception_engine(&config).unwrap();
        assert_eq!(engine.dimension(), 384);
        assert_eq!(engine.model_name(), "dummy");
    }
}
