use bevy::prelude::*;
use bevy::window::WindowClosed;
use bevy::{audio::Volume, window::ExitCondition};
use rand::RngExt;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fs};

const GRID_RADIUS: i32 = 7;
const MEDIUM_RADIUS: i32 = 3;

const HEX_SIZE: f32 = 28.0;

const COLLAPSE_DURATION: f32 = 0.45;

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct Hex {
    q: i32,
    r: i32,
}

impl Hex {
    fn new(q: i32, r: i32) -> Self {
        Self { q, r }
    }

    fn add(self, other: Hex) -> Hex {
        Hex::new(self.q + other.q, self.r + other.r)
    }

    fn rotate_right(self) -> Self {
        // Axial-coordinate rotation around the origin.
        Hex::new(-self.r, self.q + self.r)
    }

    fn distance(self, other: Hex) -> i32 {
        let s1 = -self.q - self.r;
        let s2 = -other.q - other.r;

        ((self.q - other.q).abs() + (self.r - other.r).abs() + (s1 - s2).abs()) / 2
    }
}

#[derive(PartialEq)]
enum GamePhase {
    Playing,
    GameOver,
}

#[derive(Resource)]
struct GameState {
    cells: HashMap<Hex, u32>,
    pieces: Vec<Piece>,
    selected_piece: usize,
    score: u32,
    dragging: bool,
    drag_start: Vec2,
    drag_position: Vec2,
    drag_moved: bool,
    phase: GamePhase,
}
impl GameState {
    fn from_save(save: GameSave) -> GameState {
        GameState {
            cells: HashMap::from_iter(save.cells),
            pieces: save.pieces,
            selected_piece: 0,
            score: save.score,
            dragging: false,
            drag_start: Vec2::ZERO,
            drag_position: Vec2::ZERO,
            drag_moved: false,
            phase: GamePhase::Playing,
        }
    }
}

#[derive(Clone, Serialize, Deserialize)]
struct Piece {
    cells: Vec<Hex>,
    color: Color,
}

#[derive(Component)]
struct ScoreText;

#[derive(Component)]
struct GameOverButton {
    action: GameOverAction,
}
#[derive(Component, PartialEq)]
struct GameOverUI;
#[derive(Clone, Copy, PartialEq)]
enum GameOverAction {
    Restart,
    Quit,
}

#[derive(Resource, Default)]
struct CollapseAnimations {
    active: Vec<CollapseAnimation>,
}

struct CollapseAnimation {
    center: Hex,
    cells: Vec<(Hex, Color)>,
    elapsed: f32,
}

#[derive(Resource)]
struct GameSounds {
    small_pop: Handle<AudioSource>,
    big_pop: Handle<AudioSource>,
}

#[derive(Clone, Serialize, Deserialize)]
struct GameSave {
    score: u32,
    cells: Vec<(Hex, u32)>,
    pieces: Vec<Piece>,
}
impl GameSave {
    fn from_state(state: &GameState) -> GameSave {
        GameSave {
            score: state.score,
            cells: state
                .cells
                .iter()
                .map(|(key, value)| (key.clone(), value.clone()))
                .collect(),
            pieces: state.pieces.clone(),
        }
    }
}

fn main() {
    let game = load_game();
    App::new()
        .add_plugins(DefaultPlugins.set(WindowPlugin {
            primary_window: Some(Window {
                title: "Hex Collapse".into(),
                resolution: (1100, 700).into(),
                ..default()
            }),
            exit_condition: ExitCondition::DontExit,
            ..default()
        }))
        .insert_resource(game)
        .insert_resource(CollapseAnimations::default())
        .add_systems(Startup, setup)
        .add_systems(
            Update,
            (
                handle_window_closed,
                mouse_input,
                draw_game,
                update_score_text,
                update_collapse_animations,
                check_gameover,
                button_hover_system,
                draw_gameover,
            )
                .chain(),
        )
        .run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn(Camera2d);

    commands.spawn((
        Text::new("Score: 0"),
        TextFont {
            font_size: FontSize::Px(32.0),
            ..default()
        },
        TextColor(Color::WHITE),
        Node {
            position_type: PositionType::Absolute,
            left: Val::Px(25.0),
            top: Val::Px(20.0),
            ..default()
        },
        ScoreText,
    ));

    commands.insert_resource(GameSounds {
        small_pop: asset_server.load("sounds/small_pop.ogg"),
        big_pop: asset_server.load("sounds/big_pop.ogg"),
    });
}

fn update_score_text(state: Res<GameState>, mut query: Query<&mut Text, With<ScoreText>>) {
    if !state.is_changed() {
        return;
    }

    for mut text in &mut query {
        **text = format!("Score: {}", state.score);
    }
}

fn check_gameover(mut state: ResMut<GameState>) {
    if state.phase != GamePhase::Playing {
        return;
    }

    let mut can_be_placed = false;

    'outer: for q in -GRID_RADIUS..=GRID_RADIUS {
        for r in -GRID_RADIUS..=GRID_RADIUS {
            let grid_hex = Hex::new(q, r);

            for i in 0..state.pieces.len() {
                let mut piece = state.pieces[i].clone();

                for _ in 0..6 {
                    rotate_piece(&mut piece);
                    let placed_cells: Vec<Hex> =
                        piece.cells.iter().map(|cell| grid_hex.add(*cell)).collect();

                    if can_place(&state.cells, &placed_cells) {
                        can_be_placed = true;
                        break 'outer;
                    }
                }
            }
        }
    }

    if !can_be_placed {
        state.phase = GamePhase::GameOver;
    }
}

fn draw_gameover(
    mut commands: Commands,
    state: ResMut<GameState>,
    ui_query: Query<Entity, With<GameOverUI>>,
) {
    let ui = match ui_query.single() {
        Ok(u) => Some(u),
        Err(_) => None,
    };
    if state.phase == GamePhase::GameOver && ui == None {
        // Container for buttons
        commands
            .spawn((
                Node {
                    position_type: PositionType::Absolute,
                    width: Val::Percent(100.0),
                    height: Val::Percent(100.0),
                    display: Display::Flex,
                    flex_direction: FlexDirection::Column,
                    justify_content: JustifyContent::Center,
                    align_items: AlignItems::Center,
                    ..default()
                },
                BackgroundColor(Color::srgba(0.1, 0.1, 0.1, 0.8)),
                GameOverUI,
            ))
            .with_children(|parent| {
                parent.spawn((
                    Text::new("GAME OVER"),
                    TextFont {
                        font_size: FontSize::Px(70.0),
                        ..default()
                    },
                    TextColor(Color::srgb(1.0, 0.0, 0.0)),
                ));

                parent.spawn((
                    Text::new(format!("Final Score: {}", state.score)),
                    TextFont {
                        font_size: FontSize::Px(48.0),
                        ..default()
                    },
                    TextColor(Color::WHITE),
                ));

                parent
                    .spawn((Node {
                        display: Display::Flex,
                        flex_direction: FlexDirection::Row,
                        justify_content: JustifyContent::Center,
                        align_items: AlignItems::Center,
                        ..default()
                    },))
                    .with_children(|child_node| {
                        child_node
                            .spawn((
                                Button,
                                Node {
                                    width: Val::Px(200.0),
                                    height: Val::Px(60.0),
                                    display: Display::Flex,
                                    justify_content: JustifyContent::Center,
                                    align_items: AlignItems::Center,
                                    border: UiRect::all(Val::Px(2.0)),
                                    margin: UiRect {
                                        left: (Val::Px(5.0)),
                                        right: (Val::Px(5.0)),
                                        top: (Val::Px(5.0)),
                                        bottom: (Val::Px(5.0)),
                                    },
                                    ..default()
                                },
                                BorderColor::all(Color::WHITE),
                                BackgroundColor(Color::srgb(0.2, 0.2, 0.2)),
                                GameOverButton {
                                    action: GameOverAction::Restart,
                                },
                            ))
                            .with_children(|button| {
                                button.spawn((
                                    Text::new("RESTART"),
                                    TextFont {
                                        font_size: FontSize::Px(32.0),
                                        ..default()
                                    },
                                    TextColor(Color::WHITE),
                                ));
                            });

                        child_node
                            .spawn((
                                Button,
                                Node {
                                    width: Val::Px(200.0),
                                    height: Val::Px(60.0),
                                    display: Display::Flex,
                                    justify_content: JustifyContent::Center,
                                    align_items: AlignItems::Center,
                                    border: UiRect::all(Val::Px(2.0)),
                                    margin: UiRect {
                                        left: (Val::Px(5.0)),
                                        right: (Val::Px(5.0)),
                                        top: (Val::Px(5.0)),
                                        bottom: (Val::Px(5.0)),
                                    },
                                    ..default()
                                },
                                BorderColor::all(Color::WHITE),
                                BackgroundColor(Color::srgb(0.2, 0.2, 0.2)),
                                GameOverButton {
                                    action: GameOverAction::Quit,
                                },
                            ))
                            .with_children(|button| {
                                button.spawn((
                                    Text::new("QUIT"),
                                    TextFont {
                                        font_size: FontSize::Px(32.0),
                                        ..default()
                                    },
                                    TextColor(Color::WHITE),
                                ));
                            });
                    });
            });
    } else if state.phase != GamePhase::GameOver {
        match ui {
            Some(u) => {
                commands.entity(u).despawn_children();
                commands.entity(u).despawn();
            }
            None => {}
        }
    }
}

fn mouse_input(
    buttons: Res<ButtonInput<MouseButton>>,
    windows: Query<(Entity, &Window)>,
    camera_query: Query<(&Camera, &GlobalTransform)>,
    mut state: ResMut<GameState>,
    mut animations: ResMut<CollapseAnimations>,
    mut commands: Commands,
    sounds: Res<GameSounds>,
    mut button_query: Query<(&Interaction, &GameOverButton), Changed<Interaction>>,
) {
    let (window_entity, window) = match windows.single() {
        Ok(w) => w,
        Err(_) => return,
    };
    let Some(cursor) = window.cursor_position() else {
        return;
    };

    let (camera, camera_transform) = match camera_query.single() {
        Ok(c) => c,
        Err(_) => return,
    };

    let Ok(world_position) = camera.viewport_to_world_2d(camera_transform, cursor) else {
        return;
    };

    let scale_factor = calculate_scale_factor(window.width(), window.height());
    let is_landscape = window.width() > window.height();

    match state.phase {
        GamePhase::Playing => {
            let hex_size = HEX_SIZE * scale_factor;
            let grid_origin = calculate_grid_origin(window.width(), window.height(), is_landscape);

            if buttons.just_pressed(MouseButton::Left) {
                state.drag_start = world_position;
                state.drag_position = world_position;
                state.dragging = true;
                state.drag_moved = false;

                let len = state.pieces.len();
                // Clicking one of the pieces selects it.
                for index in 0..len {
                    let position = piece_position(
                        index,
                        len,
                        window.width(),
                        window.height(),
                        scale_factor,
                        is_landscape,
                    );
                    if world_position.distance(position) < 70.0 {
                        state.selected_piece = index;
                        state.drag_start = world_position;
                        state.drag_position = world_position;
                        return;
                    }
                }
            }

            if buttons.pressed(MouseButton::Left) && state.dragging {
                state.drag_position = world_position;

                if world_position.distance(state.drag_start) > 8.0 {
                    state.drag_moved = true;
                }
            }

            if buttons.just_released(MouseButton::Left) && state.dragging {
                let release_position = world_position;
                let was_dragged = state.drag_moved;

                state.dragging = false;

                if !was_dragged {
                    // A click rotates the selected shape.
                    let selected_piece = state.selected_piece;
                    rotate_piece(&mut state.pieces[selected_piece]);
                    return;
                }

                let Some(anchor) = world_to_hex(release_position, hex_size, grid_origin) else {
                    return;
                };

                let piece = state.pieces[state.selected_piece].clone();
                let placed_cells: Vec<Hex> =
                    piece.cells.iter().map(|cell| anchor.add(*cell)).collect();

                if can_place(&state.cells, &placed_cells) {
                    let mut rng = rand::rng();

                    for cell in &placed_cells {
                        state.cells.insert(*cell, rng.random_range(1..=10));
                    }

                    commands.spawn((
                        AudioPlayer::new(sounds.small_pop.clone()),
                        PlaybackSettings::DESPAWN
                            .with_speed(rng.random_range(0.88..=1.16))
                            .with_volume(Volume::Linear(0.85)),
                    ));

                    resolve_completed_hexagons(&mut state, &mut animations, &mut commands, &sounds);

                    let selected_piece = state.selected_piece;
                    state.pieces[selected_piece] = random_piece();
                }
            }
        }
        GamePhase::GameOver => {
            // Handle button clicks
            for (interaction, button) in &mut button_query {
                match *interaction {
                    Interaction::Pressed => {
                        match button.action {
                            GameOverAction::Restart => {
                                reset_game(&mut state);
                            }
                            GameOverAction::Quit => {
                                // Close the app
                                commands.entity(window_entity).despawn();
                            }
                        }
                    }
                    _ => {}
                }
            }
        }
    }
}

fn rotate_piece(piece: &mut Piece) {
    for cell in &mut piece.cells {
        *cell = cell.rotate_right();
    }
}

fn can_place(board: &HashMap<Hex, u32>, cells: &[Hex]) -> bool {
    cells
        .iter()
        .all(|cell| cell.distance(Hex::new(0, 0)) <= GRID_RADIUS && !board.contains_key(cell))
}

fn resolve_completed_hexagons(
    state: &mut GameState,
    animations: &mut CollapseAnimations,
    commands: &mut Commands,
    sounds: &GameSounds,
) {
    let centers: Vec<Hex> = state.cells.keys().copied().collect();

    // Find every completed hexagon before changing state.cells.
    let completed: Vec<(Hex, Vec<Hex>, u32)> = centers
        .into_iter()
        .filter_map(|center| {
            let required_cells = medium_hexagon(center);

            required_cells
                .iter()
                .all(|cell| state.cells.contains_key(cell))
                .then(|| {
                    let gained = required_cells.iter().map(|cell| state.cells[cell]).sum();

                    (center, required_cells, gained)
                })
        })
        .collect();

    if completed.is_empty() {
        return;
    }

    let gained = completed.iter().map(|(_, _, gained)| *gained).sum::<u32>();
    state.score += gained * completed.len() as u32;

    commands.spawn((
        AudioPlayer::new(sounds.big_pop.clone()),
        PlaybackSettings::DESPAWN
            .with_speed(0.92)
            .with_volume(Volume::Linear(1.0)),
    ));

    // Capture animation data before removing any cells.
    for (center, required_cells, _) in &completed {
        let animated_cells: Vec<(Hex, Color)> = required_cells
            .iter()
            .filter_map(|cell| {
                state
                    .cells
                    .get(cell)
                    .map(|value| (*cell, value_color(*value)))
            })
            .collect();

        animations.active.push(CollapseAnimation {
            center: *center,
            cells: animated_cells,
            elapsed: 0.0,
        });
    }

    // Remove all cells belonging to all completed hexagons.
    for (_, required_cells, _) in &completed {
        for cell in required_cells {
            state.cells.remove(cell);
        }
    }

    // Insert every collapsed center after removals are complete.
    for (center, _, gained) in completed {
        state.cells.insert(center, gained);
    }

    // Continue resolving chains created by the collapse.
    resolve_completed_hexagons(state, animations, commands, sounds);
}

fn medium_hexagon(center: Hex) -> Vec<Hex> {
    let mut result = Vec::new();

    for q in -MEDIUM_RADIUS + 1..=MEDIUM_RADIUS - 1 {
        for r in -MEDIUM_RADIUS + 1..=MEDIUM_RADIUS - 1 {
            let cell = center.add(Hex::new(q, r));

            if cell.distance(center) < MEDIUM_RADIUS {
                result.push(cell);
            }
        }
    }

    result
}

fn world_to_hex(position: Vec2, hex_size: f32, grid_origin: Vec2) -> Option<Hex> {
    let local = position - grid_origin;

    let q = (local.x / (hex_size * 1.5)).round();
    let r = ((local.y - q * hex_size * 0.8660254) / (hex_size * 1.7320508)).round();

    let hex = Hex::new(q as i32, r as i32);

    if hex.distance(Hex::new(0, 0)) <= GRID_RADIUS {
        Some(hex)
    } else {
        None
    }
}

fn hex_to_world(hex: Hex, hex_size: f32, grid_origin: Vec2) -> Vec2 {
    grid_origin
        + Vec2::new(
            hex_size * 1.5 * hex.q as f32,
            hex_size * 1.7320508 * (hex.r as f32 + hex.q as f32 * 0.5),
        )
}

fn piece_position(
    index: usize,
    size: usize,
    width: f32,
    height: f32,
    scale_factor: f32,
    is_landscape: bool,
) -> Vec2 {
    let midpoint = (size - 1) as f32 / 2.0;
    let offset = index as f32 - midpoint;

    if is_landscape {
        // Vertical column on the left side
        let piece_spacing = 200.0 * scale_factor;
        Vec2::new(-1.0 * width * 0.3, 0.0 + (offset * piece_spacing))
    } else {
        // Horizontal row at the top
        let piece_spacing = 170.0 * scale_factor;
        Vec2::new(0.0 + (offset * piece_spacing), height * 0.3)
    }
}
fn random_piece() -> Piece {
    let mut rng = rand::rng();

    let templates = [
        vec![Hex::new(0, 0)],                                  // 1 hex
        vec![Hex::new(0, 0), Hex::new(1, 0)],                  // simple line
        vec![Hex::new(0, 0), Hex::new(1, 0), Hex::new(0, 1)],  // triangle
        vec![Hex::new(0, 0), Hex::new(1, 0), Hex::new(-1, 0)], // double length line
        vec![Hex::new(0, 0), Hex::new(1, 0), Hex::new(0, -1)], // C
        vec![
            Hex::new(0, 0),
            Hex::new(1, 0),
            Hex::new(-1, 0),
            Hex::new(0, 1),
        ], // L
        vec![
            Hex::new(0, 0),
            Hex::new(1, 0),
            Hex::new(-1, 0),
            Hex::new(1, -1),
        ], // Backwards L
        vec![
            Hex::new(0, 0),
            Hex::new(1, 0),
            Hex::new(-1, 0),
            Hex::new(1, -1),
            Hex::new(0, 1),
        ], // T
        vec![
            Hex::new(0, 0),
            Hex::new(-1, 0),
            Hex::new(1, 0),
            Hex::new(0, -1),
            Hex::new(1, -1),
        ], // Pentagon
        vec![
            Hex::new(0, 0),
            Hex::new(1, 0),
            Hex::new(-1, 0),
            Hex::new(0, 1),
            Hex::new(0, -1),
            Hex::new(1, -1),
            Hex::new(-1, 1),
        ], // Hexagon
    ];

    let pindex = rng.random_range(0..templates.len());

    let hue = rng.random_range(0.0..360.0);
    let saturation = rng.random_range(0.75..1.0);
    let value = rng.random_range(0.8..1.0);

    let mut piece = Piece {
        cells: templates[pindex].clone(),
        color: Color::hsv(hue, saturation, value),
    };

    // Randomly rotate the new piece 0–3 times.
    let rotations = rng.random_range(0..4);

    for _ in 0..rotations {
        rotate_piece(&mut piece);
    }

    piece
}

fn calculate_scale_factor(width: f32, height: f32) -> f32 {
    // Scale based on the smaller dimension to ensure everything fits
    let min_dimension = width.min(height);
    // Adjust the divisor based on your target scale
    (min_dimension / 800.0).max(0.5).min(2.0) // Clamp between 0.5x and 2.0x
}

fn calculate_grid_origin(width: f32, height: f32, is_landscape: bool) -> Vec2 {
    if is_landscape {
        Vec2::new(width * 0.2, 0.0)
    } else {
        Vec2::new(0.0, -1.0 * height * 0.1)
    }
}
fn draw_game(
    mut gizmos: Gizmos,
    state: Res<GameState>,
    mut animations: ResMut<CollapseAnimations>,
    windows: Query<&Window>,
) {
    let window = match windows.single() {
        Ok(w) => w,
        Err(_) => return,
    };
    let scale_factor = calculate_scale_factor(window.width(), window.height());
    let is_landscape = window.width() > window.height();

    let hex_size = HEX_SIZE * scale_factor;
    let grid_origin = calculate_grid_origin(window.width(), window.height(), is_landscape);

    // Draw the playable board
    for q in -GRID_RADIUS..=GRID_RADIUS {
        for r in -GRID_RADIUS..=GRID_RADIUS {
            let hex = Hex::new(q, r);

            if hex.distance(Hex::new(0, 0)) <= GRID_RADIUS {
                let position = hex_to_world(hex, hex_size, grid_origin);

                if let Some(color) = state.cells.get(&hex).map(|value| value_color(*value)) {
                    draw_hex(&mut gizmos, position, hex_size, color);
                    draw_hex(&mut gizmos, position, hex_size * 0.8, color);
                    // draw_hex(&mut gizmos, position, hex_size * 0.6, color);
                } else {
                    draw_hex(
                        &mut gizmos,
                        position,
                        hex_size,
                        Color::srgb(0.50, 0.50, 0.50),
                    );
                }
            }
        }
    }

    // Highlight the cells underneath the dragged piece
    if state.dragging && state.drag_moved {
        let piece = &state.pieces[state.selected_piece];

        if let Some(anchor) = world_to_hex(state.drag_position, hex_size, grid_origin) {
            let hovered_cells: Vec<Hex> =
                piece.cells.iter().map(|cell| anchor.add(*cell)).collect();

            let placement_is_valid = can_place(&state.cells, &hovered_cells);

            let highlight_color = if placement_is_valid {
                Color::srgba(1.0, 1.0, 1.0, 0.8)
            } else {
                Color::srgba(1.0, 0.0, 0.0, 0.8)
            };

            for hex in hovered_cells {
                if hex.distance(Hex::new(0, 0)) <= GRID_RADIUS {
                    draw_hex(
                        &mut gizmos,
                        hex_to_world(hex, hex_size, grid_origin),
                        hex_size,
                        highlight_color,
                    );
                }
            }
        }
    }

    let len = state.pieces.len();
    let piece_hex_size = hex_size * 0.8;

    // Draw the available pieces
    for index in 0..len {
        let piece = &state.pieces[index];
        let origin = piece_position(
            index,
            len,
            window.width(),
            window.height(),
            scale_factor,
            is_landscape,
        );

        for cell in &piece.cells {
            let position = origin + axial_offset(*cell, piece_hex_size);

            draw_hex(&mut gizmos, position, piece_hex_size, piece.color);
        }
    }

    // Draw the piece currently being dragged
    if state.dragging && state.drag_moved {
        let piece = &state.pieces[state.selected_piece];

        for cell in &piece.cells {
            let position = state.drag_position + axial_offset(*cell, hex_size);

            draw_hex(
                &mut gizmos,
                position,
                hex_size,
                piece.color.with_alpha(0.75),
            );
        }
    }

    draw_collapse_animations(&mut gizmos, &mut animations, hex_size, grid_origin);
}

fn axial_offset(hex: Hex, size: f32) -> Vec2 {
    Vec2::new(
        size * 1.5 * hex.q as f32,
        size * 1.7320508 * (hex.r as f32 + hex.q as f32 * 0.5),
    )
}

fn draw_hex(gizmos: &mut Gizmos, center: Vec2, radius: f32, color: Color) {
    let mut points = [Vec2::ZERO; 7];

    for i in 0..6 {
        let angle = std::f32::consts::PI / 3.0 * i as f32;
        points[i] = center + Vec2::new(angle.cos(), angle.sin()) * radius;
    }

    points[6] = points[0];

    for i in 0..6 {
        gizmos.line_2d(points[i], points[i + 1], color);
    }
}

fn value_color(value: u32) -> Color {
    const MAX_VALUE: f32 = 10000.0;

    let value = value.min(MAX_VALUE as u32) as f32;
    let position = value / MAX_VALUE;

    let stops: &[(f32, [f32; 3])] = &[
        (0.00, [0.02, 0.05, 0.35]),
        (0.01, [0.02, 0.38, 1.00]),
        (0.02, [0.01, 0.40, 1.00]),
        (0.03, [0.01, 0.42, 1.00]),
        (0.04, [0.00, 0.44, 1.00]),
        (0.05, [0.00, 0.46, 1.00]),
        (0.06, [0.00, 0.49, 1.00]),
        (0.07, [0.00, 0.52, 1.00]),
        (0.08, [0.00, 0.55, 1.00]),
        (0.09, [0.00, 0.58, 1.00]),
        (0.10, [0.00, 0.61, 1.00]),
        (0.11, [0.00, 0.64, 1.00]),
        (0.12, [0.00, 0.67, 1.00]),
        (0.13, [0.00, 0.70, 1.00]),
        (0.14, [0.00, 0.72, 1.00]),
        (0.15, [0.00, 0.75, 1.00]),
        (0.16, [0.00, 0.78, 1.00]),
        (0.17, [0.00, 0.81, 1.00]),
        (0.18, [0.00, 0.84, 1.00]),
        (0.19, [0.00, 0.86, 1.00]),
        (0.20, [0.00, 0.88, 1.00]),
        (0.21, [0.00, 0.90, 0.96]),
        (0.22, [0.00, 0.92, 0.93]),
        (0.23, [0.00, 0.93, 0.90]),
        (0.24, [0.00, 0.94, 0.87]),
        (0.25, [0.00, 0.95, 0.85]),
        (0.26, [0.00, 0.95, 0.82]),
        (0.27, [0.00, 0.95, 0.85]),
        (0.28, [0.00, 0.95, 0.79]),
        (0.29, [0.00, 0.95, 0.75]),
        (0.30, [0.00, 0.95, 0.71]),
        (0.31, [0.00, 0.95, 0.68]),
        (0.32, [0.00, 0.95, 0.65]),
        (0.33, [0.00, 0.95, 0.65]),
        (0.34, [0.00, 0.94, 0.61]),
        (0.35, [0.00, 0.93, 0.57]),
        (0.36, [0.00, 0.92, 0.53]),
        (0.37, [0.00, 0.91, 0.49]),
        (0.38, [0.00, 0.90, 0.45]),
        (0.39, [0.00, 0.86, 0.35]),
        (0.40, [0.00, 0.90, 0.45]),
        (0.41, [0.00, 0.87, 0.38]),
        (0.42, [0.00, 0.84, 0.32]),
        (0.43, [0.00, 0.81, 0.27]),
        (0.44, [0.00, 0.80, 0.25]),
        (0.45, [0.00, 0.82, 0.25]),
        (0.46, [0.01, 0.84, 0.22]),
        (0.47, [0.02, 0.86, 0.18]),
        (0.48, [0.03, 0.88, 0.14]),
        (0.49, [0.04, 0.89, 0.12]),
        (0.50, [0.05, 0.90, 0.10]),
        (0.51, [0.09, 0.91, 0.08]),
        (0.52, [0.13, 0.92, 0.06]),
        (0.53, [0.17, 0.93, 0.04]),
        (0.54, [0.21, 0.94, 0.03]),
        (0.55, [0.25, 0.95, 0.02]),
        (0.56, [0.30, 0.96, 0.02]),
        (0.57, [0.35, 0.97, 0.01]),
        (0.58, [0.40, 0.98, 0.01]),
        (0.59, [0.45, 0.99, 0.00]),
        (0.60, [0.50, 1.00, 0.00]),
        (0.61, [0.54, 1.00, 0.00]),
        (0.62, [0.59, 1.00, 0.00]),
        (0.63, [0.63, 1.00, 0.00]),
        (0.64, [0.68, 1.00, 0.00]),
        (0.65, [0.72, 1.00, 0.00]),
        (0.66, [0.76, 1.00, 0.00]),
        (0.67, [0.80, 1.00, 0.00]),
        (0.68, [0.83, 1.00, 0.00]),
        (0.69, [0.86, 1.00, 0.00]),
        (0.70, [0.88, 1.00, 0.00]),
        (0.71, [0.92, 0.99, 0.00]),
        (0.72, [0.95, 0.98, 0.00]),
        (0.73, [0.98, 0.97, 0.00]),
        (0.74, [1.00, 0.96, 0.00]),
        (0.75, [1.00, 0.95, 0.00]),
        (0.76, [1.00, 0.92, 0.00]),
        (0.77, [1.00, 0.89, 0.00]),
        (0.78, [1.00, 0.86, 0.00]),
        (0.79, [1.00, 0.84, 0.00]),
        (0.80, [1.00, 0.82, 0.00]),
        (0.81, [1.00, 0.77, 0.00]),
        (0.82, [1.00, 0.73, 0.00]),
        (0.83, [1.00, 0.70, 0.00]),
        (0.84, [1.00, 0.68, 0.00]),
        (0.85, [1.00, 0.61, 0.00]),
        (0.86, [1.00, 0.56, 0.00]),
        (0.87, [1.00, 0.52, 0.00]),
        (0.88, [1.00, 0.47, 0.00]),
        (0.89, [1.00, 0.42, 0.00]),
        (0.90, [1.00, 0.38, 0.00]),
        (0.91, [1.00, 0.30, 0.00]),
        (0.92, [1.00, 0.22, 0.00]),
        (0.93, [0.95, 0.14, 0.00]),
        (0.94, [0.90, 0.06, 0.00]),
        (0.95, [0.79, 0.03, 0.00]),
        (0.96, [0.68, 0.00, 0.00]),
        (1.0, [0.55, 0.00, 0.00]),
    ];

    for window in stops.windows(2) {
        let (mut start_position, start_color) = window[0];
        let (mut end_position, end_color) = window[1];

        start_position = start_position.powi(4);
        end_position = end_position.powi(4);

        if position <= end_position {
            let range = end_position - start_position;

            let interpolation = if range == 0.0 {
                0.0
            } else {
                ((position - start_position) / range).clamp(0.0, 1.0)
            };

            let r = lerp(start_color[0], end_color[0], interpolation);
            let g = lerp(start_color[1], end_color[1], interpolation);
            let b = lerp(start_color[2], end_color[2], interpolation);

            return Color::srgb(r, g, b);
        }
    }

    Color::BLACK
}

fn lerp(start: f32, end: f32, amount: f32) -> f32 {
    start + (end - start) * amount
}
fn update_collapse_animations(time: Res<Time>, mut animations: ResMut<CollapseAnimations>) {
    for animation in &mut animations.active {
        animation.elapsed += time.delta_secs();
    }

    animations
        .active
        .retain(|animation| animation.elapsed < COLLAPSE_DURATION);
}
fn draw_collapse_animations(
    gizmos: &mut Gizmos,
    animations: &mut CollapseAnimations,
    hex_size: f32,
    grid_origin: Vec2,
) {
    for animation in &mut animations.active {
        let progress = (animation.elapsed / COLLAPSE_DURATION).clamp(0.0, 1.0);

        // Ease-out curve: fast at first, slower near the end.
        let eased = 1.0 - (1.0 - progress).powi(3);

        // The completed hex shrinks and fades away.
        let radius = HEX_SIZE * (1.0 - eased * 0.75);
        let alpha = 1.0 - eased;

        for (hex, color) in &animation.cells {
            draw_hex(
                gizmos,
                hex_to_world(*hex, hex_size, grid_origin),
                radius,
                color.with_alpha(alpha),
            );
        }

        // Pulse the resulting center hex after the collapse.
        let pulse_start = 0.55;

        if progress > pulse_start {
            let pulse_progress = ((progress - pulse_start) / (1.0 - pulse_start)).clamp(0.0, 1.0);

            let pulse = (pulse_progress * std::f32::consts::PI).sin();
            let pulse_radius = HEX_SIZE * (1.0 + pulse * 0.35);
            let pulse_alpha = pulse * 0.9;

            draw_hex(
                gizmos,
                hex_to_world(animation.center, hex_size, grid_origin),
                pulse_radius,
                Color::WHITE.with_alpha(pulse_alpha),
            );
        }
    }
}

fn handle_window_closed(
    state: Res<GameState>,
    mut closed: MessageReader<WindowClosed>,
    mut exit: MessageWriter<AppExit>,
) {
    for _ in closed.read() {
        let _ = match save_game(&state) {
            Ok(_) => {}
            Err(e) => println!("{}", e),
        };
        exit.write(AppExit::Success);
    }
}

fn save_game(state: &GameState) -> Result<(), Box<dyn std::error::Error>> {
    let save = GameSave::from_state(state);
    let json = serde_json::to_string_pretty(&save)?;
    fs::write("save.json", json)?;
    Ok(())
}

fn load_game() -> GameState {
    let json = match fs::read_to_string("save.json") {
        Ok(j) => j,
        Err(_) => String::new(),
    };
    let save = match serde_json::from_str(&json) {
        Ok(s) => s,
        Err(_) => GameSave {
            score: 0,
            cells: Vec::new(),
            pieces: vec![random_piece(), random_piece(), random_piece()],
        },
    };
    GameState::from_save(save)
}
fn button_hover_system(
    mut query: Query<
        (&Interaction, &mut BackgroundColor),
        (Changed<Interaction>, With<GameOverButton>),
    >,
) {
    for (interaction, mut bg_color) in &mut query {
        match *interaction {
            Interaction::Hovered => {
                *bg_color = BackgroundColor(Color::srgb(0.4, 0.4, 0.4));
            }
            Interaction::None => {
                *bg_color = BackgroundColor(Color::srgb(0.2, 0.2, 0.2));
            }
            Interaction::Pressed => {
                *bg_color = BackgroundColor(Color::srgb(0.1, 0.1, 0.1));
            }
        }
    }
}

fn reset_game(state: &mut GameState) {
    // Clear all game entities
    state.cells.clear();
    state.pieces = vec![random_piece(); 3];
    state.score = 0;
    state.phase = GamePhase::Playing;
}