//! Toggleable on-screen help / cheat sheet showing keyboard bindings.
//!
//! Reachable from the HUD "Help" button (per the UI-first principle); `F1`
//! is an optional accelerator. Listed shortcuts are grouped by intent —
//! gameplay, modes, and overlays.

use bevy::input::mouse::{MouseScrollUnit, MouseWheel};
use bevy::prelude::*;

use crate::events::HelpRequestEvent;
use crate::font_plugin::FontResource;
#[cfg(target_os = "android")]
use crate::hud_plugin::ANDROID_HINT_LABEL;
use crate::platform::SHOW_KEYBOARD_ACCELERATORS;
use crate::ui_modal::{
    ButtonVariant, ModalScrim, ScrimDismissible, spawn_modal, spawn_modal_actions,
    spawn_modal_button, spawn_modal_header,
};
use crate::ui_theme::{
    BORDER_SUBTLE, HighContrastBorder, RADIUS_SM, SPACE_2, TEXT_PRIMARY, TEXT_SECONDARY, TYPE_BODY,
    TYPE_CAPTION, VAL_SPACE_1, VAL_SPACE_2, VAL_SPACE_3, Z_MODAL_PANEL,
};

/// Marker on the help overlay root node.
#[derive(Component, Debug)]
pub struct HelpScreen;

/// Marker on the "Done" button inside the Help modal.
#[derive(Component, Debug)]
pub struct HelpCloseButton;

/// Marker on the scrollable body Node inside the Help modal.
///
/// The controls reference is six sections totalling ~28 rows, which
/// overflows the modal on the 800x600 minimum window. This marker tags
/// the inner container that carries `Overflow::scroll_y()` plus a
/// `max_height` constraint so every row stays reachable. Mirrors the
/// `SettingsPanelScrollable` pattern.
#[derive(Component, Debug)]
pub struct HelpScrollable;

/// Spawns and despawns the help / controls overlay shown when the player
/// clicks the "Help" HUD button or presses `F1`. All hotkeys and gesture
/// guides live here.
pub struct HelpPlugin;

impl Plugin for HelpPlugin {
    fn build(&self, app: &mut App) {
        app.add_message::<HelpRequestEvent>()
            // `MouseWheel` and `TouchInput` are emitted by Bevy's input
            // plugin under `DefaultPlugins`; register them explicitly so
            // scroll systems run cleanly under `MinimalPlugins` in tests.
            .add_message::<MouseWheel>()
            .add_message::<TouchInput>()
            .add_systems(
                Update,
                (
                    toggle_help_screen,
                    handle_help_close_button,
                    scroll_help_panel,
                    crate::ui_modal::touch_scroll_panel::<HelpScrollable>,
                ),
            );
    }
}

fn toggle_help_screen(
    mut commands: Commands,
    keys: Res<ButtonInput<KeyCode>>,
    mut requests: MessageReader<HelpRequestEvent>,
    screens: Query<Entity, With<HelpScreen>>,
    other_modal_scrims: Query<(), (With<ModalScrim>, Without<HelpScreen>)>,
    font_res: Option<Res<FontResource>>,
) {
    // Either F1 or a click on the HUD "Help" button (which fires
    // HelpRequestEvent) toggles the overlay.
    let button_clicked = requests.read().count() > 0;
    if !keys.just_pressed(KeyCode::F1) && !button_clicked {
        return;
    }
    if let Ok(entity) = screens.single() {
        commands.entity(entity).despawn();
    } else if other_modal_scrims.is_empty() {
        spawn_help_screen(&mut commands, font_res.as_deref());
    }
}

/// Click handler for the modal's "Done" button. F1 toggles the overlay
/// the same way; this just exposes the close action to mouse / touch.
fn handle_help_close_button(
    mut commands: Commands,
    close_buttons: Query<&Interaction, (With<HelpCloseButton>, Changed<Interaction>)>,
    screens: Query<Entity, With<HelpScreen>>,
) {
    if !close_buttons.iter().any(|i| *i == Interaction::Pressed) {
        return;
    }
    for entity in &screens {
        commands.entity(entity).despawn();
    }
}

/// Routes mouse-wheel events into the Help modal's scrollable body while
/// the panel is open. No-op when no `HelpScrollable` exists in the world
/// (modal closed). Mirrors `scroll_settings_panel`.
fn scroll_help_panel(
    mut scroll_evr: MessageReader<MouseWheel>,
    mut scrollables: Query<&mut ScrollPosition, With<HelpScrollable>>,
) {
    if scrollables.is_empty() {
        scroll_evr.clear();
        return;
    }
    let delta_y: f32 = scroll_evr
        .read()
        .map(|ev| match ev.unit {
            MouseScrollUnit::Line => ev.y * 50.0,
            MouseScrollUnit::Pixel => ev.y,
        })
        .sum();
    if delta_y == 0.0 {
        return;
    }
    for mut sp in scrollables.iter_mut() {
        sp.0.y = (sp.0.y - delta_y).max(0.0);
    }
}

/// Each entry in the controls reference table.
#[allow(dead_code)]
struct ControlRow {
    keys: &'static str,
    description: &'static str,
}

/// Each section of the controls reference. Sections render with a
/// section title and a vertically stacked list of `ControlRow`s.
struct ControlSection {
    title: &'static str,
    rows: &'static [ControlRow],
}

#[cfg(target_os = "android")]
const CONTROL_SECTIONS: &[ControlSection] = &[
    ControlSection {
        title: "Touch",
        rows: &[
            ControlRow {
                keys: "Tap stock",
                description: "Draw from stock",
            },
            ControlRow {
                keys: "Drag card",
                description: "Move cards between piles",
            },
            ControlRow {
                keys: "Tap foundation area",
                description: "Auto-move top card to foundation",
            },
        ],
    },
    ControlSection {
        title: "New Game",
        rows: &[
            ControlRow {
                keys: "New+",
                description: "Start a new Classic game",
            },
            ControlRow {
                keys: "Modes↓",
                description: "Pick Daily, Zen, Challenge, or Time Attack",
            },
        ],
    },
    ControlSection {
        title: "HUD buttons",
        rows: &[
            ControlRow {
                keys: "←",
                description: "Undo last move",
            },
            ControlRow {
                keys: "||",
                description: "Pause / resume",
            },
            ControlRow {
                keys: "?",
                description: "This help screen",
            },
            ControlRow {
                keys: ANDROID_HINT_LABEL,
                description: "Show a hint",
            },
            ControlRow {
                keys: "≡",
                description: "Open menu (Stats, Settings, Profile...)",
            },
        ],
    },
];

#[cfg(not(target_os = "android"))]
const CONTROL_SECTIONS: &[ControlSection] = &[
    ControlSection {
        title: "Gameplay",
        rows: &[
            ControlRow {
                keys: "Drag",
                description: "Move cards between piles",
            },
            ControlRow {
                keys: "D / Space",
                description: "Draw from stock",
            },
            ControlRow {
                keys: "U",
                description: "Undo last move",
            },
            ControlRow {
                keys: "Click stock",
                description: "Draw",
            },
        ],
    },
    ControlSection {
        title: "Mouse",
        rows: &[
            ControlRow {
                keys: "Double-click",
                description: "Auto-move card to its best destination",
            },
            ControlRow {
                keys: "Right-click",
                description: "Highlight legal destinations briefly",
            },
            ControlRow {
                keys: "Hold RMB",
                description: "Open radial menu — release over an icon to quick-drop",
            },
        ],
    },
    ControlSection {
        title: "Keyboard drag",
        rows: &[
            ControlRow {
                keys: "Tab",
                description: "Focus next draggable card",
            },
            ControlRow {
                keys: "Enter",
                description: "Lift focused card (then arrows pick where)",
            },
            ControlRow {
                keys: "Arrows / Tab",
                description: "Cycle legal destinations while lifted",
            },
            ControlRow {
                keys: "Enter",
                description: "Drop the lifted cards on the focused pile",
            },
            ControlRow {
                keys: "Esc",
                description: "Cancel lift (Esc again clears focus)",
            },
            ControlRow {
                keys: "Space",
                description: "Auto-move focused card (foundation first)",
            },
        ],
    },
    ControlSection {
        title: "New Game",
        rows: &[
            ControlRow {
                keys: "N",
                description: "New Classic game (N twice if in progress)",
            },
            ControlRow {
                keys: "C",
                description: "Start today's daily challenge",
            },
            ControlRow {
                keys: "Z",
                description: "Start a Zen game (level 5+)",
            },
            ControlRow {
                keys: "X",
                description: "Start the next Challenge (level 5+)",
            },
            ControlRow {
                keys: "T",
                description: "Start a Time Attack session (level 5+)",
            },
        ],
    },
    ControlSection {
        title: "Mode Launcher (M)",
        rows: &[
            ControlRow {
                keys: "1",
                description: "Launch Classic",
            },
            ControlRow {
                keys: "2",
                description: "Launch Daily Challenge",
            },
            ControlRow {
                keys: "3",
                description: "Launch Zen (level 5+)",
            },
            ControlRow {
                keys: "4",
                description: "Launch Challenge (level 5+)",
            },
            ControlRow {
                keys: "5",
                description: "Launch Time Attack (level 5+)",
            },
        ],
    },
    ControlSection {
        title: "Overlays",
        rows: &[
            ControlRow {
                keys: "M",
                description: "Mode launcher (Home)",
            },
            ControlRow {
                keys: "P",
                description: "Profile",
            },
            ControlRow {
                keys: "S",
                description: "Stats & progression",
            },
            ControlRow {
                keys: "A",
                description: "Achievements",
            },
            ControlRow {
                keys: "L",
                description: "Leaderboard",
            },
            ControlRow {
                keys: "O",
                description: "Settings",
            },
            ControlRow {
                keys: "F1",
                description: "This help screen",
            },
            ControlRow {
                keys: "F11",
                description: "Toggle fullscreen",
            },
            ControlRow {
                keys: "Esc",
                description: "Pause / resume",
            },
            ControlRow {
                keys: "[ / ]",
                description: "SFX volume down / up",
            },
            ControlRow {
                keys: "Enter",
                description: "Play Again (on the Win Summary)",
            },
        ],
    },
];

fn spawn_help_screen(commands: &mut Commands, font_res: Option<&FontResource>) {
    let font_handle = font_res.map(|f| f.0.clone()).unwrap_or_default();
    let font_section = TextFont {
        font: font_handle.clone(),
        font_size: TYPE_BODY,
        ..default()
    };
    let font_row = font_section.clone();
    let font_kbd = TextFont {
        font: font_handle,
        font_size: TYPE_CAPTION,
        ..default()
    };

    let scrim = spawn_modal(commands, HelpScreen, Z_MODAL_PANEL, |card| {
        spawn_modal_header(card, "Controls", font_res);

        // Scrollable body — the controls reference is six sections totalling
        // ~28 rows, which overflows the modal on the 800x600 minimum
        // window. Wrapping in an `Overflow::scroll_y()` Node with a
        // constrained `max_height` keeps every row reachable; the Done
        // button below stays fixed outside the scroll.
        card.spawn((
            HelpScrollable,
            ScrollPosition::default(),
            Node {
                flex_direction: FlexDirection::Column,
                row_gap: VAL_SPACE_2,
                max_height: Val::Vh(70.0),
                overflow: Overflow::scroll_y(),
                padding: UiRect::bottom(Val::Px(96.0)),
                ..default()
            },
        ))
        .with_children(|body| {
            for section in CONTROL_SECTIONS {
                // Section title in muted text — distinguishes from row content.
                body.spawn((
                    Text::new(section.title),
                    font_section.clone(),
                    TextColor(TEXT_SECONDARY),
                ));

                // Each row is a flex-row: kbd-style chip + description.
                for row in section.rows {
                    body.spawn(Node {
                        flex_direction: FlexDirection::Row,
                        align_items: AlignItems::Center,
                        column_gap: VAL_SPACE_3,
                        ..default()
                    })
                    .with_children(|line| {
                        // Keyboard chip — suppressed on touch-first Android builds.
                        if SHOW_KEYBOARD_ACCELERATORS {
                            line.spawn((
                                Node {
                                    padding: UiRect::axes(VAL_SPACE_2, VAL_SPACE_1),
                                    min_width: Val::Px(64.0),
                                    justify_content: JustifyContent::Center,
                                    border: UiRect::all(Val::Px(1.0)),
                                    border_radius: BorderRadius::all(Val::Px(RADIUS_SM)),
                                    ..default()
                                },
                                BorderColor::all(BORDER_SUBTLE),
                                HighContrastBorder::with_default(BORDER_SUBTLE),
                            ))
                            .with_children(|chip| {
                                chip.spawn((
                                    Text::new(row.keys),
                                    font_kbd.clone(),
                                    TextColor(TEXT_PRIMARY),
                                ));
                            });
                        }

                        line.spawn((
                            Text::new(row.description),
                            font_row.clone(),
                            TextColor(TEXT_PRIMARY),
                        ));
                    });
                }

                // Section spacer — small empty box. Keeps each section
                // visually grouped.
                body.spawn(Node {
                    height: Val::Px(SPACE_2),
                    ..default()
                });
            }
        });

        spawn_modal_actions(card, |actions| {
            spawn_modal_button(
                actions,
                HelpCloseButton,
                "Done",
                Some("F1"),
                ButtonVariant::Primary,
                font_res,
            );
        });
    });
    // Help is read-only — clicking the scrim outside the card dismisses
    // alongside the existing F1 / Esc / Done paths.
    commands.entity(scrim).insert(ScrimDismissible);
}

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

    /// Regression test for M-17: Android help screen showed "→" (right-arrow)
    /// for the Hint button when the actual HUD button label is "!".
    /// Verifies that the HUD Buttons section contains exactly one row whose
    /// `keys` matches `ANDROID_HINT_LABEL`.
    #[cfg(target_os = "android")]
    #[test]
    fn android_hint_row_matches_hud_label() {
        use crate::hud_plugin::ANDROID_HINT_LABEL;
        let hud_section = CONTROL_SECTIONS
            .iter()
            .find(|s| s.title == "HUD buttons")
            .expect("HUD buttons section must exist");
        let hint_row = hud_section
            .rows
            .iter()
            .find(|r| r.description == "Show a hint")
            .expect("hint row must exist");
        assert_eq!(
            hint_row.keys, ANDROID_HINT_LABEL,
            "help hint row must match the HUD button label"
        );
    }

    fn headless_app() -> App {
        let mut app = App::new();
        app.add_plugins(MinimalPlugins).add_plugins(HelpPlugin);
        app.init_resource::<ButtonInput<KeyCode>>();
        app.update();
        app
    }

    #[test]
    fn pressing_f1_spawns_help_screen() {
        let mut app = headless_app();
        app.world_mut()
            .resource_mut::<ButtonInput<KeyCode>>()
            .press(KeyCode::F1);
        app.update();

        assert_eq!(
            app.world_mut()
                .query::<&HelpScreen>()
                .iter(app.world())
                .count(),
            1
        );
    }

    #[test]
    fn help_modal_body_is_scrollable() {
        let mut app = headless_app();
        app.world_mut()
            .resource_mut::<ButtonInput<KeyCode>>()
            .press(KeyCode::F1);
        app.update();

        let count = app
            .world_mut()
            .query::<&HelpScrollable>()
            .iter(app.world())
            .count();
        assert_eq!(
            count, 1,
            "Help modal must spawn exactly one HelpScrollable body"
        );

        let mut q = app
            .world_mut()
            .query_filtered::<&Node, With<HelpScrollable>>();
        let nodes: Vec<&Node> = q.iter(app.world()).collect();
        assert_ne!(
            nodes[0].max_height,
            Val::Auto,
            "scrollable body must set a non-default max_height"
        );
        assert_eq!(nodes[0].overflow, Overflow::scroll_y());
    }

    #[test]
    fn pressing_f1_twice_closes_help_screen() {
        let mut app = headless_app();
        app.world_mut()
            .resource_mut::<ButtonInput<KeyCode>>()
            .press(KeyCode::F1);
        app.update();

        {
            let mut input = app.world_mut().resource_mut::<ButtonInput<KeyCode>>();
            input.release(KeyCode::F1);
            input.clear();
            input.press(KeyCode::F1);
        }
        app.update();

        assert_eq!(
            app.world_mut()
                .query::<&HelpScreen>()
                .iter(app.world())
                .count(),
            0
        );
    }
}
