//! Sensitivity mapping and bridge field detection.

use super::profile::ColumnProfile;
use super::signals::{is_timestamp_column, is_fk_column};
use crate::ir::IdentityPlan;

// ---------------------------------------------------------------------------
// Sensitivity mapping (moved from crm.rs)
// ---------------------------------------------------------------------------

/// Map a [0.0, 1.0] sensitivity slider to Fellegi-Sunter log-likelihood
/// thresholds via piecewise linear interpolation.
///
/// Anchor points:
///  - 0.3 (Conservative): match=14.0, possible=8.0
///  - 0.5 (Balanced):     match=10.0, possible=3.0
///  - 0.7 (Moderate):     match=6.0,  possible=1.5
///  - 0.9 (Aggressive):   match=2.0,  possible=0.0
pub fn sensitivity_to_fs_thresholds(sensitivity: f64) -> (f64, f64) {
    let s = sensitivity.clamp(0.0, 1.0);

    let anchors: [(f64, f64, f64); 4] = [
        (0.3, 14.0, 8.0),
        (0.5, 10.0, 3.0),
        (0.7, 6.0, 1.5),
        (0.9, 2.0, 0.0),
    ];

    if s <= anchors[0].0 {
        return (anchors[0].1, anchors[0].2);
    }
    if s >= anchors[3].0 {
        return (anchors[3].1, anchors[3].2);
    }

    for i in 0..anchors.len() - 1 {
        let (s0, m0, p0) = anchors[i];
        let (s1, m1, p1) = anchors[i + 1];
        if s >= s0 && s <= s1 {
            let t = (s - s0) / (s1 - s0);
            let match_t = m0 + t * (m1 - m0);
            let possible_t = p0 + t * (p1 - p0);
            return (match_t, possible_t);
        }
    }

    (10.0, 3.0)
}

/// Apply a sensitivity value to an existing plan, adjusting FS thresholds.
pub fn apply_sensitivity_to_plan(plan: &mut IdentityPlan, sensitivity: f64) {
    let (match_t, possible_t) = sensitivity_to_fs_thresholds(sensitivity);
    if let Some(ref mut fs) = plan.decision.fellegi_sunter {
        fs.match_threshold = match_t;
        fs.possible_threshold = possible_t;
    }
}

// ---------------------------------------------------------------------------
// Bridge field detection (for Transitive Intelligence)
// ---------------------------------------------------------------------------

/// Detect which columns are good candidates for relationship bridge fields.
///
/// A bridge field is one that links entities across merge clusters - not a
/// primary key (uniqueness ~1.0) and not a constant (uniqueness ~0.0).
/// Fields with uniqueness ratio between 0.01 and 0.8 are selected.
///
/// Returns sorted Vec<String> for determinism. Does NOT hardcode field names;
/// discovers from data statistics.
pub fn detect_bridge_fields(profiles: &[ColumnProfile]) -> Vec<String> {
    let mut fields: Vec<String> = profiles
        .iter()
        .filter(|col| {
            let u = col.uniqueness();
            // Not PK-like (too unique) and not constant (too common)
            (0.01..=0.8).contains(&u)
                // Skip columns with very high null rates
                && col.null_rate() < 0.9
                // Only consider columns that are not timestamp/FK columns
                && !is_timestamp_column(&col.name)
                && !is_fk_column(&col.name)
        })
        .map(|col| col.name.clone())
        .collect();

    fields.sort();
    fields
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::field_profiler::signals::IdentitySignal;

    #[test]
    fn test_sensitivity_to_fs_thresholds() {
        let (m, p) = sensitivity_to_fs_thresholds(0.5);
        assert!((m - 10.0).abs() < 0.01);
        assert!((p - 3.0).abs() < 0.01);

        let (m, p) = sensitivity_to_fs_thresholds(0.3);
        assert!((m - 14.0).abs() < 0.01);
        assert!((p - 8.0).abs() < 0.01);

        let (m, p) = sensitivity_to_fs_thresholds(0.9);
        assert!((m - 2.0).abs() < 0.01);
        assert!((p - 0.0).abs() < 0.01);

        // Interpolated
        let (m, _p) = sensitivity_to_fs_thresholds(0.4);
        assert!(m > 10.0 && m < 14.0);
    }

    #[test]
    fn test_detect_bridge_fields_basic() {
        let profiles = vec![
            ColumnProfile {
                name: "company_name".into(),
                signal: Some(IdentitySignal::CompanyName),
                distinct_count: 40, null_count: 5, total_count: 100,
                sample_values: Vec::new(),
                cross_source_overlap: None,
            },
            ColumnProfile {
                name: "domain".into(),
                signal: None,
                distinct_count: 30, null_count: 10, total_count: 100,
                sample_values: Vec::new(),
                cross_source_overlap: None,
            },
            // PK-like (uniqueness = 1.0) - should be excluded
            ColumnProfile {
                name: "record_id".into(),
                signal: None,
                distinct_count: 100, null_count: 0, total_count: 100,
                sample_values: Vec::new(),
                cross_source_overlap: None,
            },
            // Constant (uniqueness = 0.0) - should be excluded
            ColumnProfile {
                name: "status".into(),
                signal: None,
                distinct_count: 0, null_count: 0, total_count: 100,
                sample_values: Vec::new(),
                cross_source_overlap: None,
            },
        ];

        let fields = detect_bridge_fields(&profiles);
        assert!(fields.contains(&"company_name".to_string()));
        assert!(fields.contains(&"domain".to_string()));
        assert!(!fields.contains(&"record_id".to_string()));
        assert!(!fields.contains(&"status".to_string()));
    }

    #[test]
    fn test_detect_bridge_fields_excludes_timestamps_and_fks() {
        let profiles = vec![
            ColumnProfile {
                name: "created_at".into(),
                signal: None,
                distinct_count: 50, null_count: 0, total_count: 100,
                sample_values: Vec::new(),
                cross_source_overlap: None,
            },
            ColumnProfile {
                name: "account_id".into(),
                signal: None,
                distinct_count: 50, null_count: 0, total_count: 100,
                sample_values: Vec::new(),
                cross_source_overlap: None,
            },
            ColumnProfile {
                name: "industry".into(),
                signal: None,
                distinct_count: 20, null_count: 5, total_count: 100,
                sample_values: Vec::new(),
                cross_source_overlap: None,
            },
        ];

        let fields = detect_bridge_fields(&profiles);
        assert!(!fields.contains(&"created_at".to_string()));
        assert!(!fields.contains(&"account_id".to_string()));
        assert!(fields.contains(&"industry".to_string()));
    }

    #[test]
    fn test_detect_bridge_fields_sorted_output() {
        let profiles = vec![
            ColumnProfile {
                name: "zebra".into(),
                signal: None,
                distinct_count: 30, null_count: 0, total_count: 100,
                sample_values: Vec::new(),
                cross_source_overlap: None,
            },
            ColumnProfile {
                name: "apple".into(),
                signal: None,
                distinct_count: 30, null_count: 0, total_count: 100,
                sample_values: Vec::new(),
                cross_source_overlap: None,
            },
        ];

        let fields = detect_bridge_fields(&profiles);
        assert_eq!(fields, vec!["apple".to_string(), "zebra".to_string()]);
    }
}
