//! Schedule hygiene checks (issue #143).
//!
//! Bevy runs systems with conflicting data access in nondeterministic order
//! unless an ordering edge exists. Nothing enforced this historically, and
//! the headless gameplay cluster carries a large legacy backlog of such
//! pairs — most benign (event/resource writers that never observably race),
//! but unproven. Rather than a permanently red hard-error test or no test
//! at all, this is a RATCHET: the count may only go down. New conflicting
//! pairs fail CI immediately; the backlog can be triaged incrementally
//! (add `.before`/`.after` where order matters, `.ambiguous_with` where it
//! provably doesn't, then lower `AMBIGUITY_BASELINE`).
//!
//! To see the offending pair names, add the `bevy_debug` / debug feature to
//! the bevy dev-dependency (system names are stripped otherwise) and set
//! `ambiguity_detection: LogLevel::Error` manually.
//!
//! Scope note: `CoreGamePlugin` (the full composition) performs real
//! storage/platform I/O in `build` and cannot run in a unit test; the
//! cluster below is the same one the engine's headless behaviour tests use.

#[cfg(test)]
mod tests {
    use bevy::ecs::schedule::{LogLevel, ScheduleBuildSettings};
    use bevy::prelude::*;

    use crate::auto_complete_plugin::AutoCompletePlugin;
    use crate::card_plugin::CardPlugin;
    use crate::game_plugin::GamePlugin;
    use crate::hud_plugin::HudPlugin;
    use crate::settings_plugin::SettingsPlugin;
    use crate::table_plugin::TablePlugin;
    use crate::ui_focus::UiFocusPlugin;
    use crate::ui_modal::UiModalPlugin;

    /// The backlog (302 pairs on 2026-07-06) was burned down to ZERO the
    /// same day (#143, PRs #146–#149) — this is now a hard gate. If your
    /// change trips this assertion you have added a pair of systems with
    /// conflicting data access and no ordering edge: add `.before`/`.after`
    /// where order matters, or `.ambiguous_with` the relevant domain set
    /// (BoardVisuals, MarkerVisuals, UiTextFx, HudButtons, writer sets)
    /// where it provably does not. Do not raise this constant.
    const AMBIGUITY_BASELINE: usize = 0;

    fn cluster_app() -> App {
        let mut app = App::new();
        app.add_plugins(MinimalPlugins)
            .add_plugins(GamePlugin)
            .add_plugins(TablePlugin)
            .add_plugins(CardPlugin)
            .add_plugins(HudPlugin)
            .add_plugins(AutoCompletePlugin)
            .add_plugins(UiModalPlugin)
            .add_plugins(UiFocusPlugin)
            .add_plugins(SettingsPlugin::headless());
        app.init_resource::<ButtonInput<KeyCode>>();
        app
    }

    #[test]
    fn update_schedule_ambiguities_do_not_grow() {
        let mut app = cluster_app();
        app.edit_schedule(Update, |s| {
            s.set_build_settings(ScheduleBuildSettings {
                ambiguity_detection: LogLevel::Error,
                ..Default::default()
            });
        });

        // With LogLevel::Error the first schedule build panics when any
        // ambiguity exists, and the panic message begins with the pair
        // count. Catch it and ratchet on that count. (Parsing a panic
        // message is brittle by design — if a Bevy upgrade rewords it,
        // this test fails loudly and the parse below needs one-line
        // maintenance, which is preferable to losing the ratchet.)
        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            app.update();
        }));

        let count = match result {
            Ok(()) => 0,
            Err(payload) => {
                let msg = payload
                    .downcast_ref::<String>()
                    .cloned()
                    .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
                    .unwrap_or_default();
                let parsed = msg
                    .split(" pairs of systems")
                    .next()
                    .and_then(|prefix| prefix.split_whitespace().last())
                    .and_then(|n| n.parse::<usize>().ok());
                parsed.unwrap_or_else(|| {
                    panic!(
                        "ambiguity panic message no longer parseable (Bevy upgrade?): {msg}"
                    )
                })
            }
        };

        assert_eq!(
            count, AMBIGUITY_BASELINE,
            "system-order ambiguities changed from the enforced baseline. \
             Add .before/.after or .ambiguous_with at the new registration site \
             (or, if the count legitimately dropped below a nonzero baseline, \
             lower AMBIGUITY_BASELINE).",
        );
    }
}
