//! Core perception and reasoning traits
//!
//! Defines the abstract interfaces for perception engines (embedding generation)
//! and reasoning engines (LLM-based logic processing).

use serde::{Deserialize, Serialize};

use super::error::{PerceptionError, ReasoningError};

// ============================================================================
// Modality Types
// ============================================================================

/// Supported modality types for perception
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Modality {
    /// Plain text content
    Text,
    /// Source code (with language awareness)
    Code,
    /// Audio content (music, speech, etc.)
    Audio,
    /// Image content
    Image,
    /// Video content
    Video,
}

impl Modality {
    /// Get the modality from a MIME type string
    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
        match mime_type {
            s if s.starts_with("text/") => Some(Modality::Text),
            s if s.starts_with("audio/") => Some(Modality::Audio),
            s if s.starts_with("image/") => Some(Modality::Image),
            s if s.starts_with("video/") => Some(Modality::Video),
            "application/json" | "application/xml" => Some(Modality::Text),
            "application/javascript" | "application/typescript" => Some(Modality::Code),
            _ => None,
        }
    }

    /// Get a human-readable name for the modality
    pub fn name(&self) -> &'static str {
        match self {
            Modality::Text => "text",
            Modality::Code => "code",
            Modality::Audio => "audio",
            Modality::Image => "image",
            Modality::Video => "video",
        }
    }
}

// ============================================================================
// Perception Result
// ============================================================================

/// Result of a perception operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerceptionResult {
    /// The generated embedding vector
    pub embedding: Vec<f32>,
    /// The detected modality
    pub modality: Modality,
    /// Confidence score (0.0 - 1.0)
    pub confidence: f32,
    /// Optional metadata extracted during perception
    pub metadata: Option<serde_json::Value>,
}

impl PerceptionResult {
    /// Create a new perception result
    pub fn new(embedding: Vec<f32>, modality: Modality) -> Self {
        Self {
            embedding,
            modality,
            confidence: 1.0,
            metadata: None,
        }
    }

    /// Set the confidence score
    pub fn with_confidence(mut self, confidence: f32) -> Self {
        self.confidence = confidence.clamp(0.0, 1.0);
        self
    }

    /// Set metadata
    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
        self.metadata = Some(metadata);
        self
    }

    /// Get the embedding dimension
    pub fn dimension(&self) -> usize {
        self.embedding.len()
    }
}

// ============================================================================
// Perception Engine Trait
// ============================================================================

/// Core trait for perception engines
///
/// A perception engine converts various types of input (text, audio, images)
/// into dense vector embeddings that can be used for similarity search.
///
/// # Example Implementation
///
/// ```ignore
/// struct MyEmbeddingEngine { /* ... */ }
///
/// impl PerceptionEngine for MyEmbeddingEngine {
///     fn conceptualize(&self, input: &str) -> Result<Vec<f32>, PerceptionError> {
///         // Generate embedding from text
///     }
///     
///     fn dimension(&self) -> usize { 384 }
///     
///     fn supported_modalities(&self) -> Vec<Modality> {
///         vec![Modality::Text]
///     }
/// }
/// ```
pub trait PerceptionEngine: Send + Sync {
    /// Convert text/code input into a semantic embedding vector
    ///
    /// This is the primary method for text-based perception. It should:
    /// 1. Tokenize the input
    /// 2. Run inference through the model
    /// 3. Apply pooling (mean, CLS, etc.)
    /// 4. Normalize the output (L2 normalization recommended)
    ///
    /// # Arguments
    /// * `input` - The text or code to conceptualize
    ///
    /// # Returns
    /// A vector of f32 values representing the semantic embedding
    fn conceptualize(&self, input: &str) -> Result<Vec<f32>, PerceptionError>;

    /// Convert multimodal data (audio, image) into an aligned embedding vector
    ///
    /// This method handles non-text modalities. The resulting embedding should
    /// be in the same semantic space as text embeddings (for cross-modal search).
    ///
    /// # Arguments
    /// * `data` - Raw bytes of the media file
    /// * `mime_type` - MIME type (e.g., "audio/mpeg", "image/jpeg")
    ///
    /// # Returns
    /// A vector of f32 values in the joint embedding space
    fn align_modality(&self, data: &[u8], mime_type: &str) -> Result<Vec<f32>, PerceptionError> {
        // Default implementation: unsupported
        let _ = data;
        Err(PerceptionError::UnsupportedModality(mime_type.to_string()))
    }

    /// Batch processing for multiple text inputs
    ///
    /// More efficient than calling `conceptualize` multiple times.
    ///
    /// # Arguments
    /// * `inputs` - Slice of text inputs
    ///
    /// # Returns
    /// A vector of embedding vectors
    fn conceptualize_batch(&self, inputs: &[&str]) -> Result<Vec<Vec<f32>>, PerceptionError> {
        // Default implementation: sequential processing
        inputs.iter().map(|input| self.conceptualize(input)).collect()
    }

    /// Get the output embedding dimension
    fn dimension(&self) -> usize;

    /// Get the list of supported modalities
    fn supported_modalities(&self) -> Vec<Modality>;

    /// Check if a specific modality is supported
    fn supports_modality(&self, modality: Modality) -> bool {
        self.supported_modalities().contains(&modality)
    }

    /// Get the model name/identifier
    fn model_name(&self) -> &str {
        "unknown"
    }

    /// Get maximum input length (tokens or characters)
    fn max_input_length(&self) -> Option<usize> {
        None
    }
}

// ============================================================================
// Reasoning Engine Trait
// ============================================================================

/// Message role in a conversation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MessageRole {
    System,
    User,
    Assistant,
}

/// A message in a conversation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
    pub role: MessageRole,
    pub content: String,
}

impl Message {
    pub fn system(content: impl Into<String>) -> Self {
        Self {
            role: MessageRole::System,
            content: content.into(),
        }
    }

    pub fn user(content: impl Into<String>) -> Self {
        Self {
            role: MessageRole::User,
            content: content.into(),
        }
    }

    pub fn assistant(content: impl Into<String>) -> Self {
        Self {
            role: MessageRole::Assistant,
            content: content.into(),
        }
    }
}

/// Result of a reasoning operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReasoningResult {
    /// The generated response
    pub content: String,
    /// Token usage information
    pub usage: Option<TokenUsage>,
    /// Model used for generation
    pub model: Option<String>,
}

/// Token usage information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenUsage {
    pub prompt_tokens: usize,
    pub completion_tokens: usize,
    pub total_tokens: usize,
}

/// Trait for reasoning engines (LLM-based logic processing)
///
/// Reasoning engines handle higher-level cognitive tasks like:
/// - Intent understanding
/// - Logical consistency checking
/// - Summarization
/// - Question answering
///
/// These typically connect to external LLM APIs (Ollama, OpenAI, etc.)
pub trait ReasoningEngine: Send + Sync {
    /// Check logical consistency between two facts
    ///
    /// # Arguments
    /// * `fact_a` - First fact/statement
    /// * `fact_b` - Second fact/statement
    ///
    /// # Returns
    /// `true` if the facts are logically consistent, `false` otherwise
    fn check_logic_consistency(&self, fact_a: &str, fact_b: &str) -> Result<bool, ReasoningError>;

    /// Understand the intent behind a query
    ///
    /// # Arguments
    /// * `query` - The user's query or request
    ///
    /// # Returns
    /// A structured understanding of the intent
    fn understand_intent(&self, query: &str) -> Result<String, ReasoningError>;

    /// Generate a summary of the given content
    ///
    /// # Arguments
    /// * `content` - The content to summarize
    /// * `max_tokens` - Maximum length of the summary
    ///
    /// # Returns
    /// A condensed summary
    fn summarize(&self, content: &str, max_tokens: usize) -> Result<String, ReasoningError>;

    /// Chat completion with message history
    ///
    /// # Arguments
    /// * `messages` - Conversation history
    ///
    /// # Returns
    /// The assistant's response
    fn chat(&self, messages: &[Message]) -> Result<ReasoningResult, ReasoningError>;

    /// Get the model name
    fn model_name(&self) -> &str;

    /// Check if the engine is available/connected
    fn is_available(&self) -> bool {
        true
    }
}

// ============================================================================
// Composite Engine
// ============================================================================

/// A composite engine that combines perception and reasoning
pub struct CompositeEngine {
    pub perception: Box<dyn PerceptionEngine>,
    pub reasoning: Option<Box<dyn ReasoningEngine>>,
}

impl CompositeEngine {
    pub fn new(perception: Box<dyn PerceptionEngine>) -> Self {
        Self {
            perception,
            reasoning: None,
        }
    }

    pub fn with_reasoning(mut self, reasoning: Box<dyn ReasoningEngine>) -> Self {
        self.reasoning = Some(reasoning);
        self
    }

    pub fn has_reasoning(&self) -> bool {
        self.reasoning.is_some()
    }
}

// ============================================================================
// Dummy/Passthrough Implementations (for testing)
// ============================================================================

/// A dummy perception engine that returns random or zero embeddings
/// Useful for testing when no real model is available
pub struct DummyPerceptionEngine {
    dimension: usize,
}

impl DummyPerceptionEngine {
    pub fn new(dimension: usize) -> Self {
        Self { dimension }
    }
}

impl PerceptionEngine for DummyPerceptionEngine {
    fn conceptualize(&self, input: &str) -> Result<Vec<f32>, PerceptionError> {
        // Generate a simple hash-based embedding for deterministic testing
        let mut embedding = vec![0.0f32; self.dimension];
        for (i, byte) in input.bytes().enumerate() {
            embedding[i % self.dimension] += byte as f32 / 255.0;
        }
        // L2 normalize
        let norm: f32 = embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
        if norm > 0.0 {
            for x in &mut embedding {
                *x /= norm;
            }
        }
        Ok(embedding)
    }

    fn dimension(&self) -> usize {
        self.dimension
    }

    fn supported_modalities(&self) -> Vec<Modality> {
        vec![Modality::Text]
    }

    fn model_name(&self) -> &str {
        "dummy"
    }
}

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

    #[test]
    fn test_modality_from_mime_type() {
        assert_eq!(Modality::from_mime_type("text/plain"), Some(Modality::Text));
        assert_eq!(Modality::from_mime_type("audio/mpeg"), Some(Modality::Audio));
        assert_eq!(Modality::from_mime_type("image/jpeg"), Some(Modality::Image));
        assert_eq!(Modality::from_mime_type("video/mp4"), Some(Modality::Video));
        assert_eq!(Modality::from_mime_type("application/octet-stream"), None);
    }

    #[test]
    fn test_dummy_perception_engine() {
        let engine = DummyPerceptionEngine::new(384);
        let embedding = engine.conceptualize("hello world").unwrap();
        assert_eq!(embedding.len(), 384);
        
        // Check L2 normalization
        let norm: f32 = embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
        assert!((norm - 1.0).abs() < 1e-5);
    }

    #[test]
    fn test_perception_result() {
        let result = PerceptionResult::new(vec![0.1, 0.2, 0.3], Modality::Text)
            .with_confidence(0.95)
            .with_metadata(serde_json::json!({"source": "test"}));
        
        assert_eq!(result.dimension(), 3);
        assert_eq!(result.confidence, 0.95);
        assert!(result.metadata.is_some());
    }
}
