use std::collections::HashSet;

use nalgebra_glm::Vec3;

use super::bvh::SdfEditBvh;
use super::clipmap::{BASE_VOXEL_SIZE, DEFAULT_LEVEL_COUNT, SdfClipmap};
use super::evaluation::{GpuBrickDispatch, prepare_gpu_brick_dispatches};
use super::primitives::{Aabb, SdfEdit, SdfPrimitive};

pub fn terrain_hash(ix: i32, iz: i32, seed: u32) -> f32 {
    let mut n =
        (ix.wrapping_mul(1619)) ^ (iz.wrapping_mul(6971)) ^ ((seed as i32).wrapping_mul(1013));
    n = n.wrapping_mul(n).wrapping_mul(n);
    n = n ^ (n >> 13);
    (n & 0x7fffffff) as f32 / 2147483647.0
}

pub fn noised_2d(px: f32, pz: f32, seed: u32) -> (f32, f32, f32) {
    let cell_x = px.floor() as i32;
    let cell_z = pz.floor() as i32;

    let wx = px - cell_x as f32;
    let wz = pz - cell_z as f32;

    let ux = wx * wx * wx * (wx * (wx * 6.0 - 15.0) + 10.0);
    let uz = wz * wz * wz * (wz * (wz * 6.0 - 15.0) + 10.0);

    let dux = 30.0 * wx * wx * (wx * (wx - 2.0) + 1.0);
    let duz = 30.0 * wz * wz * (wz * (wz - 2.0) + 1.0);

    let h00 = terrain_hash(cell_x, cell_z, seed);
    let h10 = terrain_hash(cell_x + 1, cell_z, seed);
    let h01 = terrain_hash(cell_x, cell_z + 1, seed);
    let h11 = terrain_hash(cell_x + 1, cell_z + 1, seed);

    let k0 = h00;
    let k1 = h10 - h00;
    let k2 = h01 - h00;
    let k3 = h00 - h10 - h01 + h11;

    let value = -1.0 + 2.0 * (k0 + k1 * ux + k2 * uz + k3 * ux * uz);
    let dvdx = 2.0 * dux * (k1 + k3 * uz);
    let dvdz = 2.0 * duz * (k2 + k3 * ux);

    (value, dvdx, dvdz)
}

pub fn terrain_fbm(config: &TerrainConfig, point: Vec3) -> f32 {
    let mut px = point.x * config.frequency;
    let mut pz = point.z * config.frequency;
    let mut total = 0.0_f32;
    let mut amplitude = config.amplitude;
    let mut dx_sum = 0.0_f32;
    let mut dz_sum = 0.0_f32;

    for _octave in 0..config.octaves {
        let (value, dvdx, dvdz) = noised_2d(px, pz, config.seed);
        dx_sum += dvdx * 0.5;
        dz_sum += dvdz * 0.5;
        let dampening = 1.0 / (1.0 + dx_sum * dx_sum + dz_sum * dz_sum);
        total += amplitude * value * dampening;
        let new_px = 1.6 * px - 1.2 * pz;
        let new_pz = 1.2 * px + 1.6 * pz;
        px = new_px;
        pz = new_pz;
        amplitude *= config.gain;
    }

    point.y - config.base_height - total
}

#[derive(Clone, Copy)]
pub struct TerrainConfig {
    pub enabled: bool,
    pub base_height: f32,
    pub material_id: u32,
    pub seed: u32,
    pub frequency: f32,
    pub amplitude: f32,
    pub octaves: u32,
    pub lacunarity: f32,
    pub gain: f32,
}

impl Default for TerrainConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            base_height: 0.0,
            material_id: 0,
            seed: 0,
            frequency: 0.01,
            amplitude: 30.0,
            octaves: 11,
            lacunarity: 2.0,
            gain: 0.5,
        }
    }
}

impl TerrainConfig {
    pub fn evaluate(&self, point: Vec3) -> f32 {
        terrain_fbm(self, point)
    }

    pub fn height_at(&self, x: f32, z: f32) -> f32 {
        let mut px = x * self.frequency;
        let mut pz = z * self.frequency;
        let mut total = 0.0_f32;
        let mut amplitude = self.amplitude;
        let mut dx_sum = 0.0_f32;
        let mut dz_sum = 0.0_f32;

        for _octave in 0..self.octaves {
            let (value, dvdx, dvdz) = noised_2d(px, pz, self.seed);
            dx_sum += dvdx * 0.5;
            dz_sum += dvdz * 0.5;
            let dampening = 1.0 / (1.0 + dx_sum * dx_sum + dz_sum * dz_sum);
            total += amplitude * value * dampening;
            let new_px = 1.6 * px - 1.2 * pz;
            let new_pz = 1.2 * px + 1.6 * pz;
            px = new_px;
            pz = new_pz;
            amplitude *= self.gain;
        }

        self.base_height + total
    }

    pub fn max_surface_extent(&self) -> f32 {
        self.amplitude * 2.5
    }
}

#[derive(Clone)]
pub enum UndoAction {
    AddEdit { index: usize },
    RemoveEdit { edit: SdfEdit, index: usize },
    ModifyEdit { old_edit: SdfEdit, index: usize },
}

pub struct SdfWorld {
    pub edits: Vec<SdfEdit>,
    pub clipmap: SdfClipmap,
    pub bvh: SdfEditBvh,
    pub dirty: bool,
    pub pending_gpu_dispatches: Vec<GpuBrickDispatch>,
    pub max_updates_per_frame: usize,
    pub debug_brick_coloring: bool,
    pub terrain: TerrainConfig,
    pub smoothness_scale: f32,
    undo_stack: Vec<UndoAction>,
    redo_stack: Vec<UndoAction>,
    max_undo_history: usize,
}

impl Default for SdfWorld {
    fn default() -> Self {
        Self::new()
    }
}

impl SdfWorld {
    pub fn new() -> Self {
        Self::with_config(DEFAULT_LEVEL_COUNT, BASE_VOXEL_SIZE)
    }

    pub fn with_config(level_count: usize, base_voxel_size: f32) -> Self {
        use super::brick_map::MAX_BRICKS;
        Self {
            edits: Vec::new(),
            clipmap: SdfClipmap::new(level_count, base_voxel_size, MAX_BRICKS as u32),
            bvh: SdfEditBvh::new(),
            dirty: false,
            pending_gpu_dispatches: Vec::new(),
            max_updates_per_frame: 4000,
            debug_brick_coloring: false,
            terrain: TerrainConfig::default(),
            smoothness_scale: 1.0,
            undo_stack: Vec::new(),
            redo_stack: Vec::new(),
            max_undo_history: 100,
        }
    }

    pub fn add_edit(&mut self, edit: SdfEdit) -> usize {
        let index = self.edits.len();
        let bounds = edit.effective_bounds();

        self.bvh.add_edit(index, bounds);
        self.edits.push(edit);
        self.clipmap.mark_dirty_in_bounds(&bounds);
        self.dirty = true;

        self.undo_stack.push(UndoAction::AddEdit { index });
        self.redo_stack.clear();
        self.trim_undo_history();

        index
    }

    pub fn add_edit_no_undo(&mut self, edit: SdfEdit) -> usize {
        let index = self.edits.len();
        let bounds = edit.effective_bounds();

        self.bvh.add_edit(index, bounds);
        self.edits.push(edit);
        self.clipmap.mark_dirty_in_bounds(&bounds);
        self.dirty = true;

        index
    }

    pub fn add_sphere(&mut self, center: Vec3, radius: f32, material_id: u32) -> usize {
        let transform = nalgebra_glm::translation(&center);
        let edit = SdfEdit::union(SdfPrimitive::Sphere { radius }, transform, material_id);
        self.add_edit(edit)
    }

    pub fn add_box(&mut self, center: Vec3, half_extents: Vec3, material_id: u32) -> usize {
        let transform = nalgebra_glm::translation(&center);
        let edit = SdfEdit::union(SdfPrimitive::Box { half_extents }, transform, material_id);
        self.add_edit(edit)
    }

    pub fn add_ground_plane(&mut self, height: f32, material_id: u32) -> usize {
        let transform = nalgebra_glm::translation(&Vec3::new(0.0, height, 0.0));
        let edit = SdfEdit::union(
            SdfPrimitive::Plane {
                normal: Vec3::new(0.0, 1.0, 0.0),
                offset: 0.0,
            },
            transform,
            material_id,
        );
        self.add_edit(edit)
    }

    pub fn subtract_sphere(&mut self, center: Vec3, radius: f32, smoothness: f32) -> usize {
        let transform = nalgebra_glm::translation(&center);
        let edit = if smoothness > 0.0 {
            SdfEdit::smooth_subtraction(SdfPrimitive::Sphere { radius }, transform, 0, smoothness)
        } else {
            SdfEdit::subtraction(SdfPrimitive::Sphere { radius }, transform, 0)
        };
        self.add_edit(edit)
    }

    pub fn modify_edit<F>(&mut self, index: usize, modifier: F)
    where
        F: FnOnce(&mut SdfEdit),
    {
        if let Some(edit) = self.edits.get_mut(index) {
            let old_edit = edit.clone();
            let old_bounds = edit.effective_bounds();

            modifier(edit);

            let new_bounds = edit.effective_bounds();

            self.bvh.update_edit(index, new_bounds);
            self.clipmap.mark_dirty_in_bounds(&old_bounds);
            self.clipmap.mark_dirty_in_bounds(&new_bounds);
            self.dirty = true;

            self.undo_stack
                .push(UndoAction::ModifyEdit { old_edit, index });
            self.redo_stack.clear();
            self.trim_undo_history();
        }
    }

    pub fn modify_edit_no_undo<F>(&mut self, index: usize, modifier: F)
    where
        F: FnOnce(&mut SdfEdit),
    {
        if let Some(edit) = self.edits.get_mut(index) {
            let old_bounds = edit.effective_bounds();
            modifier(edit);
            let new_bounds = edit.effective_bounds();
            self.bvh.update_edit(index, new_bounds);
            self.clipmap.mark_dirty_in_bounds(&old_bounds);
            self.clipmap.mark_dirty_in_bounds(&new_bounds);
            self.dirty = true;
        }
    }

    pub fn remove_edit(&mut self, index: usize) {
        if index < self.edits.len() {
            let edit = self.edits.remove(index);
            let bounds = edit.effective_bounds();

            self.bvh.remove_edit(index);
            for following_index in index..self.edits.len() {
                let following_bounds = self.edits[following_index].effective_bounds();
                self.bvh.remove_edit(following_index + 1);
                self.bvh.add_edit(following_index, following_bounds);
            }

            self.clipmap.mark_dirty_in_bounds(&bounds);
            self.dirty = true;

            self.undo_stack.push(UndoAction::RemoveEdit { edit, index });
            self.redo_stack.clear();
            self.trim_undo_history();
        }
    }

    pub fn undo(&mut self) -> bool {
        if let Some(action) = self.undo_stack.pop() {
            match action.clone() {
                UndoAction::AddEdit { index } => {
                    if index < self.edits.len() {
                        let edit = self.edits.remove(index);
                        let bounds = edit.effective_bounds();
                        self.bvh.remove_edit(index);
                        for following_index in index..self.edits.len() {
                            let following_bounds = self.edits[following_index].effective_bounds();
                            self.bvh.remove_edit(following_index + 1);
                            self.bvh.add_edit(following_index, following_bounds);
                        }
                        self.clipmap.mark_dirty_in_bounds(&bounds);
                        self.dirty = true;
                        self.redo_stack.push(UndoAction::RemoveEdit { edit, index });
                    }
                }
                UndoAction::RemoveEdit { edit, index } => {
                    let bounds = edit.effective_bounds();
                    for following_index in (index..self.edits.len()).rev() {
                        let following_bounds = self.edits[following_index].effective_bounds();
                        self.bvh.remove_edit(following_index);
                        self.bvh.add_edit(following_index + 1, following_bounds);
                    }
                    self.edits.insert(index, edit);
                    self.bvh.add_edit(index, bounds);
                    self.clipmap.mark_dirty_in_bounds(&bounds);
                    self.dirty = true;
                    self.redo_stack.push(UndoAction::AddEdit { index });
                }
                UndoAction::ModifyEdit { old_edit, index } => {
                    if let Some(current_edit) = self.edits.get_mut(index) {
                        let current_clone = current_edit.clone();
                        let old_bounds = current_edit.effective_bounds();
                        *current_edit = old_edit;
                        let new_bounds = current_edit.effective_bounds();
                        self.bvh.update_edit(index, new_bounds);
                        self.clipmap.mark_dirty_in_bounds(&old_bounds);
                        self.clipmap.mark_dirty_in_bounds(&new_bounds);
                        self.dirty = true;
                        self.redo_stack.push(UndoAction::ModifyEdit {
                            old_edit: current_clone,
                            index,
                        });
                    }
                }
            }
            true
        } else {
            false
        }
    }

    pub fn redo(&mut self) -> bool {
        if let Some(action) = self.redo_stack.pop() {
            match action.clone() {
                UndoAction::AddEdit { index } => {
                    if index < self.edits.len() {
                        let edit = self.edits.remove(index);
                        let bounds = edit.effective_bounds();
                        self.bvh.remove_edit(index);
                        for following_index in index..self.edits.len() {
                            let following_bounds = self.edits[following_index].effective_bounds();
                            self.bvh.remove_edit(following_index + 1);
                            self.bvh.add_edit(following_index, following_bounds);
                        }
                        self.clipmap.mark_dirty_in_bounds(&bounds);
                        self.dirty = true;
                        self.undo_stack.push(UndoAction::RemoveEdit { edit, index });
                    }
                }
                UndoAction::RemoveEdit { edit, index } => {
                    let bounds = edit.effective_bounds();
                    for following_index in (index..self.edits.len()).rev() {
                        let following_bounds = self.edits[following_index].effective_bounds();
                        self.bvh.remove_edit(following_index);
                        self.bvh.add_edit(following_index + 1, following_bounds);
                    }
                    self.edits.insert(index, edit);
                    self.bvh.add_edit(index, bounds);
                    self.clipmap.mark_dirty_in_bounds(&bounds);
                    self.dirty = true;
                    self.undo_stack.push(UndoAction::AddEdit { index });
                }
                UndoAction::ModifyEdit { old_edit, index } => {
                    if let Some(current_edit) = self.edits.get_mut(index) {
                        let current_clone = current_edit.clone();
                        let old_bounds = current_edit.effective_bounds();
                        *current_edit = old_edit;
                        let new_bounds = current_edit.effective_bounds();
                        self.bvh.update_edit(index, new_bounds);
                        self.clipmap.mark_dirty_in_bounds(&old_bounds);
                        self.clipmap.mark_dirty_in_bounds(&new_bounds);
                        self.dirty = true;
                        self.undo_stack.push(UndoAction::ModifyEdit {
                            old_edit: current_clone,
                            index,
                        });
                    }
                }
            }
            true
        } else {
            false
        }
    }

    pub fn can_undo(&self) -> bool {
        !self.undo_stack.is_empty()
    }

    pub fn can_redo(&self) -> bool {
        !self.redo_stack.is_empty()
    }

    pub fn clear_undo_history(&mut self) {
        self.undo_stack.clear();
        self.redo_stack.clear();
    }

    fn trim_undo_history(&mut self) {
        while self.undo_stack.len() > self.max_undo_history {
            self.undo_stack.remove(0);
        }
    }

    pub fn clear(&mut self) {
        for edit in &self.edits {
            let bounds = edit.effective_bounds();
            self.clipmap.mark_dirty_in_bounds(&bounds);
        }
        self.edits.clear();
        self.bvh = SdfEditBvh::new();
        self.dirty = true;
        self.clear_undo_history();
    }

    pub fn update(&mut self, camera_position: Vec3) {
        self.pending_gpu_dispatches.clear();
        for level in &mut self.clipmap.levels {
            level.pointer_dirty = false;
        }

        let bounds: Vec<Aabb> = self.edits.iter().map(|e| e.effective_bounds()).collect();
        self.bvh.rebuild_if_needed(&bounds);

        let edits_aabb = if !self.edits.is_empty() {
            let mut aabb_min = Vec3::new(f32::MAX, f32::MAX, f32::MAX);
            let mut aabb_max = Vec3::new(f32::MIN, f32::MIN, f32::MIN);
            for bound in &bounds {
                aabb_min = nalgebra_glm::min2(&aabb_min, &bound.min);
                aabb_max = nalgebra_glm::max2(&aabb_max, &bound.max);
            }
            Some(Aabb {
                min: aabb_min,
                max: aabb_max,
            })
        } else {
            None
        };

        self.clipmap.update_center_with_bvh(
            camera_position,
            Some(&self.bvh),
            edits_aabb.as_ref(),
            &self.terrain,
        );

        let dispatches = prepare_gpu_brick_dispatches(
            &mut self.clipmap,
            &self.bvh,
            &self.terrain,
            self.max_updates_per_frame,
            camera_position,
        );

        if !dispatches.is_empty() {
            self.dirty = true;
        }

        self.pending_gpu_dispatches = dispatches;
    }

    pub fn mark_edit_dirty(&mut self, index: usize) {
        if let Some(edit) = self.edits.get(index) {
            let bounds = edit.effective_bounds();
            self.clipmap.mark_dirty_in_bounds(&bounds);
            self.dirty = true;
        }
    }

    pub fn edit_count(&self) -> usize {
        self.edits.len()
    }

    pub fn allocated_brick_count(&self) -> u32 {
        self.clipmap.allocator.allocated_count()
    }

    pub fn max_brick_count(&self) -> u32 {
        self.clipmap.allocator.capacity()
    }

    pub fn level_count(&self) -> usize {
        self.clipmap.level_count()
    }

    pub fn voxel_sizes(&self) -> Vec<f32> {
        self.clipmap.voxel_sizes()
    }

    pub fn evaluate_at(&self, point: Vec3) -> f32 {
        let mut distance = if self.terrain.enabled {
            self.terrain.evaluate(point)
        } else {
            f32::MAX
        };

        for edit in &self.edits {
            let edit_distance = edit.evaluate(point);
            distance = edit.operation().apply(distance, edit_distance);
        }

        distance
    }

    pub fn evaluate_at_lod(&self, point: Vec3, max_octaves: u32) -> f32 {
        let mut distance = if self.terrain.enabled {
            let lod_config = TerrainConfig {
                octaves: max_octaves.min(self.terrain.octaves),
                ..self.terrain
            };
            lod_config.evaluate(point)
        } else {
            f32::MAX
        };

        for edit in &self.edits {
            let edit_distance = edit.evaluate(point);
            distance = edit.operation().apply(distance, edit_distance);
        }

        distance
    }

    pub fn evaluate_at_lod_excluding(
        &self,
        point: Vec3,
        max_octaves: u32,
        excluded_indices: &HashSet<usize>,
    ) -> f32 {
        let mut distance = if self.terrain.enabled {
            let lod_config = TerrainConfig {
                octaves: max_octaves.min(self.terrain.octaves),
                ..self.terrain
            };
            lod_config.evaluate(point)
        } else {
            f32::MAX
        };

        for (index, edit) in self.edits.iter().enumerate() {
            if excluded_indices.contains(&index) {
                continue;
            }
            let edit_distance = edit.evaluate(point);
            distance = edit.operation().apply(distance, edit_distance);
        }

        distance
    }

    pub fn set_smoothness_scale(&mut self, scale: f32) {
        if (self.smoothness_scale - scale).abs() > f32::EPSILON {
            self.smoothness_scale = scale;
            self.clipmap.force_reinitialize();
            self.dirty = true;
        }
    }

    pub fn set_terrain_config(&mut self, config: TerrainConfig) {
        let changed = self.terrain.enabled != config.enabled
            || self.terrain.base_height != config.base_height
            || self.terrain.seed != config.seed
            || self.terrain.frequency != config.frequency
            || self.terrain.amplitude != config.amplitude
            || self.terrain.octaves != config.octaves
            || self.terrain.gain != config.gain;
        self.terrain = config;
        if changed {
            self.clipmap.force_reinitialize();
            self.dirty = true;
        }
    }

    pub fn raycast(&self, origin: Vec3, direction: Vec3, max_distance: f32) -> Option<Vec3> {
        if self.edits.is_empty() && !self.terrain.enabled {
            return None;
        }

        let direction = nalgebra_glm::normalize(&direction);
        let mut total_distance = 0.0;
        let base_voxel_size = BASE_VOXEL_SIZE;
        let min_step = base_voxel_size * 0.5;
        let hit_threshold = base_voxel_size * 0.1;

        for _ in 0..512 {
            let current_point = origin + direction * total_distance;
            let distance = self.evaluate_at(current_point);

            if distance.abs() < hit_threshold {
                return Some(current_point);
            }

            if distance < 0.0 {
                total_distance += min_step;
            } else if distance == f32::MAX {
                total_distance += base_voxel_size * 4.0;
            } else {
                total_distance += distance.max(min_step);
            }

            if total_distance > max_distance {
                return None;
            }
        }

        None
    }

    pub fn evaluate_normal_at(&self, point: Vec3) -> Vec3 {
        let epsilon = BASE_VOXEL_SIZE * 0.5;
        let dx = self.evaluate_at(point + Vec3::new(epsilon, 0.0, 0.0))
            - self.evaluate_at(point - Vec3::new(epsilon, 0.0, 0.0));
        let dy = self.evaluate_at(point + Vec3::new(0.0, epsilon, 0.0))
            - self.evaluate_at(point - Vec3::new(0.0, epsilon, 0.0));
        let dz = self.evaluate_at(point + Vec3::new(0.0, 0.0, epsilon))
            - self.evaluate_at(point - Vec3::new(0.0, 0.0, epsilon));
        nalgebra_glm::normalize(&Vec3::new(dx, dy, dz))
    }

    pub fn snap_to_voxel_grid(&self, point: Vec3, level: usize) -> Vec3 {
        let voxel_size = if level < self.clipmap.levels.len() {
            self.clipmap.levels[level].voxel_size
        } else {
            BASE_VOXEL_SIZE
        };

        Vec3::new(
            (point.x / voxel_size).round() * voxel_size,
            (point.y / voxel_size).round() * voxel_size,
            (point.z / voxel_size).round() * voxel_size,
        )
    }

    pub fn base_voxel_size(&self) -> f32 {
        BASE_VOXEL_SIZE
    }

    pub fn get_brick_pointer_at(&self, world_pos: Vec3, level: usize) -> i32 {
        if level >= self.clipmap.levels.len() {
            return -1;
        }
        let grid = &self.clipmap.levels[level];
        let brick_coord = grid.world_to_brick_coord(world_pos);
        grid.get_pointer(brick_coord)
    }

    pub fn get_allocated_bricks_in_range(
        &self,
        level: usize,
        center: Vec3,
        radius: i32,
    ) -> Vec<(nalgebra_glm::IVec3, Vec3)> {
        self.get_allocated_bricks_in_range_with_pointers(level, center, radius)
            .into_iter()
            .map(|(coord, origin, _pointer)| (coord, origin))
            .collect()
    }

    pub fn get_allocated_bricks_in_range_with_pointers(
        &self,
        level: usize,
        center: Vec3,
        radius: i32,
    ) -> Vec<(nalgebra_glm::IVec3, Vec3, i32)> {
        let mut result = Vec::new();

        if level >= self.clipmap.levels.len() {
            return result;
        }

        let grid = &self.clipmap.levels[level];
        let brick_world_size = grid.voxel_size * 8.0;

        let center_brick = grid.world_to_brick_coord(center);

        for bz in -radius..=radius {
            for by in -radius..=radius {
                for bx in -radius..=radius {
                    let brick_coord = nalgebra_glm::IVec3::new(
                        center_brick.x + bx,
                        center_brick.y + by,
                        center_brick.z + bz,
                    );

                    let pointer = grid.get_pointer(brick_coord);
                    if pointer >= 0 {
                        let world_origin = Vec3::new(
                            brick_coord.x as f32 * brick_world_size,
                            brick_coord.y as f32 * brick_world_size,
                            brick_coord.z as f32 * brick_world_size,
                        );
                        result.push((brick_coord, world_origin, pointer));
                    }
                }
            }
        }

        result
    }
}

#[derive(Clone, Copy)]
pub struct SdfMaterial {
    pub base_color: Vec3,
    pub roughness: f32,
    pub metallic: f32,
    pub emissive: Vec3,
}

impl Default for SdfMaterial {
    fn default() -> Self {
        Self {
            base_color: Vec3::new(0.8, 0.8, 0.8),
            roughness: 0.5,
            metallic: 0.0,
            emissive: Vec3::zeros(),
        }
    }
}

impl SdfMaterial {
    pub fn new(base_color: Vec3) -> Self {
        Self {
            base_color,
            ..Default::default()
        }
    }

    pub fn with_roughness(mut self, roughness: f32) -> Self {
        self.roughness = roughness;
        self
    }

    pub fn with_metallic(mut self, metallic: f32) -> Self {
        self.metallic = metallic;
        self
    }

    pub fn with_emissive(mut self, emissive: Vec3) -> Self {
        self.emissive = emissive;
        self
    }
}

pub struct SdfMaterialRegistry {
    pub materials: Vec<SdfMaterial>,
}

impl Default for SdfMaterialRegistry {
    fn default() -> Self {
        Self::new()
    }
}

impl SdfMaterialRegistry {
    pub fn new() -> Self {
        let mut registry = Self {
            materials: Vec::new(),
        };
        registry.add_material(SdfMaterial::default());
        registry
    }

    pub fn add_material(&mut self, material: SdfMaterial) -> u32 {
        let id = self.materials.len() as u32;
        self.materials.push(material);
        id
    }

    pub fn get_material(&self, id: u32) -> Option<&SdfMaterial> {
        self.materials.get(id as usize)
    }

    pub fn set_material(&mut self, id: u32, material: SdfMaterial) {
        if let Some(existing) = self.materials.get_mut(id as usize) {
            *existing = material;
        }
    }
}
