//! Semantic checking types for AiFastDb
//!
//! Defines the core types for semantic deduplication and conflict detection.

use serde::{Deserialize, Serialize};

/// Result of a pre-ingest semantic check
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CheckResult {
    /// No issues found, safe to insert
    Safe,
    /// Potential duplicate found, needs review
    DuplicateSuspect(Vec<DuplicateCandidate>),
    /// Potential logical conflict found, needs review
    ConflictSuspect(Vec<ConflictCandidate>),
    /// Both duplicate and conflict suspects found
    Mixed {
        duplicates: Vec<DuplicateCandidate>,
        conflicts: Vec<ConflictCandidate>,
    },
}

impl CheckResult {
    /// Check if the result indicates any issues
    pub fn has_issues(&self) -> bool {
        !matches!(self, CheckResult::Safe)
    }

    /// Check if result contains duplicate suspects
    pub fn has_duplicates(&self) -> bool {
        matches!(self, CheckResult::DuplicateSuspect(_) | CheckResult::Mixed { .. })
    }

    /// Check if result contains conflict suspects
    pub fn has_conflicts(&self) -> bool {
        matches!(self, CheckResult::ConflictSuspect(_) | CheckResult::Mixed { .. })
    }

    /// Get all duplicate candidates
    pub fn duplicates(&self) -> Vec<&DuplicateCandidate> {
        match self {
            CheckResult::DuplicateSuspect(candidates) => candidates.iter().collect(),
            CheckResult::Mixed { duplicates, .. } => duplicates.iter().collect(),
            _ => Vec::new(),
        }
    }

    /// Get all conflict candidates
    pub fn conflicts(&self) -> Vec<&ConflictCandidate> {
        match self {
            CheckResult::ConflictSuspect(candidates) => candidates.iter().collect(),
            CheckResult::Mixed { conflicts, .. } => conflicts.iter().collect(),
            _ => Vec::new(),
        }
    }
}

/// A candidate for semantic duplication
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DuplicateCandidate {
    /// ID of the existing node
    pub id: String,
    /// Content of the existing node
    pub content: String,
    /// Vector similarity score (0.0 - 1.0)
    pub similarity: f32,
    /// Cross-encoder confidence score (0.0 - 1.0)
    /// Higher means more likely to be a true duplicate
    pub confidence: f32,
    /// Reason for flagging as duplicate
    pub reason: DuplicateReason,
}

/// Reason for flagging as a potential duplicate
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DuplicateReason {
    /// High vector similarity
    HighSimilarity,
    /// Cross-encoder determined semantic equivalence
    SemanticEquivalence,
    /// Exact or near-exact text match
    TextMatch,
    /// Paraphrase detected (same meaning, different words)
    Paraphrase,
}

impl std::fmt::Display for DuplicateReason {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            DuplicateReason::HighSimilarity => write!(f, "High vector similarity"),
            DuplicateReason::SemanticEquivalence => write!(f, "Semantic equivalence"),
            DuplicateReason::TextMatch => write!(f, "Text match"),
            DuplicateReason::Paraphrase => write!(f, "Paraphrase detected"),
        }
    }
}

/// A candidate for logical conflict
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConflictCandidate {
    /// ID of the conflicting node
    pub id: String,
    /// Content of the conflicting node
    pub content: String,
    /// Conflict severity score (0.0 - 1.0)
    pub severity: f32,
    /// Type of conflict detected
    pub conflict_type: ConflictType,
    /// Human-readable explanation of the conflict
    pub explanation: Option<String>,
}

/// Type of logical conflict
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ConflictType {
    /// Direct contradiction (A vs not A)
    Contradiction,
    /// Temporal conflict (same time, different events)
    Temporal,
    /// Spatial conflict (same time, incompatible locations)
    Spatial,
    /// Factual inconsistency
    Factual,
    /// Logical incompatibility
    Logical,
}

impl std::fmt::Display for ConflictType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ConflictType::Contradiction => write!(f, "Direct contradiction"),
            ConflictType::Temporal => write!(f, "Temporal conflict"),
            ConflictType::Spatial => write!(f, "Spatial conflict"),
            ConflictType::Factual => write!(f, "Factual inconsistency"),
            ConflictType::Logical => write!(f, "Logical incompatibility"),
        }
    }
}

/// Configuration for the semantic checker
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SemanticCheckerConfig {
    /// Enable duplicate detection
    pub detect_duplicates: bool,
    /// Enable conflict detection
    pub detect_conflicts: bool,
    /// Similarity threshold for duplicate detection (0.0 - 1.0)
    /// Vectors above this similarity will be considered for duplicate check
    pub similarity_threshold: f32,
    /// Confidence threshold for marking as duplicate (0.0 - 1.0)
    /// Cross-encoder scores above this will be flagged as duplicates
    pub duplicate_confidence_threshold: f32,
    /// Conflict severity threshold (0.0 - 1.0)
    pub conflict_severity_threshold: f32,
    /// Number of nearest neighbors to check
    pub top_k: usize,
    /// Whether to use cross-encoder for refinement
    pub use_cross_encoder: bool,
    /// Whether to check temporal constraints
    pub check_temporal: bool,
}

impl Default for SemanticCheckerConfig {
    fn default() -> Self {
        Self {
            detect_duplicates: true,
            detect_conflicts: true,
            similarity_threshold: 0.80,
            duplicate_confidence_threshold: 0.90,
            conflict_severity_threshold: 0.70,
            top_k: 10,
            use_cross_encoder: true,
            check_temporal: true,
        }
    }
}

impl SemanticCheckerConfig {
    /// Create a new config with default values
    pub fn new() -> Self {
        Self::default()
    }

    /// Set similarity threshold
    pub fn with_similarity_threshold(mut self, threshold: f32) -> Self {
        self.similarity_threshold = threshold.clamp(0.0, 1.0);
        self
    }

    /// Set duplicate confidence threshold
    pub fn with_duplicate_confidence_threshold(mut self, threshold: f32) -> Self {
        self.duplicate_confidence_threshold = threshold.clamp(0.0, 1.0);
        self
    }

    /// Set conflict severity threshold
    pub fn with_conflict_severity_threshold(mut self, threshold: f32) -> Self {
        self.conflict_severity_threshold = threshold.clamp(0.0, 1.0);
        self
    }

    /// Set top K neighbors to check
    pub fn with_top_k(mut self, k: usize) -> Self {
        self.top_k = k.max(1);
        self
    }

    /// Enable or disable cross-encoder
    pub fn with_cross_encoder(mut self, enabled: bool) -> Self {
        self.use_cross_encoder = enabled;
        self
    }

    /// Enable or disable duplicate detection
    pub fn with_duplicate_detection(mut self, enabled: bool) -> Self {
        self.detect_duplicates = enabled;
        self
    }

    /// Enable or disable conflict detection
    pub fn with_conflict_detection(mut self, enabled: bool) -> Self {
        self.detect_conflicts = enabled;
        self
    }
}

/// Action to take when a duplicate is detected
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DuplicateAction {
    /// Merge the new content with the existing node
    Merge { target_id: String },
    /// Overwrite the existing node with the new content
    Overwrite { target_id: String },
    /// Skip insertion, keep the existing node
    Skip,
    /// Create a new node anyway (ignore duplicate)
    CreateNew,
    /// Update the existing node's metadata/importance only
    UpdateMetadata { target_id: String },
}

/// Action to take when a conflict is detected
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ConflictAction {
    /// Mark the old memory as deprecated and insert new one
    Deprecate { old_id: String },
    /// Keep both memories with a conflict link
    KeepBoth { link_type: String },
    /// Cancel the insertion
    Cancel,
    /// Force insert (ignore conflict)
    ForceInsert,
    /// Request manual resolution
    RequestResolution { context: String },
}

/// Error types for semantic operations
#[derive(Debug, Clone)]
pub enum SemanticError {
    /// Vector dimension mismatch
    DimensionMismatch { expected: usize, got: usize },
    /// No embedding provided
    NoEmbedding,
    /// Cross-encoder not available
    CrossEncoderUnavailable,
    /// Index error during search
    IndexError(String),
    /// Configuration error
    ConfigError(String),
}

impl std::fmt::Display for SemanticError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SemanticError::DimensionMismatch { expected, got } => {
                write!(f, "Dimension mismatch: expected {}, got {}", expected, got)
            }
            SemanticError::NoEmbedding => write!(f, "No embedding provided for semantic check"),
            SemanticError::CrossEncoderUnavailable => write!(f, "Cross-encoder model not available"),
            SemanticError::IndexError(msg) => write!(f, "Index error: {}", msg),
            SemanticError::ConfigError(msg) => write!(f, "Configuration error: {}", msg),
        }
    }
}

impl std::error::Error for SemanticError {}

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

    #[test]
    fn test_check_result_safe() {
        let result = CheckResult::Safe;
        assert!(!result.has_issues());
        assert!(!result.has_duplicates());
        assert!(!result.has_conflicts());
    }

    #[test]
    fn test_check_result_duplicate() {
        let candidate = DuplicateCandidate {
            id: "test-id".to_string(),
            content: "test content".to_string(),
            similarity: 0.95,
            confidence: 0.92,
            reason: DuplicateReason::HighSimilarity,
        };
        let result = CheckResult::DuplicateSuspect(vec![candidate]);
        
        assert!(result.has_issues());
        assert!(result.has_duplicates());
        assert!(!result.has_conflicts());
        assert_eq!(result.duplicates().len(), 1);
    }

    #[test]
    fn test_config_builder() {
        let config = SemanticCheckerConfig::new()
            .with_similarity_threshold(0.85)
            .with_top_k(20)
            .with_cross_encoder(false);
        
        assert_eq!(config.similarity_threshold, 0.85);
        assert_eq!(config.top_k, 20);
        assert!(!config.use_cross_encoder);
    }

    #[test]
    fn test_config_threshold_clamping() {
        let config = SemanticCheckerConfig::new()
            .with_similarity_threshold(1.5)  // Should clamp to 1.0
            .with_conflict_severity_threshold(-0.5);  // Should clamp to 0.0
        
        assert_eq!(config.similarity_threshold, 1.0);
        assert_eq!(config.conflict_severity_threshold, 0.0);
    }
}
