use crate::{consts, core::event_bus::EventBus};
use godot::{
    classes::{Camera2D, RandomNumberGenerator},
    prelude::*,
};

#[derive(GodotClass)]
#[class(base=Node)]
pub struct CameraShakeSystem {
    #[export]
    camera: Option<Gd<Camera2D>>,

    #[export]
    decay_rate: f32,
    #[export]
    max_offset: f32,
    #[export]
    max_roll: f32,
    #[export]
    min_focus_for_shake: f32,

    trauma: f32,
    current_focus: f32,
    rng: Gd<RandomNumberGenerator>,
    event_bus: Option<Gd<EventBus>>,

    base: Base<Node>,
}

#[godot_api]
impl INode for CameraShakeSystem {
    fn init(base: Base<Node>) -> Self {
        Self {
            camera: None,
            decay_rate: 0.8,
            max_offset: 20.0,
            max_roll: 0.1,
            trauma: 0.0,
            min_focus_for_shake: 0.1,
            current_focus: 0.0,
            rng: RandomNumberGenerator::new_gd(),
            event_bus: None,
            base,
        }
    }

    fn ready(&mut self) {
        let mut bus = self.base().get_node_as::<EventBus>(consts::EVENT_BUS_PATH);

        bus.connect(
            consts::FOCUS_CHANGED,
            &self.base().callable("on_focus_changed"),
        );
        bus.connect(consts::CAMERA_TRAUMA, &self.base().callable("add_trauma"));
        bus.connect(consts::DAY_STARTED, &self.base().callable("on_day_started"));

        self.event_bus = Some(bus);
    }

    fn process(&mut self, delta: f64) {
        let dt = delta as f32;

        if self.current_focus > self.min_focus_for_shake {
            self.add_trauma(1.5 * dt);
        }

        let Some(camera) = &mut self.camera else {
            godot_error!("CameraShakeSystem: No camera assigned");
            return;
        };

        if self.trauma > 0.0 {
            self.trauma -= self.decay_rate * dt;
            self.trauma = self.trauma.max(0.0);

            let shake = self.trauma * self.trauma;

            let offset_x = self.max_offset * shake * self.rng.randf_range(-1.0, 1.0);
            let offset_y = self.max_offset * shake * self.rng.randf_range(-1.0, 1.0);
            let rotation = self.max_roll * shake * self.rng.randf_range(-1.0, 1.0);

            camera.set_offset(Vector2::new(offset_x, offset_y));
            camera.set_rotation(rotation);
        } else {
            if camera.get_offset() != Vector2::ZERO {
                let curr_off = camera.get_offset();
                let curr_rot = camera.get_rotation();
                camera.set_offset(curr_off.lerp(Vector2::ZERO, dt * 5.0));
                camera.set_rotation(curr_rot.lerp(0.0, dt * 5.0));
            }
        }
    }
}

#[godot_api]
impl CameraShakeSystem {
    #[func]
    fn on_focus_changed(&mut self, focus: f32) {
        self.current_focus = focus;
    }

    #[func]
    fn add_trauma(&mut self, amount: f32) {
        self.trauma += amount;
        self.trauma = self.trauma.clamp(0.0, 1.0);
    }

    #[func]
    fn on_day_started(&mut self, _day_index: i32) {
        self.trauma = 0.0;
        self.current_focus = 0.0;

        if let Some(camera) = &mut self.camera {
            camera.set_offset(Vector2::ZERO);
            camera.set_rotation(0.0);
        }
    }
}
