use bevy::prelude::*;
use bevy_ecs_tilemap::prelude::*;
use serde::{Deserialize, Serialize};

use super::camera;

pub struct GridPlugin;

#[derive(Debug, Component, Reflect, Serialize, Deserialize)]
#[reflect(Component)]
pub struct Tile {
    pub x: isize,
    pub y: isize,
}

#[derive(Debug, Component, Reflect, Serialize, Deserialize)]
#[reflect(Component)]
pub struct Flag;

#[derive(Debug, Component, Reflect, Serialize, Deserialize)]
#[reflect(Component)]
pub struct Revealed;

#[derive(Debug, Component, Reflect, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)]
#[reflect(Component)]
pub enum TileType {
    Empty,
    One,
    Two,
    Three,
    Four,
    Five,
    Six,
    Seven,
    Eight,
    Mine,
}

impl From<i32> for TileType {
    fn from(value: i32) -> Self {
        match value {
            1 => TileType::One,
            2 => TileType::Two,
            3 => TileType::Three,
            4 => TileType::Four,
            5 => TileType::Five,
            6 => TileType::Six,
            7 => TileType::Seven,
            8 => TileType::Eight,
            _ => TileType::Empty,
        }
    }
}

#[derive(Debug, Component, Reflect, Serialize, Deserialize)]
#[reflect(Component)]
pub struct TileOffset {
    pub translation: Vec3,
    pub x: isize,
    pub y: isize,
}

#[derive(Resource)]
pub struct CursorPos(Vec2);
impl Default for CursorPos {
    fn default() -> Self {
        Self(Vec2::new(-1000.0, -1000.0))
    }
}

#[derive(Debug, Event)]
pub struct TileClickEvent {
    pub x: isize,
    pub y: isize,
    pub button: MouseButton,
}

impl Plugin for GridPlugin {
    fn build(&self, app: &mut App) {
        app.register_type::<Tile>();
        app.register_type::<Flag>();
        app.register_type::<Revealed>();
        app.register_type::<TileType>();

        app.add_plugins(TilemapPlugin);
        app.init_resource::<CursorPos>();
        app.add_event::<TileClickEvent>();
        app.add_systems(Startup, generate_grid);
        app.add_systems(
            First,
            (
                camera::mouse_movement,
                camera::mouse_zoom,
                update_cursor_pos,
                click_tile,
            )
                .chain(),
        );
        app.add_systems(Update, update_tiles);
    }
}

fn generate_grid(mut commands: Commands, asset_server: Res<AssetServer>) {
    let texture_handle: Handle<Image> = asset_server.load("tiles.png");

    let map_size = TilemapSize { x: 82, y: 47 };
    let mut tile_storage = TileStorage::empty(map_size);
    let tilemap_entity = commands.spawn_empty().id();

    for x in 0..map_size.x {
        for y in 0..map_size.y {
            let tile_pos = TilePos { x, y };
            let tile_entity = commands
                .spawn(TileBundle {
                    position: tile_pos,
                    tilemap_id: TilemapId(tilemap_entity),
                    visible: TileVisible(true),
                    texture_index: TileTextureIndex(1),
                    ..Default::default()
                })
                .id();
            tile_storage.set(&tile_pos, tile_entity);
        }
    }

    let tile_size = TilemapTileSize { x: 16.0, y: 16.0 };
    let grid_size = TilemapTileSize { x: 16.0, y: 16.0 }.into();
    let map_type = TilemapType::Square;

    commands.entity(tilemap_entity).insert(TilemapBundle {
        grid_size,
        map_type,
        size: map_size,
        storage: tile_storage,
        texture: TilemapTexture::Single(texture_handle),
        tile_size,
        transform: get_tilemap_center_transform(&map_size, &grid_size, &map_type, 0.0),
        ..Default::default()
    });
}

fn click_tile(
    buttons: Res<ButtonInput<MouseButton>>,
    cursor_pos: Res<CursorPos>,
    tilemap_q: Query<(&TilemapSize, &TilemapGridSize, &TileStorage, &Transform)>,
    offset: Query<&TileOffset, With<Camera>>,
    mut ev_tile_click: EventWriter<TileClickEvent>,
) {
    if !buttons.just_pressed(MouseButton::Left) && !buttons.just_pressed(MouseButton::Right) {
        return;
    }

    let offset = offset.single();

    for (map_size, grid_size, tile_storage, map_transform) in tilemap_q.iter() {
        let cursor_pos: Vec2 = cursor_pos.0;
        let cursor_in_map_pos: Vec2 = {
            let cursor_pos = Vec4::from((cursor_pos, 0.0, 1.0));
            let cursor_in_map_pos = map_transform.compute_matrix().inverse() * cursor_pos;
            cursor_in_map_pos.xy()
        };

        let Some(tile_pos) = TilePos::from_world_pos(
            &cursor_in_map_pos,
            map_size,
            grid_size,
            &TilemapType::Square,
        ) else {
            continue;
        };

        if tile_storage.get(&tile_pos).is_none() {
            continue;
        };

        ev_tile_click.send(TileClickEvent {
            x: tile_pos.x as isize + offset.x,
            y: tile_pos.y as isize + offset.y,
            button: if buttons.just_pressed(MouseButton::Left) {
                MouseButton::Left
            } else {
                MouseButton::Right
            },
        });
    }
}

fn update_tiles(
    tiles: Query<(Entity, &Tile, &TileType)>,
    tile_flag: Query<&Flag>,
    tile_revealed: Query<&Revealed>,
    tilemap_q: Query<&TileStorage>,
    mut texture_index: Query<&mut TileTextureIndex>,
    offset: Query<&TileOffset, With<Camera>>,
) {
    let offset = offset.single();

    for mut index in texture_index.iter_mut() {
        *index = TileTextureIndex(1);
    }

    for (ent, tile, kind) in tiles.iter() {
        if (tile.x - offset.x) < 0 || (tile.y - offset.y) < 0 {
            continue;
        }

        let x = tile.x - offset.x;
        let y = tile.y - offset.y;

        if x < 0 || y < 0 {
            continue;
        }

        if x > 81 || y > 46 {
            continue;
        }

        let tile_pos = TilePos {
            x: x as u32,
            y: y as u32,
        };

        for tile_storage in tilemap_q.iter() {
            let Some(tile_entity) = tile_storage.get(&tile_pos) else {
                continue;
            };

            let Ok(mut index) = texture_index.get_mut(tile_entity) else {
                continue;
            };

            if tile_flag.get(ent).is_ok() {
                *index = TileTextureIndex(11);
                continue;
            }

            if tile_revealed.get(ent).is_err() {
                *index = TileTextureIndex(1);
                continue;
            }

            *index = TileTextureIndex(match kind {
                TileType::One => 2,
                TileType::Empty => 0,
                TileType::Two => 3,
                TileType::Three => 4,
                TileType::Four => 5,
                TileType::Five => 6,
                TileType::Six => 7,
                TileType::Seven => 8,
                TileType::Eight => 9,
                TileType::Mine => 10,
            });
        }
    }
}

pub fn update_cursor_pos(
    camera_q: Query<(&GlobalTransform, &Camera)>,
    mut cursor_moved_events: EventReader<CursorMoved>,
    mut cursor_pos: ResMut<CursorPos>,
) {
    for cursor_moved in cursor_moved_events.read() {
        for (cam_t, cam) in camera_q.iter() {
            if let Some(pos) = cam.viewport_to_world_2d(cam_t, cursor_moved.position) {
                *cursor_pos = CursorPos(pos);
            }
        }
    }
}
