//! Cross-project learning configuration.
//!
//! Enables patterns learned locally (from LLM extraction) to be shared across
//! organizations via the hosted server, aggregated into community extractors,
//! and distributed back to opted-in orgs.
//!
//! # User Journey
//!
//! ```text
//! [Org A: 3 projects see pattern] → [Sync to hosted]
//! [Org B: 5 projects see pattern] → [Sync to hosted]
//! [Org C: 4 projects see pattern] → [Sync to hosted]
//!         ↓
//! [Server aggregates: 12 projects total]
//!         ↓
//! [Reaches threshold (50 projects)]
//!         ↓
//! [Promotes to community extractor]
//!         ↓
//! [Opted-in orgs pull new extractor]
//! ```
//!
//! # Privacy Guarantees
//!
//! | Shared              | NOT Shared       | Why                    |
//! |---------------------|------------------|------------------------|
//! | `normalized_pattern`| `example_code`   | No proprietary code    |
//! | `claim_template`    | File paths       | No location data       |
//! | `project_count`     | `project_hashes` | Only count, not IDs    |
//! | `language`          | Org name         | Only BLAKE3 hash of org|
//! | `avg_confidence`    | Line numbers     | Statistical only       |
//!
//! # Example
//!
//! ```toml
//! [cross_project]
//! contribute_patterns = true   # Opt-in to share patterns
//! receive_community = true     # Opt-in to receive community extractors
//! min_local_projects = 3       # Require pattern seen in 3+ local projects
//! min_local_confidence = 0.85  # Require 85% confidence before sharing
//! sync_interval_secs = 3600    # Sync every hour
//! exclude_subjects = ["code://*/internal/*"]  # Don't share internal patterns
//! ```

use serde::Deserialize;

/// Cross-project learning configuration for pattern sharing.
///
/// Controls how learned patterns are shared with the hosted server
/// and how community extractors are received.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct CrossProjectConfig {
    /// Enable pattern sync to hosted server (default: false).
    ///
    /// CRITICAL: Opt-in only. When enabled, patterns that meet the
    /// local thresholds are anonymized and synced to the hosted server.
    pub contribute_patterns: bool,

    /// Receive community extractors from hosted server (default: false).
    ///
    /// CRITICAL: Opt-in only. When enabled, community extractors that
    /// have been aggregated from many organizations are pulled down.
    pub receive_community: bool,

    /// Minimum local projects before sharing pattern (default: 3).
    ///
    /// Patterns must be seen in at least this many local projects
    /// before being eligible for sharing. This prevents one-off
    /// patterns from polluting the community corpus.
    pub min_local_projects: usize,

    /// Minimum local confidence before sharing (default: 0.85).
    ///
    /// Patterns must have an average confidence of at least this
    /// threshold before being eligible for sharing.
    pub min_local_confidence: f32,

    /// Sync interval in seconds (default: 3600 = 1 hour).
    ///
    /// How often to check for new patterns to sync or community
    /// extractors to pull. Set to 0 to disable automatic sync.
    pub sync_interval_secs: u64,

    /// Exclude patterns matching these subject prefixes.
    ///
    /// Patterns with subjects starting with any of these prefixes
    /// will never be shared, even if they meet other thresholds.
    /// Useful for internal or proprietary patterns.
    ///
    /// # Example
    ///
    /// ```toml
    /// exclude_subjects = [
    ///     "code://*/internal/*",
    ///     "vendor://acme/*",
    /// ]
    /// ```
    pub exclude_subjects: Vec<String>,
}

impl Default for CrossProjectConfig {
    fn default() -> Self {
        Self {
            // CRITICAL: Opt-in only - privacy first
            contribute_patterns: false,
            receive_community: false,
            // Require pattern seen in 3+ projects
            min_local_projects: 3,
            // Require high confidence
            min_local_confidence: 0.85,
            // Sync hourly by default
            sync_interval_secs: 3600,
            // No exclusions by default
            exclude_subjects: vec![],
        }
    }
}

impl CrossProjectConfig {
    /// Returns true if pattern contribution is enabled.
    pub fn is_contribution_enabled(&self) -> bool {
        self.contribute_patterns
    }

    /// Returns true if community extractor reception is enabled.
    pub fn is_reception_enabled(&self) -> bool {
        self.receive_community
    }

    /// Check if a subject is excluded from sharing.
    ///
    /// Returns true if the subject matches any exclude pattern.
    pub fn is_subject_excluded(&self, subject: &str) -> bool {
        self.exclude_subjects.iter().any(|prefix| subject.starts_with(prefix))
    }
}

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

    #[test]
    fn test_default_is_opt_in() {
        let config = CrossProjectConfig::default();
        assert!(!config.contribute_patterns);
        assert!(!config.receive_community);
    }

    #[test]
    fn test_default_thresholds() {
        let config = CrossProjectConfig::default();
        assert_eq!(config.min_local_projects, 3);
        assert!((config.min_local_confidence - 0.85).abs() < 0.001);
        assert_eq!(config.sync_interval_secs, 3600);
    }

    #[test]
    fn test_subject_exclusion() {
        // Note: is_subject_excluded uses simple prefix matching with starts_with
        // so patterns like "code://*/internal/*" won't work - use specific prefixes
        let config = CrossProjectConfig {
            exclude_subjects: vec![
                "code://rust/internal/".to_string(),
                "vendor://acme/".to_string(),
            ],
            ..Default::default()
        };

        assert!(config.is_subject_excluded("code://rust/internal/auth"));
        assert!(config.is_subject_excluded("vendor://acme/secret"));
        assert!(!config.is_subject_excluded("code://rust/tls/min_version"));
    }

    #[test]
    fn test_serde_defaults() {
        let toml = r#"
        contribute_patterns = true
        "#;

        let config: CrossProjectConfig = toml::from_str(toml).expect("parse");
        assert!(config.contribute_patterns);
        assert!(!config.receive_community); // Uses default
        assert_eq!(config.min_local_projects, 3); // Uses default
    }
}
