use godot::{
    classes::{ColorRect, ShaderMaterial},
    prelude::*,
};

use crate::{consts, core::event_bus::EventBus, data::tunnel_config::TunnelConfig};

#[derive(GodotClass)]
#[class(base=Node)]
pub struct TunnelSystem {
    #[export]
    config: Option<Gd<TunnelConfig>>,
    #[export]
    vignette_overlay: Option<Gd<ColorRect>>,

    current_focus: f32,
    is_efforting: bool,
    event_bus: Option<Gd<EventBus>>,
    base: Base<Node>,
}

#[godot_api]
impl INode for TunnelSystem {
    fn init(base: Base<Node>) -> Self {
        Self {
            config: None,
            vignette_overlay: None,
            current_focus: 0.0,
            is_efforting: false,
            event_bus: None,
            base,
        }
    }

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

        bus.connect(
            consts::LIFT_EFFORT_APPLIED,
            &self.base().callable("on_lift_effort"),
        );
        bus.connect(
            consts::FOCUS_RELEASED,
            &self.base().callable("on_focus_release"),
        );
        bus.connect(consts::DAY_STARTED, &self.base().callable("on_day_started"));

        self.event_bus = Some(bus);
    }

    fn process(&mut self, delta: f64) {
        let Some(bus) = &mut self.event_bus else {
            return;
        };

        let Some(config) = &self.config else {
            return;
        };

        let config_bind = config.bind();
        let dt = delta as f32;

        if self.is_efforting {
            self.current_focus += config_bind.vision_narrow_rate * dt;
        } else {
            self.current_focus -= config_bind.vision_recover_rate * dt;
        }

        let mut new_focus = self.current_focus;

        if self.is_efforting {
            new_focus += config_bind.vision_narrow_rate * dt;
        } else {
            new_focus -= config_bind.vision_recover_rate * dt;
        }

        new_focus = new_focus.clamp(0.0, 1.0);

        let max_intensity = config_bind.max_tunnel_intensity;

        let visual_raw = if let Some(curve) = &config_bind.vision_curve {
            curve.sample(new_focus)
        } else {
            new_focus
        };

        let visual_value = visual_raw * max_intensity;

        if let Some(overlay) = &mut self.vignette_overlay {
            if let Some(material) = overlay
                .get_material()
                .and_then(|m| Some(m.try_cast::<ShaderMaterial>()))
            {
                if let Ok(mut mat) = material {
                    mat.set_shader_parameter(
                        consts::VIGNETTE_INTENSITY_PARAM,
                        &visual_value.to_variant(),
                    );
                }
            }
        }

        bus.bind_mut().publish_focus_changed(new_focus);
    }
}

#[godot_api]
impl TunnelSystem {
    #[func]
    fn on_lift_effort(&mut self, _strength: f32) {
        self.is_efforting = true;
    }

    #[func]
    fn on_focus_release(&mut self) {
        self.is_efforting = false;
    }

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

        if let Some(overlay) = &mut self.vignette_overlay {
            if let Some(material) = overlay.get_material() {
                if let Ok(mut mat) = material.try_cast::<ShaderMaterial>() {
                    mat.set_shader_parameter(consts::VIGNETTE_INTENSITY_PARAM, &0.0.to_variant());
                }
            }
        }

        if let Some(bus) = &mut self.event_bus {
            bus.bind_mut().publish_focus_changed(0.0);
        }
    }
}
