//! Response types and helpers for dear-imgui-reflect.
//!
//! This module defines [`ReflectResponse`] and [`ReflectEvent`], a lightweight
//! analogue to ImReflect's `ImResponse` that focuses on container-structure
//! changes (insert/remove/reorder/rename). It also provides internal helpers
//! used by the derive macro and container editors.

use std::borrow::Cow;
use std::cell::RefCell;

/// High-level response information collected during a reflection-driven UI pass.
///
/// This is a lightweight, ImReflect-style response object that records
/// container-level structural edits (insert/remove/reorder/rename) while a
/// reflected editor is rendered. It is designed to complement the boolean
/// return value from `input`, which already reports whether any value was
/// modified, without changing existing APIs.
#[derive(Default, Debug)]
pub struct ReflectResponse {
    events: Vec<ReflectEvent>,
}

impl ReflectResponse {
    /// Returns `true` if no events were recorded during the last input pass.
    pub fn is_empty(&self) -> bool {
        self.events.is_empty()
    }

    /// Returns a slice of all events recorded so far.
    pub fn events(&self) -> &[ReflectEvent] {
        &self.events
    }

    /// Clears all recorded events.
    pub fn clear(&mut self) {
        self.events.clear();
    }
}

/// A single structural change observed while rendering reflected UI.
///
/// These events focus on container structure (insert/remove/reorder/rename)
/// rather than low-level pointer or interaction details, providing a
/// simplified analogue to ImReflect's richer `ImResponse` type.
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum ReflectEvent {
    /// A vector had an element inserted at the given index.
    VecInserted {
        /// Logical field path associated with the vector, if known.
        path: Option<String>,
        /// Index where the new element was inserted.
        index: usize,
    },
    /// A vector element was removed from the given index.
    VecRemoved {
        /// Logical field path associated with the vector, if known.
        path: Option<String>,
        /// Index from which the element was removed.
        index: usize,
    },
    /// A vector element was moved from `from` to `to` (indices in the final layout).
    VecReordered {
        /// Logical field path associated with the vector, if known.
        path: Option<String>,
        /// Original index of the moved element.
        from: usize,
        /// Final index of the moved element after reordering.
        to: usize,
    },
    /// All elements were removed from a vector that previously contained `previous_len` items.
    VecCleared {
        /// Logical field path associated with the vector, if known.
        path: Option<String>,
        /// Number of elements that were present before the clear operation.
        previous_len: usize,
    },
    /// A fixed-size array had two elements swapped.
    ArrayReordered {
        /// Logical field path associated with the array, if known.
        path: Option<String>,
        /// First index in the swap operation.
        from: usize,
        /// Second index in the swap operation.
        to: usize,
    },
    /// A map entry with the given key was inserted.
    MapInserted {
        /// Logical field path associated with the map, if known.
        path: Option<String>,
        /// Key for the newly inserted entry.
        key: String,
    },
    /// A map entry with the given key was removed.
    MapRemoved {
        /// Logical field path associated with the map, if known.
        path: Option<String>,
        /// Key for the removed entry.
        key: String,
    },
    /// A map entry key was renamed from `from` to `to`.
    MapRenamed {
        /// Logical field path associated with the map, if known.
        path: Option<String>,
        /// Original key of the entry.
        from: String,
        /// New key assigned to the entry.
        to: String,
    },
    /// All entries were removed from a map that previously contained `previous_len` items.
    MapCleared {
        /// Logical field path associated with the map, if known.
        path: Option<String>,
        /// Number of entries that were present before the clear operation.
        previous_len: usize,
    },
}

thread_local! {
    /// Stack of active response collectors for the current thread.
    ///
    /// This allows `input_with_response` to temporarily install a
    /// [`ReflectResponse`] that container editors can emit events into without
    /// changing existing ImGuiValue/ImGuiReflect signatures.
    static CURRENT_RESPONSE: RefCell<Vec<*mut ReflectResponse>> = const { RefCell::new(Vec::new()) };

    /// Stack of logical field-path segments for the current thread.
    ///
    /// This is populated by the derive macro when rendering struct fields so
    /// that container events can be associated with a stable field path such
    /// as `"primitives.samples"`. Only code generated by the derive macro is
    /// expected to interact with this stack.
    static CURRENT_FIELD_PATH: RefCell<Vec<Cow<'static, str>>> = const { RefCell::new(Vec::new()) };
}

/// Executes `f` with `response` installed as the current response collector.
pub(crate) fn with_response<R, F>(response: &mut ReflectResponse, f: F) -> R
where
    F: FnOnce() -> R,
{
    struct ResponseGuard;

    impl Drop for ResponseGuard {
        fn drop(&mut self) {
            CURRENT_RESPONSE.with(|stack| {
                if let Ok(mut stack) = stack.try_borrow_mut() {
                    stack.pop();
                }
            });
        }
    }

    CURRENT_RESPONSE.with(|stack| {
        stack.borrow_mut().push(response as *mut _);
    });
    let _guard = ResponseGuard;
    f()
}

/// Returns the current logical field path if one is active.
pub(crate) fn current_field_path() -> Option<String> {
    CURRENT_FIELD_PATH.with(|stack| {
        let stack = stack.borrow();
        if stack.is_empty() {
            None
        } else {
            let mut path = String::new();
            for (i, segment) in stack.iter().enumerate() {
                let segment = segment.as_ref();
                if i > 0 && !segment.starts_with('[') {
                    path.push('.');
                }
                path.push_str(segment);
            }
            Some(path)
        }
    })
}

pub(crate) fn is_field_path_active() -> bool {
    CURRENT_FIELD_PATH.with(|stack| !stack.borrow().is_empty())
}

/// Pushes a field-path segment for the duration of the provided closure.
///
/// This is intended for use by code generated from the derive macro; regular
/// users should prefer the higher-level `input_with_response` API.
#[doc(hidden)]
pub fn with_field_path<R, F>(segment: &str, f: F) -> R
where
    F: FnOnce() -> R,
{
    struct FieldPathGuard;

    impl Drop for FieldPathGuard {
        fn drop(&mut self) {
            CURRENT_FIELD_PATH.with(|stack| {
                if let Ok(mut stack) = stack.try_borrow_mut() {
                    stack.pop();
                }
            });
        }
    }

    CURRENT_FIELD_PATH.with(|stack| {
        stack.borrow_mut().push(Cow::Owned(segment.to_owned()));
    });
    let _guard = FieldPathGuard;
    f()
}

/// Pushes a `'static` field-path segment for the duration of the provided closure.
///
/// This is an optimized variant of [`with_field_path`] used by code generated
/// from the derive macro to avoid per-frame string allocations.
#[doc(hidden)]
pub fn with_field_path_static<R, F>(segment: &'static str, f: F) -> R
where
    F: FnOnce() -> R,
{
    struct FieldPathGuard;

    impl Drop for FieldPathGuard {
        fn drop(&mut self) {
            CURRENT_FIELD_PATH.with(|stack| {
                if let Ok(mut stack) = stack.try_borrow_mut() {
                    stack.pop();
                }
            });
        }
    }

    CURRENT_FIELD_PATH.with(|stack| {
        stack.borrow_mut().push(Cow::Borrowed(segment));
    });
    let _guard = FieldPathGuard;
    f()
}

/// Records a new event into the currently active response collector, if any.
pub(crate) fn record_event(event: ReflectEvent) {
    CURRENT_RESPONSE.with(|stack| {
        if let Some(ptr) = stack.borrow().last().copied() {
            // SAFETY: pointers stored in CURRENT_RESPONSE are derived from
            // &mut ReflectResponse references passed to `with_response` and
            // remain valid for the duration of the closure.
            unsafe {
                (&mut *ptr).events.push(event);
            }
        }
    });
}

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

    #[test]
    fn collects_events_in_order() {
        let mut resp = ReflectResponse::default();

        with_response(&mut resp, || {
            record_event(ReflectEvent::VecInserted {
                path: None,
                index: 1,
            });
            record_event(ReflectEvent::VecRemoved {
                path: None,
                index: 2,
            });
        });

        let events = resp.events();
        assert_eq!(events.len(), 2);

        match &events[0] {
            ReflectEvent::VecInserted { index, path } => {
                assert_eq!(*index, 1);
                assert!(path.is_none());
            }
            other => panic!("unexpected first event: {other:?}"),
        }

        match &events[1] {
            ReflectEvent::VecRemoved { index, path } => {
                assert_eq!(*index, 2);
                assert!(path.is_none());
            }
            other => panic!("unexpected second event: {other:?}"),
        }
    }

    #[test]
    fn field_path_stack_builds_nested_paths() {
        let mut resp = ReflectResponse::default();

        with_response(&mut resp, || {
            // No active field path.
            record_event(ReflectEvent::VecInserted {
                path: current_field_path(),
                index: 0,
            });

            // Single segment.
            with_field_path("outer", || {
                record_event(ReflectEvent::VecInserted {
                    path: current_field_path(),
                    index: 1,
                });

                // Nested segment.
                with_field_path("inner[0]", || {
                    record_event(ReflectEvent::VecInserted {
                        path: current_field_path(),
                        index: 2,
                    });
                });
            });
        });

        let events = resp.events();
        assert_eq!(events.len(), 3);

        match &events[0] {
            ReflectEvent::VecInserted { path, index } => {
                assert_eq!(*index, 0);
                assert!(path.is_none());
            }
            other => panic!("unexpected first event: {other:?}"),
        }

        match &events[1] {
            ReflectEvent::VecInserted { path, index } => {
                assert_eq!(*index, 1);
                assert_eq!(path.as_deref(), Some("outer"));
            }
            other => panic!("unexpected second event: {other:?}"),
        }

        match &events[2] {
            ReflectEvent::VecInserted { path, index } => {
                assert_eq!(*index, 2);
                assert_eq!(path.as_deref(), Some("outer.inner[0]"));
            }
            other => panic!("unexpected third event: {other:?}"),
        }
    }

    #[test]
    fn response_and_field_path_stacks_restore_on_panic() {
        let mut resp = ReflectResponse::default();

        let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            with_response(&mut resp, || {
                with_field_path("a", || {
                    record_event(ReflectEvent::VecInserted {
                        path: current_field_path(),
                        index: 1,
                    });
                    panic!("boom");
                });
            });
        }));

        // Stacks must be clean after unwind.
        assert!(current_field_path().is_none());
        record_event(ReflectEvent::VecInserted {
            path: None,
            index: 2,
        });
        assert_eq!(resp.events.len(), 1);
    }

    #[test]
    fn field_path_segments_starting_with_bracket_do_not_insert_dots() {
        let mut resp = ReflectResponse::default();

        with_response(&mut resp, || {
            with_field_path("outer", || {
                with_field_path("[0]", || {
                    record_event(ReflectEvent::VecInserted {
                        path: current_field_path(),
                        index: 0,
                    });
                });
                with_field_path("[1]", || {
                    with_field_path("inner", || {
                        record_event(ReflectEvent::VecInserted {
                            path: current_field_path(),
                            index: 1,
                        });
                    });
                });
            });
        });

        let events = resp.events();
        assert_eq!(events.len(), 2);
        match &events[0] {
            ReflectEvent::VecInserted { path, .. } => assert_eq!(path.as_deref(), Some("outer[0]")),
            other => panic!("unexpected event: {other:?}"),
        }
        match &events[1] {
            ReflectEvent::VecInserted { path, .. } => {
                assert_eq!(path.as_deref(), Some("outer[1].inner"))
            }
            other => panic!("unexpected event: {other:?}"),
        }
    }
}
