use crate::editor_scene::EditorScene;
use crate::project::ProjectState;
use crate::scene_writeback;
use nightshade::ecs::scene::{
    AssetUuid, Scene, apply_scene_settings, capture_scene_settings, embed_referenced_meshes,
    embed_referenced_textures, entity_to_scene_entity, spawn_scene,
};
use nightshade::ecs::transform::queries::query_descendants;
use nightshade::prelude::*;

pub const MAX_UNDO_DEPTH: usize = 200;

/// History container parameterized by reversible operation. Stores
/// undo and redo stacks; performing an operation pushes its inverse
/// onto the matching stack.
pub struct History<T: Reversible> {
    undo_stack: Vec<HistoryEntry<T>>,
    redo_stack: Vec<HistoryEntry<T>>,
    max_entries: usize,
}

pub struct HistoryEntry<T> {
    pub operation: T,
    pub description: String,
}

impl<T: Reversible> Default for History<T> {
    fn default() -> Self {
        Self::new(MAX_UNDO_DEPTH)
    }
}

impl<T: Reversible> History<T> {
    pub fn new(max_entries: usize) -> Self {
        Self {
            undo_stack: Vec::new(),
            redo_stack: Vec::new(),
            max_entries,
        }
    }

    pub fn push(&mut self, operation: T, description: impl Into<String>) {
        self.redo_stack.clear();
        self.undo_stack.push(HistoryEntry {
            operation,
            description: description.into(),
        });
        while self.undo_stack.len() > self.max_entries {
            self.undo_stack.remove(0);
        }
    }

    pub fn undo(&mut self, ctx: &mut T::Context<'_>) -> Option<T::Result> {
        let entry = self.undo_stack.pop()?;
        let (reversed, result) = entry.operation.reverse(ctx);
        self.redo_stack.push(HistoryEntry {
            operation: reversed,
            description: entry.description,
        });
        Some(result)
    }

    pub fn redo(&mut self, ctx: &mut T::Context<'_>) -> Option<T::Result> {
        let entry = self.redo_stack.pop()?;
        let (reversed, result) = entry.operation.reverse(ctx);
        self.undo_stack.push(HistoryEntry {
            operation: reversed,
            description: entry.description,
        });
        Some(result)
    }

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

    pub fn undo_len(&self) -> usize {
        self.undo_stack.len()
    }
}

pub trait Reversible: Sized {
    type Context<'a>;
    type Result;
    fn reverse(&self, ctx: &mut Self::Context<'_>) -> (Self, Self::Result);
}

#[derive(Default)]
pub struct UndoResult {
    pub select_entity: Option<Entity>,
}

pub struct UndoCtx<'a> {
    pub world: &'a mut World,
    pub editor_scene: &'a mut EditorScene,
    pub project: &'a mut ProjectState,
}

#[derive(Clone)]
pub enum UndoableOperation {
    EntityCreated {
        captured: Box<CapturedSubtree>,
    },
    EntityDeleted {
        captured: Box<CapturedSubtree>,
    },
    TransformChanged {
        uuid: AssetUuid,
        old: LocalTransform,
        new: LocalTransform,
    },
    ComponentChanged {
        uuid: AssetUuid,
        old: Box<ComponentSnapshot>,
        new: Box<ComponentSnapshot>,
    },
    Batch {
        steps: Vec<UndoableOperation>,
    },
}

#[derive(Clone)]
pub enum ComponentSnapshot {
    Light(Light),
    Visibility(bool),
    CastsShadow(bool),
    Name(String),
    ParticleEmitter(Option<Box<nightshade::ecs::particles::components::ParticleEmitter>>),
    Decal(Option<Box<nightshade::ecs::decal::components::Decal>>),
    AudioSource(Option<Box<nightshade::ecs::audio::components::AudioSource>>),
    RigidBody(Option<Box<RigidBodyComponent>>),
    Collider(Option<Box<ColliderComponent>>),
    CharacterController(Option<Box<CharacterControllerComponent>>),
    NavmeshAgent(Option<Box<NavMeshAgent>>),
    Camera(Option<nightshade::ecs::camera::components::Camera>),
    AnimationPlayer(Option<Box<nightshade::ecs::animation::components::AnimationPlayer>>),
    Material {
        name: String,
        material: Option<Box<nightshade::ecs::material::components::Material>>,
    },
    Text {
        component: Option<Box<nightshade::ecs::text::components::Text>>,
        content: Option<String>,
    },
    RenderLayer(Option<nightshade::ecs::primitives::RenderLayer>),
    CullingMask(Option<nightshade::ecs::primitives::CullingMask>),
    CameraCullingMask(Option<nightshade::ecs::primitives::CameraCullingMask>),
    IgnoreParentScale(bool),
    PrefabSource(Option<Box<nightshade::ecs::prefab::components::PrefabSource>>),
}

impl PartialEq for ComponentSnapshot {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Light(a), Self::Light(b)) => {
                a.light_type == b.light_type
                    && a.color == b.color
                    && a.intensity == b.intensity
                    && a.range == b.range
                    && a.inner_cone_angle == b.inner_cone_angle
                    && a.outer_cone_angle == b.outer_cone_angle
                    && a.cast_shadows == b.cast_shadows
                    && a.shadow_bias == b.shadow_bias
            }
            (Self::Visibility(a), Self::Visibility(b)) => a == b,
            (Self::CastsShadow(a), Self::CastsShadow(b)) => a == b,
            (Self::Name(a), Self::Name(b)) => a == b,
            (Self::ParticleEmitter(a), Self::ParticleEmitter(b)) => {
                snapshot_bytes(a) == snapshot_bytes(b)
            }
            (Self::Decal(a), Self::Decal(b)) => a == b,
            (Self::AudioSource(a), Self::AudioSource(b)) => snapshot_bytes(a) == snapshot_bytes(b),
            (Self::RigidBody(a), Self::RigidBody(b)) => snapshot_bytes(a) == snapshot_bytes(b),
            (Self::Collider(a), Self::Collider(b)) => snapshot_bytes(a) == snapshot_bytes(b),
            (Self::CharacterController(a), Self::CharacterController(b)) => {
                snapshot_bytes(a) == snapshot_bytes(b)
            }
            (Self::NavmeshAgent(a), Self::NavmeshAgent(b)) => {
                snapshot_bytes(a) == snapshot_bytes(b)
            }
            (Self::Camera(a), Self::Camera(b)) => a == b,
            (Self::AnimationPlayer(a), Self::AnimationPlayer(b)) => a == b,
            (
                Self::Material {
                    name: a_name,
                    material: a_mat,
                },
                Self::Material {
                    name: b_name,
                    material: b_mat,
                },
            ) => a_name == b_name && a_mat == b_mat,
            (
                Self::Text {
                    component: a_component,
                    content: a_content,
                },
                Self::Text {
                    component: b_component,
                    content: b_content,
                },
            ) => {
                snapshot_bytes(a_component) == snapshot_bytes(b_component) && a_content == b_content
            }
            (Self::RenderLayer(a), Self::RenderLayer(b)) => a == b,
            (Self::CullingMask(a), Self::CullingMask(b)) => a == b,
            (Self::CameraCullingMask(a), Self::CameraCullingMask(b)) => a == b,
            (Self::IgnoreParentScale(a), Self::IgnoreParentScale(b)) => a == b,
            (Self::PrefabSource(a), Self::PrefabSource(b)) => {
                snapshot_bytes(a) == snapshot_bytes(b)
            }
            _ => false,
        }
    }
}

fn snapshot_bytes<T: serde::Serialize>(value: &T) -> Vec<u8> {
    bincode::serialize(value).unwrap_or_default()
}

impl ComponentSnapshot {
    pub fn capture(world: &World, entity: Entity, kind: SnapshotKind) -> Option<Self> {
        match kind {
            SnapshotKind::Light => world.core.get_light(entity).cloned().map(Self::Light),
            SnapshotKind::Visibility => world
                .core
                .get_visibility(entity)
                .map(|v| Self::Visibility(v.visible)),
            SnapshotKind::CastsShadow => Some(Self::CastsShadow(
                world.core.entity_has_casts_shadow(entity),
            )),
            SnapshotKind::Name => Some(Self::Name(
                world
                    .core
                    .get_name(entity)
                    .map(|n| n.0.clone())
                    .unwrap_or_default(),
            )),
            SnapshotKind::ParticleEmitter => Some(Self::ParticleEmitter(
                world
                    .core
                    .get_particle_emitter(entity)
                    .cloned()
                    .map(Box::new),
            )),
            SnapshotKind::Decal => Some(Self::Decal(
                world.core.get_decal(entity).cloned().map(Box::new),
            )),
            SnapshotKind::AudioSource => Some(Self::AudioSource(
                world.core.get_audio_source(entity).cloned().map(Box::new),
            )),
            SnapshotKind::RigidBody => Some(Self::RigidBody(
                world.core.get_rigid_body(entity).cloned().map(Box::new),
            )),
            SnapshotKind::Collider => Some(Self::Collider(
                world.core.get_collider(entity).cloned().map(Box::new),
            )),
            SnapshotKind::CharacterController => Some(Self::CharacterController(
                world
                    .core
                    .get_character_controller(entity)
                    .cloned()
                    .map(Box::new),
            )),
            SnapshotKind::NavmeshAgent => Some(Self::NavmeshAgent(
                world.core.get_navmesh_agent(entity).cloned().map(Box::new),
            )),
            SnapshotKind::Camera => Some(Self::Camera(world.core.get_camera(entity).copied())),
            SnapshotKind::AnimationPlayer => Some(Self::AnimationPlayer(
                world
                    .core
                    .get_animation_player(entity)
                    .cloned()
                    .map(Box::new),
            )),
            SnapshotKind::Material => {
                let material_ref = world.core.get_material_ref(entity)?;
                let name = material_ref.name.clone();
                let material = nightshade::ecs::material::resources::material_registry_iter(
                    &world.resources.assets.material_registry,
                )
                .find(|(entry_name, _)| entry_name.as_str() == name.as_str())
                .map(|(_, material)| Box::new(material.clone()));
                Some(Self::Material { name, material })
            }
            SnapshotKind::Text => {
                let component = world.core.get_text(entity).cloned().map(Box::new);
                let content = component
                    .as_ref()
                    .and_then(|text| world.resources.text.cache.get_text(text.text_index))
                    .map(str::to_string);
                Some(Self::Text { component, content })
            }
            SnapshotKind::RenderLayer => Some(Self::RenderLayer(
                world.core.get_render_layer(entity).copied(),
            )),
            SnapshotKind::CullingMask => Some(Self::CullingMask(
                world.core.get_culling_mask(entity).copied(),
            )),
            SnapshotKind::CameraCullingMask => Some(Self::CameraCullingMask(
                world.core.get_camera_culling_mask(entity).copied(),
            )),
            SnapshotKind::IgnoreParentScale => Some(Self::IgnoreParentScale(
                world.core.entity_has_ignore_parent_scale(entity),
            )),
            SnapshotKind::PrefabSource => Some(Self::PrefabSource(
                world.core.get_prefab_source(entity).cloned().map(Box::new),
            )),
        }
    }

    pub fn apply(&self, world: &mut World, entity: Entity) {
        use nightshade::ecs::world::{
            ANIMATION_PLAYER, AUDIO_SOURCE, CAMERA, CAMERA_CULLING_MASK, CASTS_SHADOW, COLLIDER,
            CULLING_MASK, DECAL, IGNORE_PARENT_SCALE, NAVMESH_AGENT, PARTICLE_EMITTER,
            RENDER_LAYER, RIGID_BODY, TEXT, VISIBILITY,
        };
        match self {
            Self::Light(light) => {
                if let Some(target) = world.core.get_light_mut(entity) {
                    *target = light.clone();
                }
            }
            Self::Visibility(visible) => {
                world.core.add_components(entity, VISIBILITY);
                world
                    .core
                    .set_visibility(entity, Visibility { visible: *visible });
            }
            Self::CastsShadow(value) => {
                if *value {
                    world.core.add_components(entity, CASTS_SHADOW);
                    world.core.set_casts_shadow(entity, CastsShadow);
                } else {
                    world.core.remove_components(entity, CASTS_SHADOW);
                }
            }
            Self::Name(text) => {
                world.core.set_name(entity, Name(text.clone()));
            }
            Self::ParticleEmitter(value) => {
                apply_option_component(
                    world,
                    entity,
                    value.as_deref().cloned(),
                    PARTICLE_EMITTER,
                    |world, entity, value| world.core.set_particle_emitter(entity, value),
                );
            }
            Self::Decal(value) => {
                apply_option_component(
                    world,
                    entity,
                    value.as_deref().cloned(),
                    DECAL,
                    |world, entity, value| world.core.set_decal(entity, value),
                );
            }
            Self::AudioSource(value) => {
                apply_option_component(
                    world,
                    entity,
                    value.as_deref().cloned(),
                    AUDIO_SOURCE,
                    |world, entity, value| world.core.set_audio_source(entity, value),
                );
            }
            Self::RigidBody(value) => {
                apply_option_component(
                    world,
                    entity,
                    value.as_deref().cloned(),
                    RIGID_BODY,
                    |world, entity, value| world.core.set_rigid_body(entity, value),
                );
            }
            Self::Collider(value) => {
                apply_option_component(
                    world,
                    entity,
                    value.as_deref().cloned(),
                    COLLIDER,
                    |world, entity, value| world.core.set_collider(entity, value),
                );
            }
            Self::CharacterController(value) => {
                apply_option_component(
                    world,
                    entity,
                    value.as_deref().cloned(),
                    nightshade::ecs::world::CHARACTER_CONTROLLER,
                    |world, entity, value| world.core.set_character_controller(entity, value),
                );
            }
            Self::NavmeshAgent(value) => {
                apply_option_component(
                    world,
                    entity,
                    value.as_deref().cloned(),
                    NAVMESH_AGENT,
                    |world, entity, value| world.core.set_navmesh_agent(entity, value),
                );
            }
            Self::Camera(value) => {
                apply_option_component(world, entity, *value, CAMERA, |world, entity, value| {
                    world.core.set_camera(entity, value)
                });
            }
            Self::AnimationPlayer(value) => {
                apply_option_component(
                    world,
                    entity,
                    value.as_deref().cloned(),
                    ANIMATION_PLAYER,
                    |world, entity, value| world.core.set_animation_player(entity, value),
                );
            }
            Self::Material { name, material } => {
                if let Some(material) = material.as_deref() {
                    queue_ecs_command(
                        world,
                        nightshade::ecs::world::commands::EcsCommand::ReloadMaterial {
                            name: name.clone(),
                            material: Box::new(material.clone()),
                        },
                    );
                }
            }
            Self::Text { component, content } => {
                apply_option_component(
                    world,
                    entity,
                    component.as_deref().cloned(),
                    TEXT,
                    |world, entity, value| world.core.set_text(entity, value),
                );
                if let (Some(component), Some(content)) = (component.as_deref(), content.as_deref())
                {
                    world
                        .resources
                        .text
                        .cache
                        .set_text(component.text_index, content);
                }
            }
            Self::RenderLayer(value) => {
                apply_option_component(
                    world,
                    entity,
                    *value,
                    RENDER_LAYER,
                    |world, entity, value| world.core.set_render_layer(entity, value),
                );
            }
            Self::CullingMask(value) => {
                apply_option_component(
                    world,
                    entity,
                    *value,
                    CULLING_MASK,
                    |world, entity, value| world.core.set_culling_mask(entity, value),
                );
            }
            Self::CameraCullingMask(value) => {
                apply_option_component(
                    world,
                    entity,
                    *value,
                    CAMERA_CULLING_MASK,
                    |world, entity, value| world.core.set_camera_culling_mask(entity, value),
                );
            }
            Self::IgnoreParentScale(present) => {
                if *present {
                    world.core.add_components(entity, IGNORE_PARENT_SCALE);
                    world.core.set_ignore_parent_scale(
                        entity,
                        nightshade::ecs::transform::components::IgnoreParentScale,
                    );
                } else {
                    world.core.remove_components(entity, IGNORE_PARENT_SCALE);
                }
            }
            Self::PrefabSource(value) => {
                apply_option_component(
                    world,
                    entity,
                    value.as_deref().cloned(),
                    nightshade::ecs::world::PREFAB_SOURCE,
                    |world, entity, value| world.core.set_prefab_source(entity, value),
                );
            }
        }
    }
}

/// Drops the matching component mask off `entity` and re-adds the
/// component when `value` is present, restoring whichever state the
/// snapshot captured. Used by every `ComponentSnapshot` variant whose
/// component is optional on the entity.
fn apply_option_component<T, F>(
    world: &mut World,
    entity: Entity,
    value: Option<T>,
    mask: u64,
    set: F,
) where
    F: FnOnce(&mut World, Entity, T),
{
    if let Some(value) = value {
        world.core.add_components(entity, mask);
        set(world, entity, value);
    } else {
        world.core.remove_components(entity, mask);
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum SnapshotKind {
    Light,
    Visibility,
    CastsShadow,
    Name,
    ParticleEmitter,
    Decal,
    AudioSource,
    RigidBody,
    Collider,
    CharacterController,
    NavmeshAgent,
    Camera,
    AnimationPlayer,
    Material,
    Text,
    RenderLayer,
    CullingMask,
    CameraCullingMask,
    IgnoreParentScale,
    PrefabSource,
}

#[derive(Clone)]
pub struct CapturedSubtree {
    /// Mini-Scene whose entities cover the captured root and every
    /// descendant. Uuids match the live editor's identities, so the
    /// op survives a despawn-respawn round trip without any entity-id
    /// remapping in the rest of the history stack.
    pub scene: Scene,
    /// Uuids of the captured roots, in spawn order.
    pub root_uuids: Vec<AssetUuid>,
    /// Parent uuid for each root at capture time, if any. Looked up
    /// against editor_scene on recreate so a root reattaches to
    /// whatever live entity carries that uuid now.
    pub root_parents: Vec<Option<AssetUuid>>,
}

impl Reversible for UndoableOperation {
    type Context<'a> = UndoCtx<'a>;
    type Result = UndoResult;

    fn reverse(&self, ctx: &mut Self::Context<'_>) -> (Self, Self::Result) {
        match self {
            UndoableOperation::EntityCreated { captured } => {
                for uuid in &captured.root_uuids {
                    if let Some(entity) = ctx.editor_scene.entity_for(*uuid) {
                        scene_writeback::remove_entity(ctx.project, ctx.editor_scene, entity);
                        for descendant in query_descendants(ctx.world, entity) {
                            scene_writeback::remove_entity(
                                ctx.project,
                                ctx.editor_scene,
                                descendant,
                            );
                        }
                        despawn_recursive_immediate(ctx.world, entity);
                    }
                }
                (
                    UndoableOperation::EntityDeleted {
                        captured: captured.clone(),
                    },
                    UndoResult::default(),
                )
            }
            UndoableOperation::EntityDeleted { captured } => {
                let restored_first = recreate_subtree(ctx, captured);
                (
                    UndoableOperation::EntityCreated {
                        captured: captured.clone(),
                    },
                    UndoResult {
                        select_entity: restored_first,
                    },
                )
            }
            UndoableOperation::TransformChanged { uuid, old, new } => {
                let entity = ctx.editor_scene.entity_for(*uuid);
                if let Some(entity) = entity {
                    ctx.world.core.set_local_transform(entity, *old);
                    mark_local_transform_dirty(ctx.world, entity);
                    scene_writeback::sync_entity(ctx.project, ctx.editor_scene, ctx.world, entity);
                }
                (
                    UndoableOperation::TransformChanged {
                        uuid: *uuid,
                        old: *new,
                        new: *old,
                    },
                    UndoResult {
                        select_entity: entity,
                    },
                )
            }
            UndoableOperation::ComponentChanged { uuid, old, new } => {
                let entity = ctx.editor_scene.entity_for(*uuid);
                if let Some(entity) = entity {
                    old.apply(ctx.world, entity);
                    scene_writeback::sync_entity(ctx.project, ctx.editor_scene, ctx.world, entity);
                }
                (
                    UndoableOperation::ComponentChanged {
                        uuid: *uuid,
                        old: new.clone(),
                        new: old.clone(),
                    },
                    UndoResult {
                        select_entity: entity,
                    },
                )
            }
            UndoableOperation::Batch { steps } => {
                let mut reversed_steps: Vec<UndoableOperation> = Vec::with_capacity(steps.len());
                let mut final_result = UndoResult::default();
                for step in steps.iter().rev() {
                    let (reversed, result) = step.reverse(ctx);
                    if result.select_entity.is_some() {
                        final_result = result;
                    }
                    reversed_steps.push(reversed);
                }
                (
                    UndoableOperation::Batch {
                        steps: reversed_steps,
                    },
                    final_result,
                )
            }
        }
    }
}

/// Capture an entity and all its descendants into a CapturedSubtree
/// suitable for use in an EntityCreated/Deleted op. Allocates new
/// uuids for any entity that doesn't already have one in editor_scene
/// (so the capture is self-describing for later replay).
pub fn capture_subtree(
    world: &World,
    editor_scene: &mut EditorScene,
    root: Entity,
) -> CapturedSubtree {
    let mut order: Vec<Entity> = vec![root];
    order.extend(query_descendants(world, root));

    let mut uuids: std::collections::HashMap<Entity, AssetUuid> = std::collections::HashMap::new();
    for entity in &order {
        let uuid = match editor_scene.uuid_for(*entity) {
            Some(uuid) => uuid,
            None => AssetUuid::random(),
        };
        uuids.insert(*entity, uuid);
    }

    let mut scene = Scene::new("undo_capture");
    for entity in &order {
        let uuid = uuids[entity];
        let parent_entity = world.core.get_parent(*entity).and_then(|parent| parent.0);
        let parent_uuid = parent_entity.and_then(|parent| uuids.get(&parent).copied());
        let scene_entity = entity_to_scene_entity(world, *entity, uuid, parent_uuid);
        scene.add_entity(scene_entity);
    }
    embed_referenced_meshes(world, &mut scene);
    embed_referenced_textures(world, &mut scene);
    scene.compute_spawn_order();

    let root_uuid = uuids[&root];
    let root_parent = world
        .core
        .get_parent(root)
        .and_then(|parent| parent.0)
        .and_then(|parent| editor_scene.uuid_for(parent));

    CapturedSubtree {
        scene,
        root_uuids: vec![root_uuid],
        root_parents: vec![root_parent],
    }
}

fn recreate_subtree(ctx: &mut UndoCtx<'_>, captured: &CapturedSubtree) -> Option<Entity> {
    let saved_atmosphere = ctx.world.resources.render_settings.atmosphere;
    let saved_settings = capture_scene_settings(ctx.world);

    let spawn_outcome = spawn_scene(ctx.world, &captured.scene, None);

    ctx.world.resources.render_settings.atmosphere = saved_atmosphere;
    apply_scene_settings(ctx.world, &saved_settings);

    let result = match spawn_outcome {
        Ok(result) => result,
        Err(error) => {
            tracing::error!("undo recreate failed: {error}");
            return None;
        }
    };

    for (uuid, entity) in &result.uuid_to_entity {
        ctx.editor_scene.insert(*uuid, *entity);
    }

    for (root_uuid, parent_uuid) in captured.root_uuids.iter().zip(captured.root_parents.iter()) {
        let Some(child) = result.uuid_to_entity.get(root_uuid).copied() else {
            continue;
        };
        let parent_entity = parent_uuid.and_then(|uuid| ctx.editor_scene.entity_for(uuid));
        if let Some(parent) = parent_entity {
            ctx.world.core.set_parent(
                child,
                nightshade::ecs::transform::components::Parent(Some(parent)),
            );
        }
    }

    for entity in result.uuid_to_entity.values() {
        scene_writeback::add_entity(ctx.project, ctx.editor_scene, ctx.world, *entity);
    }

    captured
        .root_uuids
        .first()
        .and_then(|uuid| result.uuid_to_entity.get(uuid).copied())
}

pub type UndoHistory = History<UndoableOperation>;

/// Tracks the engine gizmo's drag state across frames so the editor
/// can capture a TransformChanged op on the press → release boundary.
#[derive(Default)]
pub struct GizmoDragTracker {
    prev_active: bool,
    tracked_uuid: Option<AssetUuid>,
    start_transform: Option<LocalTransform>,
}

pub fn poll_gizmo_drag(
    tracker: &mut GizmoDragTracker,
    history: &mut UndoHistory,
    editor_scene: &EditorScene,
    world: &mut World,
) {
    let gizmos = &world.resources.user_interface.gizmos;
    let active = gizmos.translation_drag.is_some()
        || gizmos.scale_drag.is_some()
        || gizmos.rotation_drag.is_some()
        || gizmos.planar_translation_drag.is_some()
        || gizmos.planar_scale_drag.is_some();

    let target = world
        .resources
        .editor_selection
        .bounding_volume_selected_entity;

    if active && !tracker.prev_active {
        if let Some(entity) = target
            && let Some(uuid) = editor_scene.uuid_for(entity)
            && let Some(transform) = world.core.get_local_transform(entity).copied()
        {
            tracker.tracked_uuid = Some(uuid);
            tracker.start_transform = Some(transform);
        } else {
            tracker.tracked_uuid = None;
            tracker.start_transform = None;
        }
    } else if !active
        && tracker.prev_active
        && let (Some(uuid), Some(start)) =
            (tracker.tracked_uuid.take(), tracker.start_transform.take())
        && let Some(entity) = editor_scene.entity_for(uuid)
        && let Some(end) = world.core.get_local_transform(entity).copied()
        && start != end
    {
        history.push(
            UndoableOperation::TransformChanged {
                uuid,
                old: start,
                new: end,
            },
            "Transform",
        );
    }

    tracker.prev_active = active;
}
