use nightshade::prelude::*;

#[derive(Clone)]
pub struct EntitySnapshot {
    pub name: Option<Name>,
    pub local_transform: Option<LocalTransform>,
    pub global_transform: Option<GlobalTransform>,
    pub parent_name: Option<String>,
    pub camera: Option<Camera>,
    pub light: Option<Light>,
    pub visibility: Option<Visibility>,
    pub render_mesh: Option<RenderMesh>,
    pub material_ref: Option<MaterialRef>,
    pub render_layer: Option<RenderLayer>,
    pub script: Option<Script>,
    pub casts_shadow: bool,
}

impl EntitySnapshot {
    pub fn capture(world: &World, entity: Entity) -> Self {
        let parent_name = world
            .get_parent(entity)
            .and_then(|p| p.0)
            .and_then(|parent_entity| world.get_name(parent_entity))
            .map(|n| n.0.clone());

        Self {
            name: world.get_name(entity).cloned(),
            local_transform: world.get_local_transform(entity).cloned(),
            global_transform: world.get_global_transform(entity).cloned(),
            parent_name,
            camera: world.get_camera(entity).cloned(),
            light: world.get_light(entity).cloned(),
            visibility: world.get_visibility(entity).cloned(),
            render_mesh: world.get_render_mesh(entity).cloned(),
            material_ref: world.get_material_ref(entity).cloned(),
            render_layer: world.get_render_layer(entity).cloned(),
            script: world.get_script(entity).cloned(),
            casts_shadow: world.entity_has_casts_shadow(entity),
        }
    }
}

#[derive(Clone)]
pub struct HierarchyNode {
    pub entity: Entity,
    pub snapshot: EntitySnapshot,
    pub children: Vec<HierarchyNode>,
}

impl HierarchyNode {
    fn remap(&self, mapping: &std::collections::HashMap<Entity, Entity>) -> Self {
        Self {
            entity: mapping.get(&self.entity).copied().unwrap_or(self.entity),
            snapshot: self.snapshot.clone(),
            children: self.children.iter().map(|c| c.remap(mapping)).collect(),
        }
    }
}

#[derive(Clone)]
pub enum UndoableOperation {
    EntityCreated {
        hierarchy: Box<HierarchyNode>,
        current_entity: Entity,
    },
    EntityDeleted {
        hierarchy: Box<HierarchyNode>,
        deleted_entity: Entity,
    },
    TransformChanged {
        entity: Entity,
        old_transform: LocalTransform,
        new_transform: LocalTransform,
    },
}

#[derive(Clone)]
pub struct HistoryEntry {
    pub operation: UndoableOperation,
    pub description: String,
}

pub struct UndoResult {
    pub select_entity: Option<Entity>,
}

pub struct UndoHistory {
    undo_stack: Vec<HistoryEntry>,
    redo_stack: Vec<HistoryEntry>,
    max_history_size: usize,
    pending_transform: Option<(Entity, LocalTransform)>,
}

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

impl UndoHistory {
    pub fn new(max_history_size: usize) -> Self {
        Self {
            undo_stack: Vec::new(),
            redo_stack: Vec::new(),
            max_history_size,
            pending_transform: None,
        }
    }

    pub fn push(&mut self, operation: UndoableOperation, description: String) {
        self.redo_stack.clear();

        self.undo_stack.push(HistoryEntry {
            operation,
            description,
        });

        while self.undo_stack.len() > self.max_history_size {
            self.undo_stack.remove(0);
        }
    }

    pub fn undo(&mut self, world: &mut World) -> Option<UndoResult> {
        let entry = self.undo_stack.pop()?;
        let (redo_entry, select_entity) = self.apply_undo(world, &entry);
        self.redo_stack.push(redo_entry);
        Some(UndoResult { select_entity })
    }

    pub fn redo(&mut self, world: &mut World) -> Option<UndoResult> {
        let entry = self.redo_stack.pop()?;
        let (undo_entry, select_entity) = self.apply_redo(world, &entry);
        self.undo_stack.push(undo_entry);
        Some(UndoResult { select_entity })
    }

    fn apply_undo(
        &mut self,
        world: &mut World,
        entry: &HistoryEntry,
    ) -> (HistoryEntry, Option<Entity>) {
        match &entry.operation {
            UndoableOperation::EntityCreated {
                hierarchy,
                current_entity,
            } => {
                despawn_recursive_immediate(world, *current_entity);
                (
                    HistoryEntry {
                        operation: UndoableOperation::EntityDeleted {
                            hierarchy: hierarchy.clone(),
                            deleted_entity: *current_entity,
                        },
                        description: entry.description.clone(),
                    },
                    None,
                )
            }
            UndoableOperation::EntityDeleted { hierarchy, .. } => {
                let parent_name = hierarchy.snapshot.parent_name.as_ref();
                let original_parent = parent_name.and_then(|name| find_entity_by_name(world, name));
                let (new_entity, id_mapping) =
                    Self::recreate_hierarchy_with_mapping(world, hierarchy, original_parent);
                world.resources.children_cache_valid = false;

                for entry in &mut self.undo_stack {
                    Self::remap_entry(entry, &id_mapping);
                }
                for entry in &mut self.redo_stack {
                    Self::remap_entry(entry, &id_mapping);
                }

                if let Some(pending) = &mut self.pending_transform
                    && let Some(&new_id) = id_mapping.get(&pending.0)
                {
                    pending.0 = new_id;
                }

                let remapped_hierarchy = Box::new(hierarchy.remap(&id_mapping));

                (
                    HistoryEntry {
                        operation: UndoableOperation::EntityCreated {
                            hierarchy: remapped_hierarchy,
                            current_entity: new_entity,
                        },
                        description: entry.description.clone(),
                    },
                    Some(new_entity),
                )
            }
            UndoableOperation::TransformChanged {
                entity,
                old_transform,
                new_transform,
            } => {
                let exists = entity_exists(world, *entity);
                if exists {
                    world.set_local_transform(*entity, *old_transform);
                    mark_local_transform_dirty(world, *entity);
                }
                (
                    HistoryEntry {
                        operation: UndoableOperation::TransformChanged {
                            entity: *entity,
                            old_transform: *new_transform,
                            new_transform: *old_transform,
                        },
                        description: entry.description.clone(),
                    },
                    if exists { Some(*entity) } else { None },
                )
            }
        }
    }

    fn apply_redo(
        &mut self,
        world: &mut World,
        entry: &HistoryEntry,
    ) -> (HistoryEntry, Option<Entity>) {
        self.apply_undo(world, entry)
    }

    fn remap_entry(entry: &mut HistoryEntry, mapping: &std::collections::HashMap<Entity, Entity>) {
        match &mut entry.operation {
            UndoableOperation::EntityCreated {
                hierarchy,
                current_entity,
            } => {
                if let Some(&new_id) = mapping.get(current_entity) {
                    *current_entity = new_id;
                }
                *hierarchy = Box::new(hierarchy.remap(mapping));
            }
            UndoableOperation::EntityDeleted {
                hierarchy,
                deleted_entity,
            } => {
                if let Some(&new_id) = mapping.get(deleted_entity) {
                    *deleted_entity = new_id;
                }
                *hierarchy = Box::new(hierarchy.remap(mapping));
            }
            UndoableOperation::TransformChanged { entity, .. } => {
                if let Some(&new_id) = mapping.get(entity) {
                    *entity = new_id;
                }
            }
        }
    }

    pub fn capture_hierarchy(world: &World, entity: Entity) -> HierarchyNode {
        let snapshot = EntitySnapshot::capture(world, entity);
        let children = query_children(world, entity)
            .into_iter()
            .map(|child| Self::capture_hierarchy(world, child))
            .collect();
        HierarchyNode {
            entity,
            snapshot,
            children,
        }
    }

    fn recreate_hierarchy_with_mapping(
        world: &mut World,
        node: &HierarchyNode,
        parent: Option<Entity>,
    ) -> (Entity, std::collections::HashMap<Entity, Entity>) {
        let mut mapping = std::collections::HashMap::new();
        let root_entity =
            Self::recreate_hierarchy_internal_with_mapping(world, node, parent, &mut mapping);
        (root_entity, mapping)
    }

    fn recreate_hierarchy_internal_with_mapping(
        world: &mut World,
        node: &HierarchyNode,
        parent: Option<Entity>,
        mapping: &mut std::collections::HashMap<Entity, Entity>,
    ) -> Entity {
        let new_entity = Self::recreate_entity(world, &node.snapshot, parent);
        mapping.insert(node.entity, new_entity);

        for child_node in &node.children {
            Self::recreate_hierarchy_internal_with_mapping(
                world,
                child_node,
                Some(new_entity),
                mapping,
            );
        }

        new_entity
    }

    fn recreate_entity(
        world: &mut World,
        snapshot: &EntitySnapshot,
        parent: Option<Entity>,
    ) -> Entity {
        let entities = EntityBuilder::new()
            .with_local_transform(snapshot.local_transform.unwrap_or_default())
            .with_global_transform(snapshot.global_transform.unwrap_or_default())
            .spawn(world, 1);
        let entity = entities[0];

        if let Some(name) = &snapshot.name {
            world.set_name(entity, name.clone());
        }

        if let Some(parent_entity) = parent {
            world.set_parent(entity, Parent(Some(parent_entity)));
        }

        if let Some(camera) = &snapshot.camera {
            world.set_camera(entity, *camera);
        }
        if let Some(light) = &snapshot.light {
            world.set_light(entity, light.clone());
        }
        if let Some(visibility) = &snapshot.visibility {
            world.set_visibility(entity, visibility.clone());
        }
        if let Some(render_mesh) = &snapshot.render_mesh {
            world.set_render_mesh(entity, render_mesh.clone());
        }
        if let Some(material_ref) = &snapshot.material_ref {
            world.set_material_ref(entity, material_ref.clone());
        }
        if let Some(render_layer) = &snapshot.render_layer {
            world.set_render_layer(entity, *render_layer);
        }
        if let Some(script) = &snapshot.script {
            world.set_script(entity, script.clone());
        }
        if snapshot.casts_shadow {
            world.add_components(entity, CASTS_SHADOW);
        }

        entity
    }

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

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

    pub fn undo_stack(&self) -> &[HistoryEntry] {
        &self.undo_stack
    }

    pub fn redo_stack(&self) -> &[HistoryEntry] {
        &self.redo_stack
    }

    pub fn begin_transform_change(&mut self, entity: Entity, transform: LocalTransform) {
        if self.pending_transform.is_none() {
            self.pending_transform = Some((entity, transform));
        }
    }

    pub fn commit_transform_change(&mut self, entity: Entity, new_transform: LocalTransform) {
        if let Some((pending_entity, old_transform)) = self.pending_transform.take()
            && pending_entity == entity
            && old_transform != new_transform
        {
            self.push(
                UndoableOperation::TransformChanged {
                    entity,
                    old_transform,
                    new_transform,
                },
                "Transform changed".to_string(),
            );
        }
    }
}

fn entity_exists(world: &World, entity: Entity) -> bool {
    world.get_local_transform(entity).is_some()
        || world.get_name(entity).is_some()
        || world.get_camera(entity).is_some()
        || world.get_light(entity).is_some()
}

fn find_entity_by_name(world: &World, name: &str) -> Option<Entity> {
    world
        .query_entities(NAME)
        .find(|&entity| world.get_name(entity).is_some_and(|n| n.0 == name))
}
