//! HUD interaction: action-button handlers, Modes/Menu popovers, and
//! the chrome tap-to-toggle gesture.

use super::*;



/// `Changed<Interaction>` filter ensures we only react on the frame the
/// interaction state transitions, avoiding repeat events while the button
/// is held down. Each click handler fires the corresponding request event,
/// which `pause_plugin` / `help_plugin` / `game_plugin` consume alongside
/// their existing keyboard handlers.
pub(super) fn handle_new_game_button(
    interaction_query: Query<&Interaction, (With<NewGameButton>, Changed<Interaction>)>,
    mut new_game: MessageWriter<NewGameRequestEvent>,
) {
    for interaction in &interaction_query {
        if *interaction == Interaction::Pressed {
            new_game.write(NewGameRequestEvent::default());
        }
    }
}

pub(super) fn handle_undo_button(
    interaction_query: Query<&Interaction, (With<UndoButton>, Changed<Interaction>)>,
    mut undo: MessageWriter<UndoRequestEvent>,
) {
    for interaction in &interaction_query {
        if *interaction == Interaction::Pressed {
            undo.write(UndoRequestEvent);
        }
    }
}

pub(super) fn handle_pause_button(
    interaction_query: Query<&Interaction, (With<PauseButton>, Changed<Interaction>)>,
    mut pause: MessageWriter<PauseRequestEvent>,
) {
    for interaction in &interaction_query {
        if *interaction == Interaction::Pressed {
            pause.write(PauseRequestEvent);
        }
    }
}

pub(super) fn handle_help_button(
    interaction_query: Query<&Interaction, (With<HelpButton>, Changed<Interaction>)>,
    mut help: MessageWriter<HelpRequestEvent>,
) {
    for interaction in &interaction_query {
        if *interaction == Interaction::Pressed {
            help.write(HelpRequestEvent);
        }
    }
}

pub(super) fn handle_hint_button(
    interaction_query: Query<&Interaction, (With<HintButton>, Changed<Interaction>)>,
    paused: Option<Res<PausedResource>>,
    game: Option<Res<GameStateResource>>,
    solver_config: Option<Res<crate::input_plugin::HintSolverConfig>>,
    mut pending_hint: Option<ResMut<crate::pending_hint::PendingHintTask>>,
    mut info_toast: MessageWriter<InfoToastEvent>,
) {
    for interaction in &interaction_query {
        if *interaction != Interaction::Pressed {
            continue;
        }
        if paused.as_ref().is_some_and(|p| p.0) {
            return;
        }
        let Some(ref g) = game else { return };
        if g.0.is_won() {
            info_toast.write(InfoToastEvent(HINT_WON_MSG.to_string()));
            return;
        }
        if let (Some(cfg), Some(hint)) = (solver_config.as_ref(), pending_hint.as_mut()) {
            hint.spawn(g.0.clone(), cfg.moves_budget, cfg.states_budget);
        }
    }
}

/// Toggles the [`ModesPopover`]: spawns it on first click, despawns it on
/// second click. Mode rows are populated per the player's current level so
/// only unlocked options appear.
pub(super) fn handle_modes_button(
    interaction_query: Query<&Interaction, (With<ModesButton>, Changed<Interaction>)>,
    popovers: Query<Entity, With<ModesPopover>>,
    backdrops: Query<Entity, With<ModesPopoverBackdrop>>,
    progress: Option<Res<ProgressResource>>,
    daily: Option<Res<DailyChallengeResource>>,
    font_res: Option<Res<FontResource>>,
    mut commands: Commands,
) {
    let pressed = interaction_query.iter().any(|i| *i == Interaction::Pressed);
    if !pressed {
        return;
    }
    if let Ok(entity) = popovers.single() {
        commands.entity(entity).despawn();
        for e in &backdrops {
            commands.entity(e).despawn();
        }
    } else {
        spawn_modes_popover(
            &mut commands,
            progress.as_deref(),
            daily.as_deref(),
            font_res.as_deref(),
        );
    }
}

/// Spawns the modes popover anchored just below the action bar's right
/// edge. Always includes Classic; includes Daily Challenge when a daily
/// resource is loaded; includes Zen / Challenge / Time Attack once the
/// player reaches the challenge unlock level.
pub(super) fn spawn_modes_popover(
    commands: &mut Commands,
    progress: Option<&ProgressResource>,
    daily: Option<&DailyChallengeResource>,
    font_res: Option<&FontResource>,
) {
    let level = progress.map_or(0, |p| p.0.level);
    let font = TextFont {
        font: font_res.map(|f| f.0.clone()).unwrap_or_default(),
        font_size: 15.0,
        ..default()
    };

    // Each row carries a tooltip alongside its label so hover reveals
    // a one-line description of what the mode does — mirroring the
    // tooltips on the action-bar buttons that opened this popover.
    let mut rows: Vec<(ModeOption, &'static str, &'static str)> = vec![(
        ModeOption::Classic,
        "Classic",
        "Standard Klondike. Score, timer, and full progression.",
    )];
    if daily.is_some() {
        rows.push((
            ModeOption::DailyChallenge,
            "Daily Challenge",
            "Today's seeded deal. Same for every player worldwide.",
        ));
    }
    if level >= CHALLENGE_UNLOCK_LEVEL {
        rows.push((
            ModeOption::Zen,
            "Zen",
            "No timer, no score, no penalties. Just play.",
        ));
        rows.push((
            ModeOption::Challenge,
            "Challenge",
            "Hand-picked hard seeds. No undo allowed.",
        ));
        rows.push((
            ModeOption::TimeAttack,
            "Time Attack",
            "Win as many games as you can in ten minutes.",
        ));
    }

    // Popover opens upward from just above the bottom action bar.
    // Use a platform-aware offset that clears the bar height + safe-area
    // gesture zone on Android, and the flat bar height on desktop.
    let popover_bottom = Val::Px(ACTION_POPOVER_BOTTOM_PX);

    commands
        .spawn((
            ModesPopover,
            HudPopoverOpen,
            Node {
                position_type: PositionType::Absolute,
                right: VAL_SPACE_3,
                bottom: popover_bottom,
                flex_direction: FlexDirection::Column,
                row_gap: VAL_SPACE_1,
                padding: UiRect::all(VAL_SPACE_2),
                border_radius: BorderRadius::all(Val::Px(RADIUS_MD)),
                ..default()
            },
            BackgroundColor(BG_ELEVATED),
            ZIndex(Z_HUD_POPOVER),
        ))
        .with_children(|panel| {
            for (option, label, tooltip) in rows {
                panel
                    .spawn((
                        option,
                        ActionButton,
                        PopoverRow,
                        Button,
                        Tooltip::new(tooltip),
                        Node {
                            padding: UiRect::axes(VAL_SPACE_3, Val::Px(6.0)),
                            justify_content: JustifyContent::FlexStart,
                            align_items: AlignItems::Center,
                            min_width: Val::Px(150.0),
                            border_radius: BorderRadius::all(Val::Px(RADIUS_SM)),
                            ..default()
                        },
                        BackgroundColor(ACTION_BTN_IDLE),
                    ))
                    .with_children(|b| {
                        b.spawn((Text::new(label), font.clone(), TextColor(TEXT_PRIMARY)));
                    });
            }
        });

    // Fullscreen transparent backdrop at Z_HUD_POPOVER_BACKDROP (below the
    // popover at Z_HUD_POPOVER) so tapping outside light-dismisses it.
    commands.spawn((
        ModesPopoverBackdrop,
        Button,
        Node {
            position_type: PositionType::Absolute,
            left: Val::Px(0.0),
            top: Val::Px(0.0),
            width: Val::Percent(100.0),
            height: Val::Percent(100.0),
            ..default()
        },
        BackgroundColor(Color::NONE),
        ZIndex(Z_HUD_POPOVER_BACKDROP),
    ));
}

/// Dispatches the click on a popover row to the matching request event,
/// then despawns the popover.
///
/// Classic uses [`NewGameRequestEvent`] directly; the other modes use
/// their `Start*RequestEvent` so the existing keyboard handler runs
/// (level gates, `TimeAttackResource` setup, daily seed lookup, etc.) —
/// the popover stays a thin entry point and never duplicates that logic.
#[allow(clippy::too_many_arguments)]
pub(super) fn handle_mode_option_click(
    interaction_query: Query<(&Interaction, &ModeOption), Changed<Interaction>>,
    popovers: Query<Entity, With<ModesPopover>>,
    backdrops: Query<Entity, With<ModesPopoverBackdrop>>,
    mut new_game: MessageWriter<NewGameRequestEvent>,
    mut zen: MessageWriter<StartZenRequestEvent>,
    mut challenge: MessageWriter<StartChallengeRequestEvent>,
    mut time_attack: MessageWriter<StartTimeAttackRequestEvent>,
    mut daily: MessageWriter<StartDailyChallengeRequestEvent>,
    mut commands: Commands,
) {
    let mut clicked_any = false;
    for (interaction, option) in &interaction_query {
        if *interaction != Interaction::Pressed {
            continue;
        }
        clicked_any = true;
        match option {
            ModeOption::Classic => {
                new_game.write(NewGameRequestEvent::default());
            }
            ModeOption::DailyChallenge => {
                daily.write(StartDailyChallengeRequestEvent);
            }
            ModeOption::Zen => {
                zen.write(StartZenRequestEvent);
            }
            ModeOption::Challenge => {
                challenge.write(StartChallengeRequestEvent);
            }
            ModeOption::TimeAttack => {
                time_attack.write(StartTimeAttackRequestEvent);
            }
        }
    }
    if clicked_any && let Ok(entity) = popovers.single() {
        commands.entity(entity).despawn();
        for e in &backdrops {
            commands.entity(e).despawn();
        }
    }
}

/// Toggles the [`MenuPopover`]: spawns it on first click, despawns it on
/// second click. The popover lists the five overlays previously only
/// reachable via the S / A / P / O / L hotkeys.
pub(super) fn handle_menu_button(
    interaction_query: Query<&Interaction, (With<MenuButton>, Changed<Interaction>)>,
    popovers: Query<Entity, With<MenuPopover>>,
    backdrops: Query<Entity, With<MenuPopoverBackdrop>>,
    scrims: Query<(), With<ModalScrim>>,
    font_res: Option<Res<FontResource>>,
    mut commands: Commands,
) {
    let pressed = interaction_query.iter().any(|i| *i == Interaction::Pressed);
    if !pressed {
        return;
    }
    if let Ok(entity) = popovers.single() {
        commands.entity(entity).despawn();
        for e in &backdrops {
            commands.entity(e).despawn();
        }
    } else if scrims.is_empty() {
        spawn_menu_popover(&mut commands, font_res.as_deref());
    }
}

/// Spawns the menu popover anchored just below the action bar, with one
/// row per overlay. Each row dispatches its corresponding
/// `Toggle*RequestEvent` so the existing toggle handler runs (and the
/// HUD never duplicates spawn / despawn / fetch logic).
pub(super) fn spawn_menu_popover(commands: &mut Commands, font_res: Option<&FontResource>) {
    let font = TextFont {
        font: font_res.map(|f| f.0.clone()).unwrap_or_default(),
        font_size: 15.0,
        ..default()
    };

    // Each row carries a tooltip alongside its label so hover reveals
    // a one-line description of what each overlay shows — mirroring
    // the tooltips on the action-bar buttons that opened this popover.
    let rows: [(MenuOption, &'static str, &'static str); 7] = [
        (
            MenuOption::Help,
            "Help",
            "Show controls, rules, and keyboard shortcuts.",
        ),
        (
            MenuOption::Modes,
            "Game Modes",
            "Switch modes: Classic, Daily, Zen, Challenge, Time Attack.",
        ),
        (
            MenuOption::Stats,
            "Stats",
            "Lifetime totals: wins, streaks, fastest time, best score.",
        ),
        (
            MenuOption::Achievements,
            "Achievements",
            "Browse unlocked achievements and the rewards still ahead.",
        ),
        (
            MenuOption::Profile,
            "Profile",
            "Your level, XP progress, and sync status.",
        ),
        (
            MenuOption::Settings,
            "Settings",
            "Audio, animations, theme, draw mode, and sync.",
        ),
        (
            MenuOption::Leaderboard,
            "Leaderboard",
            "Top players from your sync server. Opt in from Profile.",
        ),
    ];

    // Same upward-opening placement as ModesPopover.
    let popover_bottom = Val::Px(ACTION_POPOVER_BOTTOM_PX);

    commands
        .spawn((
            MenuPopover,
            HudPopoverOpen,
            Node {
                position_type: PositionType::Absolute,
                right: VAL_SPACE_3,
                bottom: popover_bottom,
                flex_direction: FlexDirection::Column,
                row_gap: VAL_SPACE_1,
                padding: UiRect::all(VAL_SPACE_2),
                border_radius: BorderRadius::all(Val::Px(RADIUS_MD)),
                ..default()
            },
            BackgroundColor(BG_ELEVATED),
            ZIndex(Z_HUD_POPOVER),
        ))
        .with_children(|panel| {
            for (option, label, tooltip) in rows {
                panel
                    .spawn((
                        option,
                        ActionButton,
                        PopoverRow,
                        Button,
                        Tooltip::new(tooltip),
                        Node {
                            padding: UiRect::axes(VAL_SPACE_3, Val::Px(6.0)),
                            justify_content: JustifyContent::FlexStart,
                            align_items: AlignItems::Center,
                            min_width: Val::Px(150.0),
                            border_radius: BorderRadius::all(Val::Px(RADIUS_SM)),
                            ..default()
                        },
                        BackgroundColor(ACTION_BTN_IDLE),
                    ))
                    .with_children(|b| {
                        b.spawn((Text::new(label), font.clone(), TextColor(TEXT_PRIMARY)));
                    });
            }
        });

    // Transparent fullscreen backdrop behind the popover — tapping anywhere
    // outside the panel light-dismisses it via handle_menu_backdrop_click.
    commands.spawn((
        MenuPopoverBackdrop,
        Button,
        Node {
            position_type: PositionType::Absolute,
            left: Val::Px(0.0),
            top: Val::Px(0.0),
            width: Val::Percent(100.0),
            height: Val::Percent(100.0),
            ..default()
        },
        BackgroundColor(Color::NONE),
        ZIndex(Z_HUD_POPOVER_BACKDROP),
    ));
}

/// Dispatches the click on a menu row to the matching toggle event,
/// then despawns the popover.
#[allow(clippy::too_many_arguments)]
pub(super) fn handle_menu_option_click(
    interaction_query: Query<(&Interaction, &MenuOption), Changed<Interaction>>,
    popovers: Query<Entity, With<MenuPopover>>,
    backdrops: Query<Entity, With<MenuPopoverBackdrop>>,
    mut stats: MessageWriter<ToggleStatsRequestEvent>,
    mut achievements: MessageWriter<ToggleAchievementsRequestEvent>,
    mut profile: MessageWriter<ToggleProfileRequestEvent>,
    mut settings: MessageWriter<ToggleSettingsRequestEvent>,
    mut leaderboard: MessageWriter<ToggleLeaderboardRequestEvent>,
    mut help: MessageWriter<HelpRequestEvent>,
    progress: Option<Res<ProgressResource>>,
    daily: Option<Res<DailyChallengeResource>>,
    font_res: Option<Res<FontResource>>,
    mut commands: Commands,
) {
    let mut clicked_any = false;
    let mut open_modes = false;
    for (interaction, option) in &interaction_query {
        if *interaction != Interaction::Pressed {
            continue;
        }
        clicked_any = true;
        match option {
            MenuOption::Help => {
                help.write(HelpRequestEvent);
            }
            MenuOption::Modes => {
                open_modes = true;
            }
            MenuOption::Stats => {
                stats.write(ToggleStatsRequestEvent);
            }
            MenuOption::Achievements => {
                achievements.write(ToggleAchievementsRequestEvent);
            }
            MenuOption::Profile => {
                profile.write(ToggleProfileRequestEvent);
            }
            MenuOption::Settings => {
                settings.write(ToggleSettingsRequestEvent);
            }
            MenuOption::Leaderboard => {
                leaderboard.write(ToggleLeaderboardRequestEvent);
            }
        }
    }
    if clicked_any && let Ok(entity) = popovers.single() {
        commands.entity(entity).despawn();
        for e in &backdrops {
            commands.entity(e).despawn();
        }
    }
    if open_modes {
        spawn_modes_popover(
            &mut commands,
            progress.as_deref(),
            daily.as_deref(),
            font_res.as_deref(),
        );
    }
}

/// Despawns the [`ModesPopover`] and its backdrop when Escape / Android back
/// is pressed while the popover is open. Runs so `PausePlugin`'s guard (which
/// checks [`HudPopoverOpen`]) sees an empty world and stays idle.
pub(super) fn close_modes_popover_on_escape(
    keys: Res<ButtonInput<KeyCode>>,
    popovers: Query<Entity, With<ModesPopover>>,
    backdrops: Query<Entity, With<ModesPopoverBackdrop>>,
    mut commands: Commands,
) {
    if !keys.just_pressed(KeyCode::Escape) || popovers.is_empty() {
        return;
    }
    for e in popovers.iter().chain(backdrops.iter()) {
        commands.entity(e).despawn();
    }
}

/// Despawns the [`MenuPopover`] and its backdrop when Escape / Android back
/// is pressed while the popover is open.
pub(super) fn close_menu_popover_on_escape(
    keys: Res<ButtonInput<KeyCode>>,
    popovers: Query<Entity, With<MenuPopover>>,
    backdrops: Query<Entity, With<MenuPopoverBackdrop>>,
    mut commands: Commands,
) {
    if !keys.just_pressed(KeyCode::Escape) || popovers.is_empty() {
        return;
    }
    for e in popovers.iter().chain(backdrops.iter()) {
        commands.entity(e).despawn();
    }
}

/// Despawns the [`ModesPopover`] and its backdrop when the player taps
/// anywhere outside the panel.
pub(super) fn handle_modes_backdrop_click(
    interaction_query: Query<&Interaction, (With<ModesPopoverBackdrop>, Changed<Interaction>)>,
    popovers: Query<Entity, With<ModesPopover>>,
    backdrops: Query<Entity, With<ModesPopoverBackdrop>>,
    mut commands: Commands,
) {
    let pressed = interaction_query.iter().any(|i| *i == Interaction::Pressed);
    if !pressed {
        return;
    }
    for e in popovers.iter().chain(backdrops.iter()) {
        commands.entity(e).despawn();
    }
}

/// Despawns the [`MenuPopover`] and its backdrop when the player taps
/// anywhere outside the panel (i.e. the transparent backdrop is pressed).
pub(super) fn handle_menu_backdrop_click(
    interaction_query: Query<&Interaction, (With<MenuPopoverBackdrop>, Changed<Interaction>)>,
    popovers: Query<Entity, With<MenuPopover>>,
    backdrops: Query<Entity, With<MenuPopoverBackdrop>>,
    mut commands: Commands,
) {
    let pressed = interaction_query.iter().any(|i| *i == Interaction::Pressed);
    if !pressed {
        return;
    }
    for e in popovers.iter().chain(backdrops.iter()) {
        commands.entity(e).despawn();
    }
}

#[cfg(target_os = "android")]
#[allow(clippy::too_many_arguments)]
pub(super) fn toggle_hud_on_tap(
    mut touch_events: MessageReader<TouchInput>,
    drag: Res<DragState>,
    scrims: Query<(), With<ModalScrim>>,
    paused: Option<Res<PausedResource>>,
    mut tracker: ResMut<HudTapTracker>,
    mut hud_vis: ResMut<HudVisibility>,
    buttons: Query<&Interaction, With<ActionButton>>,
    mut game_consumed: ResMut<GameInputConsumedResource>,
) {
    use bevy::input::touch::TouchPhase;
    if !scrims.is_empty() || paused.is_some_and(|p| p.0) {
        // Drain buffered events so they don't replay in the frame after
        // the scrim despawns, which would trigger a spurious visibility
        // toggle as the resume/close button tap's Started+Ended pair
        // replays in the now-scrim-free frame.
        for _ in touch_events.read() {}
        tracker.start_pos = None;
        tracker.started_on_button = false;
        game_consumed.0 = false;
        return;
    }
    for event in touch_events.read() {
        match event.phase {
            TouchPhase::Started => {
                tracker.start_pos = Some(event.position);
                // Record whether the finger-down landed on a button so
                // the finger-up doesn't double-fire (toggle bar + press
                // button at the same time).
                tracker.started_on_button = buttons.iter().any(|i| *i != Interaction::None);
            }
            TouchPhase::Ended if drag.is_idle() => {
                // Also treat taps where game logic consumed the touch (e.g.
                // drawing from stock) as "on button" so they don't toggle
                // the HUD. The flag is set on TouchPhase::Started by the
                // input system that consumed the tap and must be cleared here
                // regardless of whether we toggle.
                let on_button = tracker.started_on_button || game_consumed.0;
                game_consumed.0 = false;
                if let Some(start) = tracker.start_pos.take()
                    && !on_button
                    && (event.position - start).length() < HUD_TAP_SLOP_PX
                {
                    *hud_vis = match *hud_vis {
                        HudVisibility::Visible => HudVisibility::Hidden,
                        HudVisibility::Hidden => HudVisibility::Visible,
                    };
                }
                tracker.started_on_button = false;
            }
            // Moved: don't clear start_pos — Android fires Moved for normal
            // tap jitter, and the distance check at Ended already rejects
            // real drags. Clearing here would silently swallow tap toggles.
            TouchPhase::Canceled => {
                tracker.start_pos = None;
                tracker.started_on_button = false;
                game_consumed.0 = false;
            }
            _ => {}
        }
    }
}

