use glam::{Mat4, Vec3};
use std::path::Path;

use crate::components::lights::spot::SpotlightComponent;

pub struct LightData
{
    pub component: SpotlightComponent,
    pub transform: Mat4,
    pub tag: Option<String>,
}

pub struct Lights
{
    spotlights: Vec<LightData>,
}

impl Lights
{
    fn new(spotlights: Vec<LightData>) -> Self
    {
        Self { spotlights }
    }

    pub fn into_spotlights(self) -> Vec<LightData>
    {
        self.spotlights
    }

    pub fn load_gltf_lights(path: impl AsRef<Path>) -> Result<Lights, Box<dyn std::error::Error>>
    {
        let (gltf, _buffers, _images) = gltf::import(path)?;

        let mut all_directional = Vec::new();
        let mut all_point = Vec::new();
        let mut all_spot = Vec::new();

        for scene in gltf.scenes()
        {
            for node in scene.nodes()
            {
                Self::process_node(
                    &node,
                    Mat4::IDENTITY,
                    &mut all_directional,
                    &mut all_point,
                    &mut all_spot,
                )?;
            }
        }

        Ok(Lights::new(all_spot))
    }

    fn process_node(
        node: &gltf::Node,
        parent_transform: Mat4,
        all_directional: &mut Vec<LightData>,
        all_point: &mut Vec<LightData>,
        all_spot: &mut Vec<LightData>,
    ) -> Result<(), Box<dyn std::error::Error>>
    {
        let local_transform = Mat4::from_cols_array_2d(&node.transform().matrix());
        let global_transform = parent_transform * local_transform;

        if let Some(light) = node.light()
        {
            let local_transform = Mat4::from_cols_array_2d(&node.transform().matrix());
            let global_transform = parent_transform * local_transform;
            let (_scale, rotation, _translation) = global_transform.to_scale_rotation_translation();

            let tag = serde_json::to_value(light.extras())
                .ok()
                .and_then(|extras| {
                    extras
                        .get("light_tag")
                        .and_then(|v| v.as_str())
                        .map(String::from)
                });

            match light.kind()
            {
                gltf::khr_lights_punctual::Kind::Directional => todo!(),
                gltf::khr_lights_punctual::Kind::Point => todo!(),
                gltf::khr_lights_punctual::Kind::Spot {
                    inner_cone_angle,
                    outer_cone_angle,
                } =>
                {
                    let range = light.range().unwrap_or(100.0);
                    let spotlight = SpotlightComponent::new(
                        Vec3::ZERO,
                        rotation * -Vec3::Z,
                        range,
                        inner_cone_angle,
                        outer_cone_angle,
                    );
                    all_spot.push(LightData {
                        component: spotlight,
                        transform: global_transform,
                        tag,
                    });
                }
            }
        }

        for child in node.children()
        {
            Self::process_node(
                &child,
                global_transform,
                all_directional,
                all_point,
                all_spot,
            )?;
        }

        Ok(())
    }

    pub fn load_lights(path: impl AsRef<Path>) -> Result<Lights, Box<dyn std::error::Error>>
    {
        crate::render::with_device(|_device| Lights::load_gltf_lights(path))
    }
}
