//! Model-level tests for the switcher selection and lifecycle.

use super::*;
    use super::*;
    use smithay::utils::{Point, Rectangle, Size};

    fn entry(id: u32, app: &str, title: &str) -> WindowEntry {
        WindowEntry {
            output: None,
            surface_id: id,
            object_id: None,
            app_id: app.into(),
            title: title.into(),
            thumbnail: None,
            thumb_size: None,
        }
    }

    #[test]
    fn begin_selects_next_window() {
        let mut s = AppSwitcher::new();
        s.begin(
            vec![entry(1, "a", ""), entry(2, "b", ""), entry(3, "c", "")],
            1,
            None,
        );
        assert!(s.visible);
        assert_eq!(s.selected, 1);
    }

    #[test]
    fn shift_tab_selects_previous_window() {
        let mut s = AppSwitcher::new();
        s.begin(
            vec![entry(1, "a", ""), entry(2, "b", ""), entry(3, "c", "")],
            -1,
            None,
        );
        assert!(s.visible);
        assert_eq!(s.selected, 2); // -1 mod 3 == 2
    }

    #[test]
    fn cycle_wraps_around() {
        let mut s = AppSwitcher::new();
        s.begin(vec![entry(1, "a", ""), entry(2, "b", "")], 1, None);
        s.step(1); // 0
        s.step(1); // 1
        assert_eq!(s.selected, 1);
        s.step(-1); // 0
        s.step(-1); // 1
        assert_eq!(s.selected, 1);
    }

    #[test]
    fn empty_does_not_show() {
        let mut s = AppSwitcher::new();
        s.begin(vec![], 1, None);
        assert!(!s.visible);
    }

    #[test]
    fn commit_selects_entry_and_hides() {
        let mut s = AppSwitcher::new();
        s.begin(vec![entry(10, "a", ""), entry(20, "b", "")], 1, None);
        // selected = 1 → entry 20
        assert_eq!(s.entries[s.selected].surface_id, 20);
        // commit_object_id() is the production commit path; it starts the
        // fade-out so the switcher stops taking input immediately
        // (is_active false) even though `visible` stays true until the
        // animation finishes.
        let _ = s.commit_object_id();
        assert!(!s.is_active());
        // Driving the fade-out to completion tears the state down.
        s.finish_hide();
        assert!(!s.visible);
    }

    #[test]
    fn cancel_clears_state() {
        let mut s = AppSwitcher::new();
        s.begin(vec![entry(1, "a", ""), entry(2, "b", "")], 0, None);
        assert!(s.is_active());
        s.cancel();
        // cancel() starts the fade-out; no longer active, fade in progress.
        assert!(!s.is_active());
        // Completing the fade-out clears the entries.
        s.finish_hide();
        assert!(!s.visible);
        // With the entries cleared, committing yields nothing.
        assert_eq!(s.commit_object_id(), None);
    }

    #[test]
    fn remove_at_adjusts_selection() {
        let mut s = AppSwitcher::new();
        s.begin(
            vec![entry(1, "a", ""), entry(2, "b", ""), entry(3, "c", "")],
            0,
            None,
        );
        // step(2) explicitly to land on index 2
        s.step(2);
        assert_eq!(s.selected, 2);
        s.remove_at(2);
        assert_eq!(s.entries.len(), 2);
        assert_eq!(s.selected, 1); // clamped
    }

    #[test]
    fn remove_last_cancels_switcher() {
        let mut s = AppSwitcher::new();
        s.begin(vec![entry(1, "a", "")], 0, None);
        assert!(s.is_active());
        s.remove_at(0);
        assert!(!s.is_active());
    }

    #[test]
    fn click_at_returns_dismiss_when_outside() {
        let s = AppSwitcher::new();
        // Switcher not visible
        let click = s.click_at(Point::from((0.0, 0.0)));
        assert_eq!(click, AppSwitcherClick::Dismiss);
    }

    #[test]
    fn click_at_close_takes_precedence_over_tile() {
        let mut s = AppSwitcher::new();
        s.begin(vec![entry(1, "a", ""), entry(2, "b", "")], 0, None);
        // Manually populate hit rects (render() would normally do this).
        s.tile_rects
            .push(Rectangle::new(Point::from((0, 0)), Size::from((100, 100))));
        s.close_rects
            .push(Rectangle::new(Point::from((80, 0)), Size::from((20, 20))));
        // Click in the overlap (close button area)
        assert_eq!(
            s.click_at(Point::from((85.0, 10.0))),
            AppSwitcherClick::Close(0)
        );
        // Click in tile but not close
        assert_eq!(
            s.click_at(Point::from((10.0, 50.0))),
            AppSwitcherClick::Activate(0)
        );
        // Clear the open-grace window: right after begin(), clicks outside
        // the tiles are swallowed (NoOp) so the panel isn't dismissed by the
        // same gesture that opened it. Past the grace, an outside click
        // dismisses.
        s.opened_at = None;
        // Click outside everything
        assert_eq!(
            s.click_at(Point::from((500.0, 500.0))),
            AppSwitcherClick::Dismiss
        );
    }

