use nalgebra_glm::Vec3;

use super::brick_map::{BrickData, MAX_BRICKS};
use super::bvh::SdfEditBvh;
use super::clipmap::{BASE_VOXEL_SIZE, DEFAULT_LEVEL_COUNT, SdfClipmap};
use super::evaluation::{BrickUpdateResult, update_dirty_bricks};
use super::primitives::{Aabb, SdfEdit, SdfPrimitive};

#[derive(Clone, Copy)]
pub struct TerrainConfig {
    pub enabled: bool,
    pub height: f32,
    pub material_id: u32,
}

impl Default for TerrainConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            height: 0.0,
            material_id: 0,
        }
    }
}

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

#[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 brick_data_cache: Vec<Option<BrickData>>,
    pub pending_uploads: Vec<BrickUpdateResult>,
    pub max_updates_per_frame: usize,
    pub debug_brick_coloring: bool,
    pub terrain: TerrainConfig,
    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, MAX_BRICKS as u32)
    }

    pub fn with_config(level_count: usize, base_voxel_size: f32, max_bricks: u32) -> Self {
        let mut brick_data_cache = Vec::with_capacity(max_bricks as usize);
        brick_data_cache.resize_with(max_bricks as usize, || None);

        Self {
            edits: Vec::new(),
            clipmap: SdfClipmap::new(level_count, base_voxel_size, max_bricks),
            bvh: SdfEditBvh::new(),
            dirty: false,
            brick_data_cache,
            pending_uploads: Vec::new(),
            max_updates_per_frame: 4000,
            debug_brick_coloring: false,
            terrain: TerrainConfig::default(),
            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.clone());
        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.clone());
        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.clone());
            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 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.clone());
                    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.clone());
                        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.clone());
                    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.clone());
                        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_uploads.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);

        self.clipmap.update_center_with_bvh_and_terrain(
            camera_position,
            Some(&self.bvh),
            Some(&self.terrain),
        );

        let results = update_dirty_bricks(
            &mut self.clipmap,
            &self.edits,
            &self.bvh,
            &self.terrain,
            self.max_updates_per_frame,
            camera_position,
        );

        for result in &results {
            if let Some(ref brick_data) = result.brick_data {
                if result.new_pointer >= 0 {
                    let slot = result.new_pointer as usize;
                    if slot < self.brick_data_cache.len() {
                        self.brick_data_cache[slot] = Some(brick_data.clone());
                    }
                }
            } else if result.old_pointer >= 0 && result.new_pointer < 0 {
                let slot = result.old_pointer as usize;
                if slot < self.brick_data_cache.len() {
                    self.brick_data_cache[slot] = None;
                }
            }
        }

        self.pending_uploads.extend(results);

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

    pub fn take_pending_uploads(&mut self) -> Vec<BrickUpdateResult> {
        std::mem::take(&mut self.pending_uploads)
    }

    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 get_brick_data(&self, brick_index: u32) -> Option<&BrickData> {
        self.brick_data_cache
            .get(brick_index as usize)
            .and_then(|opt| opt.as_ref())
    }

    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 set_terrain(&mut self, enabled: bool, height: f32, material_id: u32) {
        let changed = self.terrain.enabled != enabled || self.terrain.height != height;
        self.terrain.enabled = enabled;
        self.terrain.height = height;
        self.terrain.material_id = material_id;
        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;
        }
    }
}
