//! Hint and right-click highlights, plus cursor hit-testing helpers.

use super::*;


use bevy::color::Color;
use solitaire_core::Card;
use solitaire_core::game_state::GameState;

use crate::events::StateChangedEvent;
use crate::layout::{Layout, LayoutResource};
use crate::pause_plugin::PausedResource;
use crate::resources::{DragState, GameStateResource};
use crate::settings_plugin::SettingsResource;
use crate::table_plugin::{PILE_MARKER_DEFAULT_COLOUR, PileMarker};

/// Counts down `HintHighlight::remaining` each frame. When it reaches zero,
/// removes both `HintHighlight` and `HintHighlightTimer` (if present) and
/// resets the card sprite to its normal face-up colour.
pub(super) fn tick_hint_highlight(
    time: Res<Time>,
    mut commands: Commands,
    mut query: Query<(Entity, &mut HintHighlight, &mut Sprite, &CardEntity)>,
    game: Res<GameStateResource>,
    settings: Option<Res<SettingsResource>>,
    card_images: Option<Res<CardImageSet>>,
) {
    let back_idx = settings.as_ref().map_or(0, |s| s.0.selected_card_back);
    let use_images = card_images.is_some();
    for (entity, mut hint, mut sprite, card_entity) in query.iter_mut() {
        hint.remaining -= time.delta_secs();
        if hint.remaining <= 0.0 {
            // Restore the normal sprite colour.
            // When image-based rendering is active, WHITE is the neutral tint;
            // otherwise restore the solid colour appropriate to the card state.
            sprite.color = if use_images {
                Color::WHITE
            } else {
                let is_face_up = all_cards(&game.0)
                    .iter()
                    .find(|(c, _face_up)| *c == card_entity.card)
                    .is_some_and(|(_, face_up)| *face_up);
                if is_face_up {
                    CARD_FACE_COLOUR
                } else {
                    card_back_colour(back_idx)
                }
            };
            commands
                .entity(entity)
                .remove::<HintHighlight>()
                .remove::<HintHighlightTimer>();
        }
    }
}

// ---------------------------------------------------------------------------
// Task #46 — Right-click legal destination highlights
// ---------------------------------------------------------------------------

/// Lime tint applied to a `PileMarker` sprite when it is a legal
/// destination for the right-clicked card. Same RGB as the design-
/// system [`STATE_SUCCESS`] token at 60% alpha. Spelled as a literal
/// because `Alpha::with_alpha` is not yet a `const` trait method on
/// stable; the tracking test below pins the RGB to `STATE_SUCCESS`
/// so a palette swap can't drift the two apart silently.
pub(super) const RIGHT_CLICK_HIGHLIGHT_COLOUR: Color = Color::srgba(0.675, 0.761, 0.404, 0.6);

/// Counts down `RightClickHighlightTimer` each frame and clears the highlight
/// when the timer expires.
///
/// This is a fallback expiry: highlights also clear immediately on
/// `StateChangedEvent` (move made) or when the game is paused, whichever comes
/// first. The 1.5 s timer ensures highlights always disappear even if the
/// player takes no further action.
pub(super) fn tick_right_click_highlights(
    mut commands: Commands,
    time: Res<Time>,
    paused: Option<Res<PausedResource>>,
    mut highlights: Query<
        (Entity, &mut RightClickHighlightTimer, &mut Sprite),
        With<RightClickHighlight>,
    >,
) {
    if paused.is_some_and(|p| p.0) {
        return;
    }
    let dt = time.delta_secs();
    for (entity, mut timer, mut sprite) in &mut highlights {
        timer.0 -= dt;
        if timer.0 <= 0.0 {
            // Restore the pile marker to its default colour before removing
            // the highlight marker component.
            sprite.color = PILE_MARKER_DEFAULT_COLOUR;
            commands
                .entity(entity)
                .remove::<RightClickHighlight>()
                .remove::<RightClickHighlightTimer>();
        }
    }
}

/// Removes the `RightClickHighlight` marker from every highlighted pile and
/// resets its sprite colour to `PILE_MARKER_DEFAULT_COLOUR`.
///
/// Shared by the on-state-change and on-pause clear systems to avoid
/// duplicating the removal logic.
pub(super) fn clear_right_click_highlights(
    commands: &mut Commands,
    highlighted: &Query<Entity, With<RightClickHighlight>>,
    pile_markers: &mut Query<(Entity, &PileMarker, &mut Sprite)>,
) {
    for entity in highlighted.iter() {
        commands.entity(entity).remove::<RightClickHighlight>();
    }
    for (_entity, _, mut sprite) in pile_markers.iter_mut() {
        if sprite.color == RIGHT_CLICK_HIGHLIGHT_COLOUR {
            sprite.color = PILE_MARKER_DEFAULT_COLOUR;
        }
    }
}

/// Clears all right-click destination highlights whenever any game-state
/// mutation succeeds (`StateChangedEvent` fires).
///
/// This ensures stale highlights do not linger after a card is moved.
pub(super) fn clear_right_click_highlights_on_state_change(
    mut events: MessageReader<StateChangedEvent>,
    mut commands: Commands,
    highlighted: Query<Entity, With<RightClickHighlight>>,
    mut pile_markers: Query<(Entity, &PileMarker, &mut Sprite)>,
) {
    if events.read().next().is_none() {
        return;
    }
    clear_right_click_highlights(&mut commands, &highlighted, &mut pile_markers);
}

/// Clears all right-click destination highlights when the game is paused
/// (`PausedResource` changes to `true`).
///
/// Prevents highlighted pile markers from remaining visible behind the pause
/// overlay.
pub(super) fn clear_right_click_highlights_on_pause(
    paused: Option<Res<PausedResource>>,
    mut commands: Commands,
    highlighted: Query<Entity, With<RightClickHighlight>>,
    mut pile_markers: Query<(Entity, &PileMarker, &mut Sprite)>,
) {
    let Some(paused) = paused else { return };
    if paused.is_changed() && paused.0 {
        clear_right_click_highlights(&mut commands, &highlighted, &mut pile_markers);
    }
}

/// Handles right-click: highlights legal destination piles for the clicked card,
/// and clears highlights on any subsequent right- or left-click.
///
/// This system lives in `CardPlugin` to keep `InputPlugin` untouched.
#[allow(clippy::too_many_arguments)]
pub(super) fn handle_right_click(
    buttons: Option<Res<ButtonInput<MouseButton>>>,
    paused: Option<Res<PausedResource>>,
    drag: Res<DragState>,
    windows: Query<&Window, With<bevy::window::PrimaryWindow>>,
    cameras: Query<(&Camera, &GlobalTransform)>,
    layout: Option<Res<LayoutResource>>,
    game: Res<GameStateResource>,
    mut commands: Commands,
    mut pile_markers: Query<(Entity, &PileMarker, &mut Sprite)>,
    card_entities: Query<(Entity, &CardEntity, &Transform)>,
    highlighted: Query<Entity, With<RightClickHighlight>>,
) {
    if paused.is_some_and(|p| p.0) {
        return;
    }

    let Some(buttons) = buttons else { return };
    let left_pressed = buttons.just_pressed(MouseButton::Left);
    let right_pressed = buttons.just_pressed(MouseButton::Right);

    // Clear existing highlights on any click.
    if left_pressed || right_pressed {
        for entity in &highlighted {
            commands.entity(entity).remove::<RightClickHighlight>();
        }
        for (_entity, _, mut sprite) in &mut pile_markers {
            if sprite.color == RIGHT_CLICK_HIGHLIGHT_COLOUR {
                sprite.color = PILE_MARKER_DEFAULT_COLOUR;
            }
        }
    }

    // Only proceed for right-clicks while not dragging.
    if !right_pressed || !drag.is_idle() {
        return;
    }

    let Some(layout) = layout else { return };

    // Convert cursor to world-space position.
    let Some(world) = cursor_world_pos(&windows, &cameras) else {
        return;
    };

    // Find the topmost face-up card under the cursor.
    let Some(card) = find_top_card_at(world, &game.0, &layout.0, &card_entities) else {
        return;
    };

    let Some(source_pile) = game.0.pile_containing_card(card.clone()) else {
        return;
    };

    // Tint piles that legally accept the card.
    for (entity, pile_marker, mut sprite) in &mut pile_markers {
        let legal = game.0.can_move_cards(&source_pile, &pile_marker.0, 1);
        if legal {
            sprite.color = RIGHT_CLICK_HIGHLIGHT_COLOUR;
            commands
                .entity(entity)
                .insert(RightClickHighlight)
                .insert(RightClickHighlightTimer(1.5));
        }
    }
}

/// Converts cursor position to 2-D world coordinates.
pub(super) fn cursor_world_pos(
    windows: &Query<&Window, With<bevy::window::PrimaryWindow>>,
    cameras: &Query<(&Camera, &GlobalTransform)>,
) -> Option<Vec2> {
    let window = windows.single().ok()?;
    let cursor = window.cursor_position()?;
    let (camera, camera_transform) = cameras.single().ok()?;
    camera.viewport_to_world_2d(camera_transform, cursor).ok()
}

/// Returns the topmost face-up `Card` under `cursor` by checking axis-aligned
/// bounding rectangles of all card sprites, picking the highest Z.
pub(super) fn find_top_card_at(
    cursor: Vec2,
    game: &GameState,
    layout: &Layout,
    card_entities: &Query<(Entity, &CardEntity, &Transform)>,
) -> Option<Card> {
    let half = layout.card_size / 2.0;
    let mut best: Option<(f32, Card)> = None;

    for (_, card_entity, transform) in card_entities.iter() {
        let pos = transform.translation.truncate();
        if cursor.x < pos.x - half.x
            || cursor.x > pos.x + half.x
            || cursor.y < pos.y - half.y
            || cursor.y > pos.y + half.y
        {
            continue;
        }
        let found = all_cards(game)
            .into_iter()
            .find(|(c, face_up)| *c == card_entity.card && *face_up);
        if let Some((card, _)) = found {
            let z = transform.translation.z;
            if best.as_ref().is_none_or(|(bz, _)| z > *bz) {
                best = Some((z, card));
            }
        }
    }
    best.map(|(_, card)| card)
}

// ---------------------------------------------------------------------------
// Task #28 — Stock-empty visual indicator
// ---------------------------------------------------------------------------


