//! Selector for the runner's per-version fan-out.
//!
//! v1 of the `target_versions:` work parsed the list and validated
//! every entry round-trips through [`crate::protocol::ProtocolVersion::from_wire`].
//! v2 lands the runtime piece: when the suite declares a non-empty
//! list, the runner executes the suite once per version in
//! declaration order, and the optional `mcptest run --target-version
//! <V>` CLI flag selects a subset.
//!
//! This module owns the pure selection rule: given the suite's
//! declared versions and an optional CLI override, return the
//! effective list the runner should walk. The selector is pure and
//! free of I/O so the unit tests drive every branch in isolation;
//! the run-loop integration that calls it lives in the CLI's
//! `run_live` handler.

use crate::protocol::ProtocolVersion;

/// Reasons the selector refuses to produce a list.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SelectionError {
    /// The CLI override pointed at a version the suite did not
    /// declare. Surface the user-facing message so the runner can
    /// print the suite's declared versions and the override that
    /// missed.
    UnknownOverride {
        /// Raw value the user wrote on the CLI.
        override_value: String,
        /// The suite's declared list, so the error can suggest the
        /// closest match without re-walking the protocol crate.
        declared: Vec<String>,
    },
    /// The CLI override was a string that does not round-trip
    /// through `ProtocolVersion::from_wire`. The runner is strict
    /// about the wire shape so a typo cannot silently select the
    /// default-protocol fallback.
    BadOverride {
        /// The offending value.
        override_value: String,
    },
}

/// Resolve the effective list of protocol versions the runner walks.
///
/// `suite_versions` is the parsed `target_versions:` block; an empty
/// vec keeps the legacy single-run behavior. `cli_override` is the
/// optional `--target-version <V>` value. When the override is
/// present, the selector returns exactly `[that_version]` (after
/// validating it parses and was declared); when absent, the suite
/// list is returned verbatim.
pub fn effective(
    suite_versions: &[ProtocolVersion],
    cli_override: Option<&str>,
) -> Result<Vec<ProtocolVersion>, SelectionError> {
    let Some(raw) = cli_override else {
        return Ok(suite_versions.to_vec());
    };
    let parsed = ProtocolVersion::from_wire(raw).ok_or_else(|| SelectionError::BadOverride {
        override_value: raw.to_string(),
    })?;
    if !suite_versions.is_empty() && !suite_versions.contains(&parsed) {
        return Err(SelectionError::UnknownOverride {
            override_value: raw.to_string(),
            declared: suite_versions
                .iter()
                .map(|v| v.as_wire().to_string())
                .collect(),
        });
    }
    Ok(vec![parsed])
}

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

    #[test]
    fn empty_suite_and_no_override_yields_an_empty_list() {
        let out = effective(&[], None).unwrap();
        assert!(out.is_empty());
    }

    #[test]
    fn suite_list_without_override_passes_through_in_order() {
        let suite = vec![ProtocolVersion::V2026_07_28, ProtocolVersion::V2025_06_18];
        let out = effective(&suite, None).unwrap();
        assert_eq!(out, suite);
    }

    #[test]
    fn override_selects_a_single_declared_version() {
        let suite = vec![ProtocolVersion::V2026_07_28, ProtocolVersion::V2025_06_18];
        let out = effective(&suite, Some("2025-06-18")).unwrap();
        assert_eq!(out, vec![ProtocolVersion::V2025_06_18]);
    }

    #[test]
    fn override_missing_from_the_suite_list_is_rejected() {
        let suite = vec![ProtocolVersion::V2025_06_18];
        let err = effective(&suite, Some("2026-07-28")).unwrap_err();
        match err {
            SelectionError::UnknownOverride {
                override_value,
                declared,
            } => {
                assert_eq!(override_value, "2026-07-28");
                assert_eq!(declared, vec!["2025-06-18".to_string()]);
            }
            other => panic!("expected UnknownOverride, got {other:?}"),
        }
    }

    #[test]
    fn override_that_does_not_parse_is_rejected() {
        let err = effective(&[], Some("2026-07-29")).unwrap_err();
        assert!(matches!(err, SelectionError::BadOverride { .. }));
    }

    #[test]
    fn override_against_an_empty_suite_returns_just_that_version() {
        // When the suite did not declare target_versions, an override
        // is still legal: the runner walks just the user-named one.
        // This mirrors the rule the `mcptest doctor --target-version`
        // flag already follows.
        let out = effective(&[], Some("2026-07-28")).unwrap();
        assert_eq!(out, vec![ProtocolVersion::V2026_07_28]);
    }
}
