use crate::domain::Finding;

pub(crate) fn sort_findings(findings: &mut [Finding]) {
    findings.sort_by(|a, b| {
        a.probe
            .location
            .file
            .cmp(&b.probe.location.file)
            .then(a.probe.location.line.cmp(&b.probe.location.line))
            .then(a.probe.family.as_str().cmp(b.probe.family.as_str()))
    });
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::domain::{
        ActivationEvidence, Confidence, DeltaKind, ExposureClass, Probe, ProbeFamily, ProbeId,
        RevealEvidence, RiprEvidence, SourceLocation, StageEvidence, StageState,
    };

    fn stage() -> StageEvidence {
        StageEvidence::new(StageState::Unknown, Confidence::Unknown, "sort test")
    }

    fn finding(id: &str, file: &str, line: usize, family: ProbeFamily) -> Finding {
        Finding {
            id: id.to_string(),
            probe: Probe {
                id: ProbeId(format!("probe-{id}")),
                location: SourceLocation::new(file, line, 1),
                owner: None,
                family,
                delta: DeltaKind::Control,
                before: None,
                after: None,
                expression: "value > threshold".to_string(),
                expected_sinks: Vec::new(),
                required_oracles: Vec::new(),
            },
            class: ExposureClass::WeaklyExposed,
            ripr: RiprEvidence {
                reach: stage(),
                infect: stage(),
                propagate: stage(),
                reveal: RevealEvidence {
                    observe: stage(),
                    discriminate: stage(),
                },
            },
            confidence: 0.0,
            evidence: Vec::new(),
            missing: Vec::new(),
            flow_sinks: Vec::new(),
            activation: ActivationEvidence::default(),
            stop_reasons: Vec::new(),
            related_tests: Vec::new(),
            recommended_next_step: None,
            language: None,
            language_status: None,
            owner_kind: None,
            static_limit_kind: None,
        }
    }

    #[test]
    fn sort_findings_orders_by_file_line_then_probe_family() {
        let mut findings = vec![
            finding(
                "same-line-predicate",
                "src/b.rs",
                10,
                ProbeFamily::Predicate,
            ),
            finding("later-line", "src/a.rs", 20, ProbeFamily::Predicate),
            finding("same-line-error", "src/b.rs", 10, ProbeFamily::ErrorPath),
            finding("earlier-file", "src/a.rs", 5, ProbeFamily::SideEffect),
        ];

        sort_findings(&mut findings);

        let ids: Vec<&str> = findings.iter().map(|finding| finding.id.as_str()).collect();
        assert_eq!(
            ids,
            vec![
                "earlier-file",
                "later-line",
                "same-line-error",
                "same-line-predicate",
            ]
        );
    }
}
