//! Concrete editor commands that mutate the [`EditorState`](crate::state::EditorState).
//!
//! Routed through the [`CommandStack`](crate::command::CommandStack) so every
//! one is undoable through the Edit menu, `Ctrl+Z`/`Ctrl+Y`, and the same path
//! that future tools (transform gizmos, sculpt, paint) will use.
//!
//! Piece 6 ships the **first** commands so the undo plumbing is exercised
//! end-to-end:
//!
//! - [`SetTransformCmd`] — change an entity's local [`Transform`]. Consecutive
//!   edits to the same entity coalesce via [`Command::merge`] so a slider drag
//!   or a (future) gizmo drag becomes one undo entry.
//! - [`RenameCmd`] — rename an entity.
//!
//! Spawn/despawn aren't wired yet: round-tripping a despawn would need stable
//! entity ids across re-spawn (the scene reuses ids), which is a Stage-7
//! design step. The hierarchy panel still offers Add/Delete; they bypass the
//! stack today and are clearly labeled as "not undoable" in the shell.

use std::any::Any;

use oxide_engine::prelude::*;

use crate::command::Command;
use crate::state::EditorState;

/// Replaces the open UI document's panel wholesale (widget tree + sizes).
///
/// The UI canvas snapshots the panel before an edit and again after, so any
/// structural change (add / remove / move a widget) or property change goes
/// through one undoable command without per-operation bookkeeping. A panel is a
/// small data tree, so cloning it for the snapshots is cheap.
pub struct SetUiPanelCmd {
    /// The panel before the edit.
    pub before: UiPanel,
    /// The panel after the edit.
    pub after: UiPanel,
    /// Human-readable description for the Edit menu.
    pub label: String,
}

impl Command<EditorState> for SetUiPanelCmd {
    fn apply(&mut self, state: &mut EditorState) {
        if let Some(doc) = &mut state.ui_doc {
            doc.panel = self.after.clone();
            doc.dirty = true;
        }
    }

    fn undo(&mut self, state: &mut EditorState) {
        if let Some(doc) = &mut state.ui_doc {
            doc.panel = self.before.clone();
            doc.dirty = true;
        }
    }

    fn label(&self) -> String {
        self.label.clone()
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

/// Replaces an entity's local [`Transform`]. Coalesces consecutive edits to
/// the same entity so an interactive drag is one undo entry.
pub struct SetTransformCmd {
    pub entity: Entity,
    /// The transform before the first apply — preserved through merges so
    /// undo reverses the whole drag at once.
    pub before: Transform,
    /// The transform after the most recent apply.
    pub after: Transform,
}

impl SetTransformCmd {
    /// Builds the command, snapshotting the entity's current transform as the
    /// pre-edit state. Returns `None` if the entity has no transform (e.g. it
    /// was just despawned).
    pub fn new(state: &EditorState, entity: Entity, after: Transform) -> Option<Self> {
        let before = state.scene.local_transform(entity)?;
        Some(Self {
            entity,
            before,
            after,
        })
    }
}

impl Command<EditorState> for SetTransformCmd {
    fn apply(&mut self, state: &mut EditorState) {
        state.scene.set_local_transform(self.entity, self.after);
    }

    fn undo(&mut self, state: &mut EditorState) {
        state.scene.set_local_transform(self.entity, self.before);
    }

    fn label(&self) -> String {
        "Edit Transform".to_owned()
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }

    fn merge(&mut self, next: &mut dyn Command<EditorState>) -> bool {
        let Some(next) = next.as_any_mut().downcast_mut::<SetTransformCmd>() else {
            return false;
        };
        if next.entity != self.entity {
            return false;
        }
        // Absorb `next` by extending our `after` while preserving `before`,
        // so a long drag remains a single undo step.
        self.after = next.after;
        true
    }
}

/// Sets a single **reflected field** of a component on an entity, addressed by
/// type name + field name and carried as RON.
///
/// This is the generic counterpart to [`SetTransformCmd`]: the
/// reflection-driven inspector emits one of these for *any* registered
/// component's field, so a new component type becomes undoably editable with no
/// new command type. Consecutive edits to the same `(entity, type, field)`
/// coalesce via [`Command::merge`], so dragging a value slider is one undo
/// entry.
pub struct SetFieldCmd {
    pub entity: Entity,
    /// The registered type name (e.g. `"Transform"`).
    pub type_name: &'static str,
    /// The reflected field name (e.g. `"translation"`).
    pub field: &'static str,
    /// The field's RON before the first apply — preserved through merges.
    pub before: String,
    /// The field's RON after the most recent apply.
    pub after: String,
}

impl SetFieldCmd {
    /// Builds the command, snapshotting the field's current RON as the
    /// pre-edit state. Returns `None` if the field can't be read (unknown
    /// type/field, or the entity lacks the component).
    pub fn new(
        state: &EditorState,
        entity: Entity,
        type_name: &'static str,
        field: &'static str,
        after: String,
    ) -> Option<Self> {
        let before = state
            .registry
            .get_field(state.scene.world(), entity, type_name, field)
            .ok()?;
        Some(Self {
            entity,
            type_name,
            field,
            before,
            after,
        })
    }
}

impl Command<EditorState> for SetFieldCmd {
    fn apply(&mut self, state: &mut EditorState) {
        // Disjoint borrows of EditorState: &registry (receiver) + &mut scene
        // (the world). A write only fails if the entity/component vanished
        // between snapshot and apply, in which case there's nothing to do.
        let _ = state.registry.set_field(
            state.scene.world_mut(),
            self.entity,
            self.type_name,
            self.field,
            &self.after,
        );
    }

    fn undo(&mut self, state: &mut EditorState) {
        let _ = state.registry.set_field(
            state.scene.world_mut(),
            self.entity,
            self.type_name,
            self.field,
            &self.before,
        );
    }

    fn label(&self) -> String {
        format!("Edit {}.{}", self.type_name, self.field)
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }

    fn merge(&mut self, next: &mut dyn Command<EditorState>) -> bool {
        let Some(next) = next.as_any_mut().downcast_mut::<SetFieldCmd>() else {
            return false;
        };
        // Only coalesce edits to the *same* field of the same component on the
        // same entity; preserve `before` so undo reverses the whole drag.
        if next.entity != self.entity
            || next.type_name != self.type_name
            || next.field != self.field
        {
            return false;
        }
        self.after = std::mem::take(&mut next.after);
        true
    }
}

/// Renames an entity.
pub struct RenameCmd {
    pub entity: Entity,
    pub before: String,
    pub after: String,
}

impl RenameCmd {
    /// Snapshots the entity's current name as the pre-edit state.
    pub fn new(state: &EditorState, entity: Entity, after: String) -> Self {
        let before = state.scene.name(entity).unwrap_or_default();
        Self {
            entity,
            before,
            after,
        }
    }
}

impl Command<EditorState> for RenameCmd {
    fn apply(&mut self, state: &mut EditorState) {
        state.scene.set_name(self.entity, self.after.clone());
    }

    fn undo(&mut self, state: &mut EditorState) {
        state.scene.set_name(self.entity, self.before.clone());
    }

    fn label(&self) -> String {
        format!("Rename to '{}'", self.after)
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::command::CommandStack;

    fn state_with_entity() -> (EditorState, Entity) {
        let mut state = EditorState::new();
        let e = state
            .scene
            .spawn("alpha", Transform::from_translation(Vec3::ZERO));
        (state, e)
    }

    #[test]
    fn transform_undo_redo_round_trips() {
        let (mut state, e) = state_with_entity();
        let target = Transform::from_translation(Vec3::new(1.0, 2.0, 3.0));
        let cmd = SetTransformCmd::new(&state, e, target).expect("transform present");

        let mut stack: CommandStack<EditorState> = CommandStack::with_capacity(32);
        stack.push(cmd, &mut state);
        assert_eq!(
            state.scene.local_transform(e).unwrap().translation,
            target.translation
        );

        assert!(stack.undo(&mut state).is_some());
        assert_eq!(
            state.scene.local_transform(e).unwrap().translation,
            Vec3::ZERO
        );

        assert!(stack.redo(&mut state).is_some());
        assert_eq!(
            state.scene.local_transform(e).unwrap().translation,
            target.translation
        );
    }

    #[test]
    fn consecutive_transform_edits_coalesce_into_one_undo() {
        // Mirrors the "interactive drag" case: dozens of per-frame edits, one
        // undo step that returns to the pre-drag state.
        let (mut state, e) = state_with_entity();
        let mut stack: CommandStack<EditorState> = CommandStack::with_capacity(32);

        for step in 1..=5 {
            let target = Transform::from_translation(Vec3::splat(step as f32));
            let cmd = SetTransformCmd::new(&state, e, target).unwrap();
            stack.push(cmd, &mut state);
        }
        assert_eq!(
            state.scene.local_transform(e).unwrap().translation,
            Vec3::splat(5.0)
        );

        // A single undo wipes the whole drag — that's the merge contract.
        assert!(stack.undo(&mut state).is_some());
        assert_eq!(
            state.scene.local_transform(e).unwrap().translation,
            Vec3::ZERO
        );
    }

    #[test]
    fn set_field_undo_redo_round_trips() {
        let (mut state, e) = state_with_entity();
        let cmd = SetFieldCmd::new(
            &state,
            e,
            "Transform",
            "translation",
            "(1.0,2.0,3.0)".into(),
        )
        .expect("transform field readable");

        let mut stack: CommandStack<EditorState> = CommandStack::with_capacity(32);
        stack.push(cmd, &mut state);
        assert_eq!(
            state.scene.local_transform(e).unwrap().translation,
            Vec3::new(1.0, 2.0, 3.0)
        );

        assert!(stack.undo(&mut state).is_some());
        assert_eq!(
            state.scene.local_transform(e).unwrap().translation,
            Vec3::ZERO
        );

        assert!(stack.redo(&mut state).is_some());
        assert_eq!(
            state.scene.local_transform(e).unwrap().translation,
            Vec3::new(1.0, 2.0, 3.0)
        );
    }

    #[test]
    fn consecutive_field_edits_to_same_field_coalesce() {
        // A value-slider drag: many per-frame edits, one undo back to start.
        let (mut state, e) = state_with_entity();
        let mut stack: CommandStack<EditorState> = CommandStack::with_capacity(32);
        for step in 1..=5 {
            let ron = format!("({0}.0,{0}.0,{0}.0)", step);
            let cmd = SetFieldCmd::new(&state, e, "Transform", "translation", ron).unwrap();
            stack.push(cmd, &mut state);
        }
        assert_eq!(
            state.scene.local_transform(e).unwrap().translation,
            Vec3::splat(5.0)
        );
        assert!(stack.undo(&mut state).is_some());
        assert_eq!(
            state.scene.local_transform(e).unwrap().translation,
            Vec3::ZERO
        );
    }

    #[test]
    fn set_field_edits_to_different_fields_do_not_coalesce() {
        // Editing translation then scale must be two undo steps, not one.
        let (mut state, e) = state_with_entity();
        let mut stack: CommandStack<EditorState> = CommandStack::with_capacity(32);
        stack.push(
            SetFieldCmd::new(
                &state,
                e,
                "Transform",
                "translation",
                "(1.0,0.0,0.0)".into(),
            )
            .unwrap(),
            &mut state,
        );
        stack.push(
            SetFieldCmd::new(&state, e, "Transform", "scale", "(2.0,2.0,2.0)".into()).unwrap(),
            &mut state,
        );
        // Undo reverses scale only.
        assert!(stack.undo(&mut state).is_some());
        let t = state.scene.local_transform(e).unwrap();
        assert_eq!(t.scale, Vec3::ONE);
        assert_eq!(t.translation, Vec3::new(1.0, 0.0, 0.0));
        // A second undo reverses translation.
        assert!(stack.undo(&mut state).is_some());
        assert_eq!(
            state.scene.local_transform(e).unwrap().translation,
            Vec3::ZERO
        );
    }

    #[test]
    fn rename_undo_restores_previous_name() {
        let (mut state, e) = state_with_entity();
        let cmd = RenameCmd::new(&state, e, "beta".into());
        let mut stack: CommandStack<EditorState> = CommandStack::with_capacity(32);
        stack.push(cmd, &mut state);
        assert_eq!(state.scene.name(e).as_deref(), Some("beta"));

        assert!(stack.undo(&mut state).is_some());
        assert_eq!(state.scene.name(e).as_deref(), Some("alpha"));
    }
}
