//! Starter objects like planes and primitive objects are that made during runtime with
//! vertices rather than from a model.

use crate::asset::{AssetRegistry, Handle};
use crate::buffer::DynamicBuffer;
use crate::graphics::SharedGraphicsContext;
use crate::model::ModelVertex;
use crate::model::{Material, Mesh, Model};
use crate::utils::ResourceReference;
use parking_lot::RwLock;
use rkyv::Archive;
use serde::{Deserialize, Serialize};
use std::hash::{DefaultHasher, Hasher};
use std::sync::Arc;
use wgpu::util::DeviceExt;

pub mod cube;

#[derive(
    Clone,
    Debug,
    PartialEq,
    Eq,
    Hash,
    Serialize,
    Deserialize,
    Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub enum ProcObjType {
    Cuboid,
}

/// An object that comes with a template, and is generated through parameter input.
#[derive(
    Clone,
    Debug,
    PartialEq,
    Eq,
    Hash,
    Serialize,
    Deserialize,
    Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub struct ProcedurallyGeneratedObject {
    pub vertices: Vec<ModelVertex>,
    pub indices: Vec<u32>,
    pub ty: ProcObjType,
}

impl ProcedurallyGeneratedObject {
    /// Constructs a [`Model`] and returns the model itself instead of adding to the registry.
    pub fn construct(
        &self,
        graphics: Arc<SharedGraphicsContext>,
        material: Option<Material>,
        label: Option<&str>,
        hash: Option<u64>,
        registry: Arc<RwLock<AssetRegistry>>,
    ) -> Model {
        let hash = if let Some(hash) = hash {
            hash
        } else {
            let mut hasher = DefaultHasher::new();
            hasher.write(bytemuck::cast_slice(&self.vertices));
            hasher.write(bytemuck::cast_slice(&self.indices));
            hasher.finish()
        };

        let label = label
            .map(|s| s.to_string())
            .unwrap_or_else(|| format!("procedural_{hash:016x}"));

        let vertices = self.vertices.clone();
        let indices = self.indices.clone();

        let vertex_buffer = DynamicBuffer::from_slice(
            &graphics.device,
            &vertices,
            wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
            &format!("{label} Vertex Buffer"),
        );

        let index_buffer = DynamicBuffer::from_slice(
            &graphics.device,
            &indices,
            wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
            &format!("{label} Index Buffer"),
        );

        let mesh = Mesh {
            name: label.clone(),
            vertex_buffer,
            index_buffer,
            num_elements: indices.len() as u32,
            material: 0,
            morph_deltas_offset: 0,
            morph_target_count: 0,
            morph_vertex_count: self.vertices.len() as u32,
            morph_default_weights: Vec::new(),
        };

        let material = material.unwrap_or_else(|| {
            let mut _rguard = registry.write();
            let white_srgb_texture =
                _rguard.solid_texture_rgba8(graphics.clone(), [255, 255, 255, 255], None);

            Material::new(
                &mut _rguard,
                graphics.clone(),
                "procedural_material",
                white_srgb_texture,
                None,
                None,
                None,
                None,
                [1.0, 1.0, 1.0, 1.0],
                Some("procedural_material".to_string()),
            )
        });

        let model = Model {
            label: label.clone(),
            hash,
            path: ResourceReference::Procedural(self.clone()),
            meshes: vec![mesh],
            materials: vec![material],
            skins: Vec::new(),
            animations: Vec::new(),
            nodes: Vec::new(),
            morph_deltas_buffer: None,
        };

        model
    }

    /// Builds a GPU-backed model from this procedural mesh.
    ///
    /// - `material`: optional material; when `None`, a cached 1x1 grey texture is used.
    /// - `label`: optional cache label; when `None`, a stable hash-based label is generated.
    pub fn build_model(
        &self,
        graphics: Arc<SharedGraphicsContext>,
        material: Option<Material>,
        label: Option<&str>,
        registry: Arc<RwLock<AssetRegistry>>,
    ) -> Handle<Model> {
        puffin::profile_function!();
        let mut hasher = DefaultHasher::new();
        hasher.write(bytemuck::cast_slice(&self.vertices));
        hasher.write(bytemuck::cast_slice(&self.indices));
        let hash = hasher.finish();

        let label_str = label
            .map(|s| s.to_string())
            .unwrap_or_else(|| format!("procedural_{hash:016x}"));

        {
            let mut _rguard = registry.read();

            if let Some(handle) = _rguard.model_handle_by_hash(hash) {
                return handle;
            }
        }

        let model = Self::construct(
            &self,
            graphics,
            material,
            label,
            Some(hash),
            registry.clone(),
        );

        {
            let mut _rguard = registry.write();
            _rguard.add_model_with_label(label_str, model)
        }
    }
}
