use std::time::Duration;

use crate::{LOGICAL_HEIGHT, LOGICAL_WIDTH};

use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts};
use bevy_smooth_pixel_camera::components::PixelCamera;
use bevy_smooth_pixel_camera::viewport::ViewportSize;

pub struct CameraPlugin;

impl Plugin for CameraPlugin {
    fn build(&self, app: &mut App) {
        app.add_plugins(bevy_smooth_pixel_camera::PixelCameraPlugin)
            .init_resource::<CameraRig>()
            .add_systems(Startup, setup_camera)
            .add_systems(Update, (update_camera, camera_rig_debug));
    }
}

#[derive(Resource)]
pub struct CameraRig {
    /// Which [`CameraTarget`](s) does the camera target?
    pub targetting: usize,
    precision: f32,
    /// how long should the lerp take?
    pub snap_duration: Duration,
}

impl CameraRig {
    /// Camera lag - [0.0, 1.0]
    ///
    /// The camera will nominally be within this percentage of the
    /// target within [`self::snap_duration`]
    pub fn lag(&self) -> f32 {
        self.precision
    }

    pub fn set_lag(&mut self, lag: f32) {
        self.precision = lag.clamp(0.0, 1.0);
    }
}

impl Default for CameraRig {
    fn default() -> Self {
        Self {
            targetting: 0,
            precision: 0.7,
            snap_duration: Duration::from_secs_f32(10f32.recip()),
        }
    }
}

/// The maximum value used for [`CameraRig::targetting`]
pub const MAX_TARGET: usize = 3;

fn camera_rig_debug(mut rig: ResMut<CameraRig>, mut contexts: EguiContexts) {
    egui::Window::new("Camera Rig").show(contexts.ctx_mut(), |ui| {
        ui.horizontal(|ui| {
            ui.label("Targetting");
            ui.add(egui::Slider::new(&mut rig.targetting, 0..=MAX_TARGET));
        });
        ui.horizontal(|ui| {
            ui.label("Precision");
            ui.add(egui::Slider::new(&mut rig.precision, 0.0..=1.0));
        });
        ui.horizontal(|ui| {
            ui.label("Snap Inverse Halflife");
            let mut recip_halflife = rig.snap_duration.as_secs_f32().recip();
            if ui
                .add(egui::Slider::new(&mut recip_halflife, 1.0..=100.0))
                .changed()
            {
                rig.snap_duration = Duration::from_secs_f32(recip_halflife.recip());
            }
        });
    });
}

/// Attach this to something to make it the target of the camera
#[derive(Component, Default)]
pub struct CameraTarget(pub usize);

#[derive(Component, Default)]
struct Camera;

#[derive(Bundle)]
struct CameraBundle {
    camera: Camera,
    camera_2d: Camera2dBundle,
    pixel_camera: PixelCamera,
    // chroma_aberration: post_process::ChromaticAberattionSettings,
}

fn setup_camera(mut commands: Commands) {
    commands.insert_resource(ClearColor(Color::rgb(0.07, 0.05, 0.05)));
    commands.spawn(CameraBundle {
        camera: Camera,
        camera_2d: Camera2dBundle::default(),
        pixel_camera: PixelCamera::from_size(ViewportSize::AutoMax {
            max_width: LOGICAL_WIDTH,
            max_height: LOGICAL_HEIGHT,
        }),
        // chroma_aberration: post_process::ChromaticAberattionSettings {
        //     // intensity: 0.02,
        //     ..default()
        // },
    });
}

fn update_camera(
    mut camera: Query<(&mut PixelCamera, &GlobalTransform), With<Camera>>,
    targets: Query<(&GlobalTransform, &CameraTarget)>,
    rig: Res<CameraRig>,
    time: Res<Time>,
) {
    // This is only 1 camera...
    let (mut camera, cam_transform) = camera.single_mut();
    // ...but soooo many targets
    let targeted = targets
        .iter()
        .filter(|(_, t)| t.0 == rig.targetting)
        .collect::<Vec<_>>();
    // Get the midpoint of all targeted items
    let goal = targeted
        .iter()
        .map(|(transform, _)| transform.translation())
        .reduce(|total, e| total + e)
        .map(|total| total / targeted.len() as f32);
    if let Some(goal) = goal {
        // Lerp the camera to targets's global translation point
        let source = cam_transform.translation();
        // Taken from https://mastodon.social/@acegikmo/111931613710775864
        let lambda = -rig.snap_duration.as_secs_f32() / rig.precision.log2();
        let alpha = 2.0f32.powf(-time.delta_seconds() / lambda);
        let lerped = source * alpha + goal * (1.0 - alpha);
        camera.subpixel_pos = lerped.xy();
    }
}