use bevy::prelude::*;
use bevy::window::{CursorGrabMode, CursorOptions, PrimaryWindow};
use bevy_egui::PrimaryEguiContext;
use game::player::PlayerCamera;
use game::{GameInputEnabled, GameInputFocused, SimEnabled};
use protocol::PlayerInputIntent;
use sim::{Crouching, Grounded, JumpState, Player, PlayerVelocity};

use crate::camera::EditorCamera;

/// Editor/game mode. `Playing` runs the real game sim; use [`PlayPossession`] for camera control.
#[derive(States, Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum EditorMode {
    #[default]
    Editing,
    Playing,
}

/// FPS vs editor fly camera while sim runs in Play mode.
#[derive(Resource, Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PlayPossession {
    #[default]
    Possessed,
    Ejected,
}

/// Player/sim state captured on play enter, restored on play stop.
#[derive(Resource, Default, Clone)]
pub struct PlayModeSnapshot {
    pub player: Option<PlayerSimSnapshot>,
}

#[derive(Clone)]
pub struct PlayerSimSnapshot {
    pub transform: Transform,
    pub velocity: Vec3,
    pub intent: PlayerInputIntent,
    pub jump_coyote: f32,
    pub jump_buffer: f32,
    pub grounded: bool,
    pub crouching: bool,
    pub camera_local: Transform,
}

/// When true, Play mode is active but the fixed-step sim is frozen.
#[derive(Resource, Debug, Clone, Copy, Default)]
pub struct PlayPaused(pub bool);

/// Request one fixed-step sim tick while paused in Play mode (H3).
#[derive(Resource, Debug, Default)]
pub struct PlayStepOnce(pub bool);

pub struct EditorStatePlugin;

impl Plugin for EditorStatePlugin {
    fn build(&self, app: &mut App) {
        app.init_state::<EditorMode>()
            .init_resource::<PlayPossession>()
            .init_resource::<PlayPaused>()
            .init_resource::<PlayStepOnce>()
            .init_resource::<PlayModeSnapshot>()
            .add_systems(PostStartup, init_player_camera_for_editor)
            .add_systems(
                Update,
                (
                    toggle_editor_mode,
                    toggle_play_pause,
                    toggle_play_step,
                    toggle_play_possession,
                    sync_mode_resources_and_cameras,
                    sync_play_input_focus,
                )
                    .chain(),
            )
            .add_systems(
                OnEnter(EditorMode::Playing),
                (
                    enter_play_mode,
                    reset_possession,
                    reset_play_paused,
                    grab_play_cursor,
                ),
            )
            .add_systems(
                OnEnter(EditorMode::Editing),
                (exit_play_mode, reset_play_paused, release_play_cursor),
            );
    }
}

/// Scene tools (selection, gizmos, undo, fly cam) are active in Edit mode or ejected PIE.
pub fn scene_tools_active(mode: Res<State<EditorMode>>, possession: Res<PlayPossession>) -> bool {
    match mode.get() {
        EditorMode::Editing => true,
        EditorMode::Playing => *possession == PlayPossession::Ejected,
    }
}

fn toggle_editor_mode(
    keys: Res<ButtonInput<KeyCode>>,
    state: Res<State<EditorMode>>,
    mut next: ResMut<NextState<EditorMode>>,
) {
    if keys.just_pressed(KeyCode::F5) {
        next.set(match state.get() {
            EditorMode::Editing => EditorMode::Playing,
            EditorMode::Playing => EditorMode::Editing,
        });
    }
}

fn toggle_play_possession(
    keys: Res<ButtonInput<KeyCode>>,
    mode: Res<State<EditorMode>>,
    mut possession: ResMut<PlayPossession>,
    mut cursor: Single<&mut CursorOptions, With<PrimaryWindow>>,
    mut editor_cameras: Query<&mut Transform, With<EditorCamera>>,
    player: Query<&GlobalTransform, With<Player>>,
    player_cameras: Query<&GlobalTransform, With<PlayerCamera>>,
) {
    if *mode.get() != EditorMode::Playing || !keys.just_pressed(KeyCode::F8) {
        return;
    }

    match *possession {
        PlayPossession::Possessed => {
            *possession = PlayPossession::Ejected;
            if let Ok(camera_gt) = player_cameras.single() {
                for mut editor_transform in &mut editor_cameras {
                    *editor_transform = Transform {
                        translation: camera_gt.translation(),
                        rotation: camera_gt.rotation(),
                        ..default()
                    };
                }
            } else if let Ok(player_gt) = player.single() {
                for mut editor_transform in &mut editor_cameras {
                    editor_transform.translation = player_gt.translation();
                }
            }
            release_play_cursor_internal(&mut cursor);
        }
        PlayPossession::Ejected => {
            *possession = PlayPossession::Possessed;
            grab_play_cursor_internal(&mut cursor);
        }
    }
}

fn toggle_play_pause(
    keys: Res<ButtonInput<KeyCode>>,
    mode: Res<State<EditorMode>>,
    mut paused: ResMut<PlayPaused>,
) {
    if *mode.get() != EditorMode::Playing || !keys.just_pressed(KeyCode::F6) {
        return;
    }
    paused.0 = !paused.0;
}

fn toggle_play_step(
    keys: Res<ButtonInput<KeyCode>>,
    mode: Res<State<EditorMode>>,
    paused: Res<PlayPaused>,
    mut step: ResMut<PlayStepOnce>,
) {
    if *mode.get() != EditorMode::Playing || !paused.0 || !keys.just_pressed(KeyCode::F7) {
        return;
    }
    step.0 = true;
}

fn reset_play_paused(mut paused: ResMut<PlayPaused>) {
    paused.0 = false;
}

fn reset_possession(mut possession: ResMut<PlayPossession>) {
    *possession = PlayPossession::Possessed;
}

pub(crate) fn init_player_camera_for_editor(
    mut player_cameras: Query<&mut Camera, (With<PlayerCamera>, Without<EditorCamera>)>,
) {
    for mut camera in &mut player_cameras {
        camera.order = 1;
        camera.is_active = false;
    }
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn sync_mode_resources_and_cameras(
    state: Res<State<EditorMode>>,
    possession: Res<PlayPossession>,
    paused: Res<PlayPaused>,
    mut step: ResMut<PlayStepOnce>,
    mut input_enabled: ResMut<GameInputEnabled>,
    mut sim_enabled: ResMut<SimEnabled>,
    mut editor_cameras: Query<&mut Camera, With<EditorCamera>>,
    mut player_cameras: Query<&mut Camera, (With<PlayerCamera>, Without<EditorCamera>)>,
    mut egui_cameras: Query<
        &mut Camera,
        (
            With<PrimaryEguiContext>,
            Without<EditorCamera>,
            Without<PlayerCamera>,
        ),
    >,
    mut last_mode: Local<Option<(EditorMode, PlayPossession)>>,
) {
    let mode = *state.get();
    let playing = mode == EditorMode::Playing;
    let ejected = playing && *possession == PlayPossession::Ejected;
    let possessed = playing && *possession == PlayPossession::Possessed;

    sim_enabled.0 = playing && (!paused.0 || step.0);
    input_enabled.0 = possessed && !paused.0;
    if step.0 {
        step.0 = false;
    }

    for mut camera in &mut editor_cameras {
        camera.is_active = !playing || ejected;
    }
    for mut camera in &mut player_cameras {
        camera.is_active = possessed;
    }

    for mut camera in &mut egui_cameras {
        camera.clear_color = if !playing || ejected {
            ClearColorConfig::None
        } else {
            ClearColorConfig::Custom(Color::BLACK)
        };
    }

    let key = (mode, *possession);
    if *last_mode != Some(key) {
        *last_mode = Some(key);
        info!("EditorMode -> {mode:?}, possession -> {possession:?}");
    }
}

fn sync_play_input_focus(
    mode: Res<State<EditorMode>>,
    possession: Res<PlayPossession>,
    paused: Res<PlayPaused>,
    mut focused: ResMut<GameInputFocused>,
) {
    focused.0 =
        *mode.get() == EditorMode::Playing && *possession == PlayPossession::Possessed && !paused.0;
}

fn enter_play_mode(world: &mut World) {
    // Edit-mode scrubbing must not override authored controller playback in PIE.
    game::animation::stop_animation_preview(world);
    let snapshot = snapshot_player_sim(world);
    world.resource_mut::<PlayModeSnapshot>().player = Some(snapshot);
}

fn exit_play_mode(world: &mut World) {
    if let Some(snapshot) = world.resource_mut::<PlayModeSnapshot>().player.take() {
        restore_player_sim(world, &snapshot);
    }
    world.resource_mut::<SimEnabled>().0 = false;
    game::animation::reset_authored_animation_runtime(world);
    game::audio::reset_authored_audio_runtime(world);
}

fn snapshot_player_sim(world: &mut World) -> PlayerSimSnapshot {
    let player_data = {
        let mut player_query = world.query_filtered::<(
            &Transform,
            &PlayerVelocity,
            &PlayerInputIntent,
            &JumpState,
            &Grounded,
            &Crouching,
        ), With<Player>>();

        player_query.iter(world).next().map(
            |(transform, velocity, intent, jump, grounded, crouching)| {
                (
                    *transform,
                    velocity.0,
                    *intent,
                    jump.coyote,
                    jump.buffer,
                    grounded.0,
                    crouching.0,
                )
            },
        )
    };

    let Some((transform, velocity, intent, jump_coyote, jump_buffer, grounded, crouching)) =
        player_data
    else {
        return PlayerSimSnapshot {
            transform: Transform::default(),
            velocity: Vec3::ZERO,
            intent: PlayerInputIntent::default(),
            jump_coyote: 0.0,
            jump_buffer: 0.0,
            grounded: false,
            crouching: false,
            camera_local: Transform::IDENTITY,
        };
    };

    let camera_local = {
        let mut camera_query = world.query_filtered::<&Transform, With<PlayerCamera>>();
        camera_query
            .iter(world)
            .next()
            .copied()
            .unwrap_or(Transform::IDENTITY)
    };

    PlayerSimSnapshot {
        transform,
        velocity,
        intent,
        jump_coyote,
        jump_buffer,
        grounded,
        crouching,
        camera_local,
    }
}

fn restore_player_sim(world: &mut World, snapshot: &PlayerSimSnapshot) {
    let mut player_query = world.query_filtered::<(
        &mut Transform,
        &mut PlayerVelocity,
        &mut PlayerInputIntent,
        &mut JumpState,
        &mut Grounded,
        &mut Crouching,
    ), With<Player>>();

    if let Some((mut transform, mut velocity, mut intent, mut jump, mut grounded, mut crouching)) =
        player_query.iter_mut(world).next()
    {
        *transform = snapshot.transform;
        velocity.0 = snapshot.velocity;
        *intent = snapshot.intent;
        *jump = JumpState {
            coyote: snapshot.jump_coyote,
            buffer: snapshot.jump_buffer,
        };
        grounded.0 = snapshot.grounded;
        crouching.0 = snapshot.crouching;
    }

    let mut camera_query = world.query_filtered::<&mut Transform, With<PlayerCamera>>();
    for mut camera in camera_query.iter_mut(world) {
        *camera = snapshot.camera_local;
    }
}

fn grab_play_cursor(mut cursor: Single<&mut CursorOptions, With<PrimaryWindow>>) {
    grab_play_cursor_internal(&mut cursor);
}

fn release_play_cursor(mut cursor: Single<&mut CursorOptions, With<PrimaryWindow>>) {
    release_play_cursor_internal(&mut cursor);
}

fn grab_play_cursor_internal(cursor: &mut CursorOptions) {
    cursor.grab_mode = CursorGrabMode::Locked;
    cursor.visible = false;
}

fn release_play_cursor_internal(cursor: &mut CursorOptions) {
    cursor.grab_mode = CursorGrabMode::None;
    cursor.visible = true;
}

/// Toggles possess/eject from UI (mirrors F8 handling in [`toggle_play_possession`]).
pub fn toggle_play_possession_world(world: &mut World) {
    if *world.resource::<State<EditorMode>>().get() != EditorMode::Playing {
        return;
    }
    let next = match *world.resource::<PlayPossession>() {
        PlayPossession::Possessed => PlayPossession::Ejected,
        PlayPossession::Ejected => PlayPossession::Possessed,
    };
    *world.resource_mut::<PlayPossession>() = next;

    let mut cursor = world
        .query_filtered::<&mut CursorOptions, With<PrimaryWindow>>()
        .single_mut(world)
        .expect("primary window cursor");

    match next {
        PlayPossession::Possessed => grab_play_cursor_internal(&mut cursor),
        PlayPossession::Ejected => {
            release_play_cursor_internal(&mut cursor);
            let camera_pose = world
                .query_filtered::<&GlobalTransform, With<PlayerCamera>>()
                .single(world)
                .ok()
                .map(|gt| (gt.translation(), gt.rotation()));
            if let Some((translation, rotation)) = camera_pose {
                let mut editor_query = world.query_filtered::<&mut Transform, With<EditorCamera>>();
                for mut editor_transform in editor_query.iter_mut(world) {
                    *editor_transform = Transform {
                        translation,
                        rotation,
                        ..default()
                    };
                }
            }
        }
    }
}
