#![forbid(unsafe_code)]

//! Modal stack for managing nested modals with proper z-ordering.
//!
//! The `ModalStack` manages multiple open modals in a LIFO (stack) order.
//! Only the topmost modal receives input events, while all modals are
//! rendered from bottom to top with appropriate backdrop dimming.
//!
//! # Invariants
//!
//! - Z-order is strictly increasing: later modals are always on top.
//! - Only the top modal receives input events.
//! - Close ordering is LIFO by default; pop-by-id removes from any position.
//! - Backdrop opacity is reduced for lower modals to create depth effect.
//!
//! # Failure Modes
//!
//! - `pop()` on empty stack returns `None` (no panic).
//! - `pop_id()` for non-existent ID returns `None`.
//! - `get()` / `get_mut()` for non-existent ID returns `None`.
//!
//! # Example
//!
//! ```ignore
//! let mut stack = ModalStack::new();
//!
//! // Push modals
//! let id1 = stack.push(ModalEntry::new(dialog1));
//! let id2 = stack.push(ModalEntry::new(dialog2));
//!
//! // Only top modal (id2) receives events
//! stack.handle_event(&event, None);
//!
//! // Render all modals in z-order
//! stack.render(frame, screen_area);
//!
//! // Pop top modal
//! let result = stack.pop(); // Returns id2's entry
//! ```

use ftui_core::event::Event;
use ftui_core::geometry::Rect;
use ftui_render::frame::{Frame, HitData, HitId, HitRegion, HitTestResult};
use ftui_style::Style;
use std::sync::atomic::{AtomicU64, Ordering};

use crate::focus::FocusId;
use crate::modal::{BackdropConfig, MODAL_HIT_BACKDROP, MODAL_HIT_CONTENT, ModalSizeConstraints};
use crate::set_style_area;

#[cfg(test)]
use super::focus_integration::{ModalFocusCoordinator, next_focus_group_id};

#[cfg(feature = "tracing")]
use web_time::Instant;

/// Base z-index for modal layer.
const BASE_MODAL_Z: u32 = 1000;

/// Z-index increment between modals (leaves room for internal layers).
const Z_INCREMENT: u32 = 10;

/// Global counter for unique modal IDs.
static MODAL_ID_COUNTER: AtomicU64 = AtomicU64::new(1);

/// Unique identifier for a modal in the stack.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ModalId(u64);

impl ModalId {
    /// Create a new unique modal ID.
    fn new() -> Self {
        Self(MODAL_ID_COUNTER.fetch_add(1, Ordering::Relaxed))
    }

    /// Get the raw ID value.
    #[inline]
    pub const fn id(self) -> u64 {
        self.0
    }
}

/// Result returned when a modal is closed.
#[derive(Debug, Clone)]
pub struct ModalResult {
    /// The modal ID that was closed.
    pub id: ModalId,
    /// Optional result data from the modal.
    pub data: Option<ModalResultData>,
    /// Focus group ID if one was associated (for calling `FocusManager::pop_trap`).
    pub focus_group_id: Option<u32>,
}

/// Modal result data variants.
#[derive(Debug, Clone)]
pub enum ModalResultData {
    /// Dialog was dismissed (escaped or cancelled).
    Dismissed,
    /// Dialog was confirmed.
    Confirmed,
    /// Dialog returned a custom value.
    Custom(String),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct FocusTrapSpec {
    pub group_id: u32,
    pub return_focus: Option<FocusId>,
}

/// A FocusId alias for modal focus management.
pub type ModalFocusId = u64;

/// Trait for modal content that can be managed in the stack.
///
/// This trait abstracts over different modal implementations (Dialog, custom modals)
/// so they can all be managed by the same stack.
///
/// # Focus Management (bd-39vx.5)
///
/// Modals can optionally participate in focus management by providing:
/// - `focusable_ids()`: List of focusable widget IDs within the modal
/// - `aria_modal()`: Whether this modal should be treated as an ARIA modal
///
/// When focus management is enabled, the caller should:
/// 1. Create a focus group from `focusable_ids()` when the modal opens
/// 2. Push a focus trap to constrain Tab navigation within the modal
/// 3. Auto-focus the first focusable widget
/// 4. Restore previous focus when the modal closes
pub trait StackModal: Send {
    /// A stable-ish label for this modal type, used for tracing/logging.
    ///
    /// Default: the Rust type name of the concrete modal implementation.
    fn modal_type(&self) -> &'static str {
        std::any::type_name::<Self>()
    }

    /// Render the modal content at the given area.
    fn render_content(&self, area: Rect, frame: &mut Frame);

    /// Handle an event, returning true if the modal should close.
    ///
    /// `hit` should be the last rendered hit-test result for the pointer location,
    /// if one exists. `hit_id` is the stack-assigned ID for this modal.
    fn handle_event(
        &mut self,
        event: &Event,
        hit: Option<(HitId, HitRegion, HitData)>,
        hit_id: HitId,
    ) -> Option<ModalResultData>;

    /// Get the modal's size constraints.
    fn size_constraints(&self) -> ModalSizeConstraints;

    /// Get the backdrop configuration.
    fn backdrop_config(&self) -> BackdropConfig;

    /// Whether this modal can be closed by pressing Escape.
    fn close_on_escape(&self) -> bool {
        true
    }

    /// Whether this modal can be closed by clicking the backdrop.
    fn close_on_backdrop(&self) -> bool {
        true
    }

    /// Whether this modal is an ARIA modal (accessibility semantic).
    ///
    /// ARIA modals:
    /// - Trap focus within the modal (Tab cannot escape)
    /// - Announce modal semantics to screen readers
    /// - Block interaction with content behind the modal
    ///
    /// Default: `true` for accessibility compliance.
    ///
    /// # Invariants
    /// - When `aria_modal()` returns true, focus MUST be trapped within the modal.
    /// - Screen readers should announce modal state changes.
    ///
    /// # Failure Modes
    /// - If focus trap is not configured, Tab may escape (accessibility violation).
    fn aria_modal(&self) -> bool {
        true
    }

    /// Get the IDs of focusable widgets within this modal.
    ///
    /// These IDs are used to create a focus group when the modal opens.
    /// The first ID in the list receives auto-focus.
    ///
    /// Returns `None` if focus management is not needed (e.g., non-interactive modals).
    ///
    /// # Example
    /// ```ignore
    /// fn focusable_ids(&self) -> Option<Vec<ModalFocusId>> {
    ///     Some(vec![
    ///         self.input_field_id,
    ///         self.confirm_button_id,
    ///         self.cancel_button_id,
    ///     ])
    /// }
    /// ```
    fn focusable_ids(&self) -> Option<Vec<ModalFocusId>> {
        None
    }
}

/// An active modal in the stack.
struct ActiveModal {
    /// Unique identifier for this modal.
    id: ModalId,
    /// Z-index for layering (reserved for future compositor integration).
    #[allow(dead_code)]
    z_index: u32,
    /// The modal content.
    modal: Box<dyn StackModal>,
    /// Hit ID for this modal's hit regions.
    hit_id: HitId,
    /// Focus group ID for focus trap integration.
    focus_group_id: Option<u32>,
    /// Focus target that should be restored when this modal closes.
    focus_return_focus: Option<FocusId>,
}

/// Stack of active modals with z-ordering and input routing.
///
/// # Invariants
///
/// - `modals` is ordered by z_index (lowest to highest).
/// - `next_z` always produces a z_index greater than any existing modal.
/// - Input is only routed to the top modal (last in the vec).
pub struct ModalStack {
    /// Active modals in z-order (bottom to top).
    modals: Vec<ActiveModal>,
    /// Next z-index to assign.
    next_z: u32,
    /// Next hit ID to assign.
    next_hit_id: u32,
}

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

impl ModalStack {
    /// Create an empty modal stack.
    pub fn new() -> Self {
        Self {
            modals: Vec::new(),
            next_z: 0,
            next_hit_id: 1000, // Start hit IDs high to avoid conflicts
        }
    }

    // --- Stack Operations ---

    /// Push a modal onto the stack.
    ///
    /// Returns the unique `ModalId` for the pushed modal.
    pub fn push(&mut self, modal: Box<dyn StackModal>) -> ModalId {
        self.push_with_focus(modal, None)
    }

    /// Push a modal with an associated focus group ID.
    ///
    /// The focus group ID is used to integrate with `FocusManager`:
    /// 1. Before calling this, create a focus group with `focus_manager.create_group(id, members)`
    /// 2. Then call `focus_manager.push_trap(id)` to trap focus within the modal
    /// 3. When the modal closes, call `focus_manager.pop_trap()` to restore focus
    ///
    /// Returns the unique `ModalId` for the pushed modal.
    pub fn push_with_focus(
        &mut self,
        modal: Box<dyn StackModal>,
        focus_group_id: Option<u32>,
    ) -> ModalId {
        #[cfg(feature = "tracing")]
        let modal_type = modal.modal_type();
        #[cfg(feature = "tracing")]
        let focus_trapped = focus_group_id.is_some() && modal.aria_modal();

        let id = ModalId::new();
        let z_index = BASE_MODAL_Z + self.next_z;
        self.next_z += Z_INCREMENT;

        let hit_id = HitId::new(self.next_hit_id);
        self.next_hit_id += 1;

        self.modals.push(ActiveModal {
            id,
            z_index,
            modal,
            hit_id,
            focus_group_id,
            focus_return_focus: None,
        });

        #[cfg(feature = "tracing")]
        tracing::debug!(
            modal_id = id.id(),
            modal_type,
            focus_trapped,
            depth = self.modals.len(),
            "modal opened"
        );

        id
    }

    /// Get the focus group ID for a modal.
    ///
    /// Returns `None` if the modal doesn't exist or has no focus group.
    pub fn focus_group_id(&self, modal_id: ModalId) -> Option<u32> {
        self.modals
            .iter()
            .find(|m| m.id == modal_id)
            .and_then(|m| m.focus_group_id)
    }

    /// Get the focus group ID for the top modal.
    ///
    /// Useful for checking if focus trap should be active.
    pub fn top_focus_group_id(&self) -> Option<u32> {
        self.modals.last().and_then(|m| m.focus_group_id)
    }

    pub(super) fn next_focus_modal_after(&self, modal_id: ModalId) -> Option<(ModalId, u32)> {
        let idx = self.modals.iter().position(|modal| modal.id == modal_id)?;
        self.modals[idx + 1..]
            .iter()
            .find_map(|modal| modal.focus_group_id.map(|group_id| (modal.id, group_id)))
    }

    /// Pop the top modal from the stack.
    ///
    /// Returns the result if a modal was popped, or `None` if the stack is empty.
    /// If the modal had a focus group, the caller should call `FocusManager::pop_trap()`.
    pub fn pop(&mut self) -> Option<ModalResult> {
        let modal = self.modals.pop()?;
        #[cfg(feature = "tracing")]
        let modal_type = modal.modal.modal_type();

        let result = ModalResult {
            id: modal.id,
            data: None,
            focus_group_id: modal.focus_group_id,
        };

        #[cfg(feature = "tracing")]
        tracing::debug!(
            modal_id = result.id.id(),
            modal_type,
            depth = self.modals.len(),
            "modal closed"
        );

        Some(result)
    }

    /// Pop a specific modal by ID.
    ///
    /// Returns the result if the modal was found and removed, or `None` if not found.
    /// Note: This breaks strict LIFO ordering but is sometimes needed.
    /// If the modal had a focus group, the caller should handle focus restoration.
    pub fn pop_id(&mut self, id: ModalId) -> Option<ModalResult> {
        self.pop_id_with_restore_retarget(id, true)
    }

    pub(super) fn pop_id_with_restore_retarget(
        &mut self,
        id: ModalId,
        retarget_upper_return_focus: bool,
    ) -> Option<ModalResult> {
        let idx = self.modals.iter().position(|m| m.id == id)?;
        let modal = self.modals.remove(idx);
        #[cfg(feature = "tracing")]
        let modal_type = modal.modal.modal_type();

        if retarget_upper_return_focus
            && modal.focus_group_id.is_some()
            && let Some(upper_modal) = self.modals[idx..]
                .iter_mut()
                .find(|candidate| candidate.focus_group_id.is_some())
        {
            upper_modal.focus_return_focus = modal.focus_return_focus;
        }

        let result = ModalResult {
            id: modal.id,
            data: None,
            focus_group_id: modal.focus_group_id,
        };

        #[cfg(feature = "tracing")]
        tracing::debug!(
            modal_id = result.id.id(),
            modal_type,
            depth = self.modals.len(),
            "modal closed (pop_id)"
        );

        Some(result)
    }

    /// Pop all modals from the stack.
    ///
    /// Returns results in LIFO order (top first).
    pub fn pop_all(&mut self) -> Vec<ModalResult> {
        let mut results = Vec::with_capacity(self.modals.len());
        while let Some(result) = self.pop() {
            results.push(result);
        }
        results
    }

    /// Get a reference to the top modal.
    pub fn top(&self) -> Option<&(dyn StackModal + 'static)> {
        self.modals.last().map(|m| &*m.modal)
    }

    /// Get a mutable reference to the top modal.
    pub fn top_mut(&mut self) -> Option<&mut (dyn StackModal + 'static)> {
        match self.modals.last_mut() {
            Some(m) => Some(m.modal.as_mut()),
            None => None,
        }
    }

    // --- State Queries ---

    /// Check if the stack is empty.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.modals.is_empty()
    }

    /// Get the number of modals in the stack.
    #[inline]
    pub fn depth(&self) -> usize {
        self.modals.len()
    }

    /// Check if a modal with the given ID exists in the stack.
    pub fn contains(&self, id: ModalId) -> bool {
        self.modals.iter().any(|m| m.id == id)
    }

    /// Get the ID of the top modal, if any.
    pub fn top_id(&self) -> Option<ModalId> {
        self.modals.last().map(|m| m.id)
    }

    /// Get focus group IDs for active modals in stack order (bottom to top).
    pub fn focus_group_ids_in_order(&self) -> Vec<u32> {
        self.modals
            .iter()
            .filter_map(|m| m.focus_group_id)
            .collect()
    }

    pub(super) fn focus_modal_specs_in_order(&self) -> Vec<(ModalId, FocusTrapSpec)> {
        self.modals
            .iter()
            .filter_map(|modal| {
                modal.focus_group_id.map(|group_id| {
                    (
                        modal.id,
                        FocusTrapSpec {
                            group_id,
                            return_focus: modal.focus_return_focus,
                        },
                    )
                })
            })
            .collect()
    }

    pub(super) fn set_focus_return_focus(
        &mut self,
        modal_id: ModalId,
        return_focus: Option<FocusId>,
    ) -> bool {
        let Some(modal) = self.modals.iter_mut().find(|modal| modal.id == modal_id) else {
            return false;
        };
        if modal.focus_group_id.is_none() {
            return false;
        }
        modal.focus_return_focus = return_focus;
        true
    }

    // --- Event Handling ---

    /// Handle an event, routing it to the top modal only.
    ///
    /// Returns `Some(ModalResult)` if the top modal closed, otherwise `None`.
    /// If the result contains a `focus_group_id`, the caller should call
    /// `FocusManager::pop_trap()` to restore focus.
    ///
    /// For mouse interactions, pass the provenance-aware result from
    /// [`Frame::hit_test_detailed`]. Plain `(HitId, HitRegion, HitData)` tuples
    /// do not carry enough ownership information for layered modal routing.
    pub fn handle_event(
        &mut self,
        event: &Event,
        hit: Option<HitTestResult>,
    ) -> Option<ModalResult> {
        let top_index = self.modals.len().checked_sub(1)?;
        let top_owner = self.modals[top_index].id.id();
        let hit_id = self.modals[top_index].hit_id;
        let filtered_hit = hit.filter(|hit| hit.owner == Some(top_owner));
        let top = &mut self.modals[top_index];
        let id = top.id;
        let focus_group_id = top.focus_group_id;
        #[cfg(feature = "tracing")]
        let modal_type = top.modal.modal_type();

        if let Some(data) =
            top.modal
                .handle_event(event, filtered_hit.map(HitTestResult::into_tuple), hit_id)
        {
            // Modal wants to close
            self.modals.pop();
            let result = ModalResult {
                id,
                data: Some(data),
                focus_group_id,
            };

            #[cfg(feature = "tracing")]
            tracing::debug!(
                modal_id = result.id.id(),
                modal_type,
                result_data = ?result.data,
                depth = self.modals.len(),
                "modal closed (event)"
            );

            return Some(result);
        }

        None
    }

    // --- Rendering ---

    /// Render all modals in z-order.
    ///
    /// Modals are rendered from bottom to top. Lower modals have reduced
    /// backdrop opacity to create a visual depth effect.
    pub fn render(&self, frame: &mut Frame, screen: Rect) {
        if self.modals.is_empty() {
            return;
        }

        let modal_count = self.modals.len();

        for (i, modal) in self.modals.iter().enumerate() {
            let is_top = i == modal_count - 1;

            // Calculate backdrop opacity with depth dimming
            let base_opacity = modal.modal.backdrop_config().opacity;
            let opacity = if is_top {
                base_opacity
            } else {
                // Reduce opacity for lower modals (50% of configured)
                base_opacity * 0.5
            };

            #[cfg(feature = "tracing")]
            let render_start = Instant::now();
            #[cfg(feature = "tracing")]
            let render_span = tracing::debug_span!(
                "modal.render",
                modal_type = modal.modal.modal_type(),
                focus_trapped = (modal.focus_group_id.is_some() && modal.modal.aria_modal()),
                backdrop_active = (opacity > 0.0),
                render_duration_us = tracing::field::Empty,
            );
            #[cfg(feature = "tracing")]
            let _render_guard = render_span.enter();

            // Render backdrop
            if opacity > 0.0 {
                let bg_color = modal.modal.backdrop_config().color.with_opacity(opacity);
                set_style_area(&mut frame.buffer, screen, Style::new().bg(bg_color));
            }

            frame.with_hit_owner(modal.id.id(), |frame| {
                // Register backdrop hits even when the modal content clamps to zero.
                // A zero-sized modal can still present a visible overlay and should
                // still receive backdrop clicks.
                if !screen.is_empty() {
                    frame.register_hit(screen, modal.hit_id, MODAL_HIT_BACKDROP, 0);
                }

                // Calculate modal content area
                let constraints = modal.modal.size_constraints();
                let available = ftui_core::geometry::Size::new(screen.width, screen.height);
                let size = constraints.clamp(available);

                if size.width == 0 || size.height == 0 {
                    return;
                }

                // Center the modal
                let x = screen.x + (screen.width.saturating_sub(size.width)) / 2;
                let y = screen.y + (screen.height.saturating_sub(size.height)) / 2;
                let content_area = Rect::new(x, y, size.width, size.height);

                // Register hit regions for backdrop and content so that
                // close_on_backdrop and custom mouse dispatch can distinguish clicks.
                if !content_area.is_empty() {
                    frame.register_hit(content_area, modal.hit_id, MODAL_HIT_CONTENT, 0);
                }

                // Render modal content
                modal.modal.render_content(content_area, frame);
            });

            #[cfg(feature = "tracing")]
            {
                let elapsed = render_start.elapsed();
                render_span.record("render_duration_us", elapsed.as_micros() as u64);
            }
        }
    }
}

/// A simple modal entry that wraps any Widget.
pub struct WidgetModalEntry<W> {
    widget: W,
    size: ModalSizeConstraints,
    backdrop: BackdropConfig,
    close_on_escape: bool,
    close_on_backdrop: bool,
    aria_modal: bool,
    focusable_ids: Option<Vec<ModalFocusId>>,
}

impl<W> WidgetModalEntry<W> {
    /// Create a new modal entry with a widget.
    pub fn new(widget: W) -> Self {
        Self {
            widget,
            size: ModalSizeConstraints::new()
                .min_width(30)
                .max_width(60)
                .min_height(10)
                .max_height(20),
            backdrop: BackdropConfig::default(),
            close_on_escape: true,
            close_on_backdrop: true,
            aria_modal: true,
            focusable_ids: None,
        }
    }

    /// Set size constraints.
    #[must_use]
    pub fn size(mut self, size: ModalSizeConstraints) -> Self {
        self.size = size;
        self
    }

    /// Set backdrop configuration.
    #[must_use]
    pub fn backdrop(mut self, backdrop: BackdropConfig) -> Self {
        self.backdrop = backdrop;
        self
    }

    /// Set whether Escape closes the modal.
    #[must_use]
    pub fn close_on_escape(mut self, close: bool) -> Self {
        self.close_on_escape = close;
        self
    }

    /// Set whether backdrop click closes the modal.
    #[must_use]
    pub fn close_on_backdrop(mut self, close: bool) -> Self {
        self.close_on_backdrop = close;
        self
    }

    /// Set whether this modal is an ARIA modal.
    ///
    /// ARIA modals trap focus and announce semantics to screen readers.
    /// Default is `true` for accessibility compliance.
    #[must_use]
    pub fn with_aria_modal(mut self, aria_modal: bool) -> Self {
        self.aria_modal = aria_modal;
        self
    }

    /// Set the focusable widget IDs for focus trap integration.
    ///
    /// When provided, these IDs will be used to:
    /// 1. Create a focus group constraining Tab navigation
    /// 2. Auto-focus the first focusable widget when modal opens
    /// 3. Restore focus to the previous element when modal closes
    #[must_use]
    pub fn with_focusable_ids(mut self, ids: Vec<ModalFocusId>) -> Self {
        self.focusable_ids = Some(ids);
        self
    }
}

impl<W: crate::Widget + Send> StackModal for WidgetModalEntry<W> {
    fn render_content(&self, area: Rect, frame: &mut Frame) {
        self.widget.render(area, frame);
    }

    fn handle_event(
        &mut self,
        event: &Event,
        hit: Option<(HitId, HitRegion, HitData)>,
        hit_id: HitId,
    ) -> Option<ModalResultData> {
        use ftui_core::event::{KeyCode, KeyEvent, KeyEventKind};

        if self.close_on_backdrop
            && let Event::Mouse(ftui_core::event::MouseEvent {
                kind: ftui_core::event::MouseEventKind::Down(ftui_core::event::MouseButton::Left),
                ..
            }) = event
            && let Some((id, region, _)) = hit
            && id == hit_id
            && region == MODAL_HIT_BACKDROP
        {
            return Some(ModalResultData::Dismissed);
        }

        // Handle escape to close
        if self.close_on_escape
            && let Event::Key(KeyEvent {
                code: KeyCode::Escape,
                kind: KeyEventKind::Press,
                ..
            }) = event
        {
            return Some(ModalResultData::Dismissed);
        }

        None
    }

    fn size_constraints(&self) -> ModalSizeConstraints {
        self.size
    }

    fn backdrop_config(&self) -> BackdropConfig {
        self.backdrop
    }

    fn close_on_escape(&self) -> bool {
        self.close_on_escape
    }

    fn close_on_backdrop(&self) -> bool {
        self.close_on_backdrop
    }

    fn aria_modal(&self) -> bool {
        self.aria_modal
    }

    fn focusable_ids(&self) -> Option<Vec<ModalFocusId>> {
        self.focusable_ids.clone()
    }
}

// =========================================================================
// Modal Focus Integration Helper (bd-39vx.5)
// =========================================================================

/// Helper for integrating `ModalStack` with `FocusManager`.
///
/// This struct provides a convenient API for:
/// - Pushing modals with automatic focus trap setup
/// - Popping modals with focus restoration
/// - Managing focus groups for nested modals
///
/// # Example
/// ```ignore
/// use ftui_widgets::modal::{ModalStack, ModalFocusIntegration};
/// use ftui_widgets::focus::FocusManager;
///
/// let mut stack = ModalStack::new();
/// let mut focus = FocusManager::new();
/// let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
///
/// // Push a modal with focus management
/// let modal_id = integrator.push_with_focus(dialog);
///
/// // ... handle events ...
///
/// // Pop modal and restore focus
/// integrator.pop_with_focus();
/// ```
#[cfg(test)]
#[allow(dead_code)]
pub struct ModalFocusIntegration<'a> {
    stack: &'a mut ModalStack,
    focus: &'a mut crate::focus::FocusManager,
    base_focus: Option<Option<crate::focus::FocusId>>,
}

#[cfg(test)]
#[allow(dead_code)]
impl<'a> ModalFocusIntegration<'a> {
    /// Create a new integration helper.
    pub fn new(stack: &'a mut ModalStack, focus: &'a mut crate::focus::FocusManager) -> Self {
        let base_focus = focus.base_trap_return_focus();
        Self {
            stack,
            focus,
            base_focus,
        }
    }

    /// Push a modal with automatic focus management.
    ///
    /// 1. Creates a focus group from `modal.focusable_ids()` (if provided)
    /// 2. Pushes a focus trap to constrain Tab navigation
    /// 3. Auto-focuses the first focusable widget
    /// 4. Stores the previous focus for restoration on close
    ///
    /// Returns the modal ID.
    pub fn push_with_focus(&mut self, modal: Box<dyn StackModal>) -> ModalId {
        let focusable_ids = modal.focusable_ids();
        let is_aria_modal = modal.aria_modal();
        ModalFocusCoordinator::new(self.stack, self.focus, &mut self.base_focus)
            .push_modal_with_trap(modal, focusable_ids, is_aria_modal, next_focus_group_id)
    }

    /// Pop the top modal with focus restoration.
    ///
    /// If the modal had a focus group, the trap is popped and focus
    /// is restored to the element that was focused before the modal opened.
    ///
    /// Returns the modal result.
    pub fn pop_with_focus(&mut self) -> Option<ModalResult> {
        ModalFocusCoordinator::new(self.stack, self.focus, &mut self.base_focus).pop_modal()
    }

    /// Pop a specific modal with focus restoration/rebuild.
    pub fn pop_id_with_focus(&mut self, id: ModalId) -> Option<ModalResult> {
        ModalFocusCoordinator::new(self.stack, self.focus, &mut self.base_focus).pop_modal_by_id(id)
    }

    /// Pop all modals with focus restoration/rebuild.
    pub fn pop_all_with_focus(&mut self) -> Vec<ModalResult> {
        ModalFocusCoordinator::new(self.stack, self.focus, &mut self.base_focus).pop_all_modals()
    }

    /// Handle an event with automatic focus trap popping.
    ///
    /// If the event causes the modal to close, the focus trap is popped.
    pub fn handle_event(
        &mut self,
        event: &Event,
        hit: Option<HitTestResult>,
    ) -> Option<ModalResult> {
        ModalFocusCoordinator::new(self.stack, self.focus, &mut self.base_focus)
            .handle_modal_event(event, hit)
    }

    /// Check if focus is currently trapped in a modal.
    pub fn is_focus_trapped(&self) -> bool {
        self.focus.is_trapped()
    }

    /// Get a reference to the underlying modal stack.
    pub fn stack(&self) -> &ModalStack {
        self.stack
    }

    /// Get a mutable reference to the underlying modal stack.
    ///
    /// **Warning**: Direct manipulation may desync focus state. Call
    /// `resync_focus_state()` after mutating the stack directly.
    pub fn stack_mut(&mut self) -> &mut ModalStack {
        self.stack
    }

    /// Get a reference to the underlying focus manager.
    pub fn focus(&self) -> &crate::focus::FocusManager {
        self.focus
    }

    /// Get a mutable reference to the underlying focus manager.
    ///
    /// **Warning**: Direct manipulation may desync modal focus restoration.
    /// Call `resync_focus_state()` after mutating traps, groups, or graph state directly.
    pub fn focus_mut(&mut self) -> &mut crate::focus::FocusManager {
        self.focus
    }

    /// Rebuild modal focus state after direct mutation via `stack_mut()` or `focus_mut()`.
    pub fn resync_focus_state(&mut self) {
        let mut coordinator =
            ModalFocusCoordinator::new(self.stack, self.focus, &mut self.base_focus);
        coordinator.rebuild_focus_traps();
        coordinator.refresh_inactive_modal_return_focus_targets();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Widget;
    use ftui_core::event::{KeyCode, KeyEvent, KeyEventKind, Modifiers};
    use ftui_render::cell::PackedRgba;
    use ftui_render::grapheme_pool::GraphemePool;
    #[cfg(feature = "tracing")]
    use std::sync::{Arc, Mutex};

    #[cfg(feature = "tracing")]
    use tracing::Subscriber;
    #[cfg(feature = "tracing")]
    use tracing_subscriber::Layer;
    #[cfg(feature = "tracing")]
    use tracing_subscriber::layer::{Context, SubscriberExt};

    #[derive(Debug, Clone)]
    struct StubWidget;

    impl Widget for StubWidget {
        fn render(&self, _area: Rect, _frame: &mut Frame) {}
    }

    #[derive(Debug, Default)]
    struct CloseOnAnyHitModal;

    #[derive(Debug, Default)]
    struct CloseOnBackdropHitModal;

    #[derive(Debug, Default)]
    struct CloseOnInnerHitModal;

    #[derive(Debug, Default)]
    struct CloseOnCollidingInnerHitModal;

    impl StackModal for CloseOnAnyHitModal {
        fn render_content(&self, _area: Rect, _frame: &mut Frame) {}

        fn handle_event(
            &mut self,
            _event: &Event,
            hit: Option<(HitId, HitRegion, HitData)>,
            _hit_id: HitId,
        ) -> Option<ModalResultData> {
            hit.map(|_| ModalResultData::Dismissed)
        }

        fn size_constraints(&self) -> ModalSizeConstraints {
            ModalSizeConstraints::new()
                .min_width(10)
                .max_width(10)
                .min_height(3)
                .max_height(3)
        }

        fn backdrop_config(&self) -> BackdropConfig {
            BackdropConfig::default()
        }

        fn close_on_backdrop(&self) -> bool {
            false
        }
    }

    impl StackModal for CloseOnBackdropHitModal {
        fn render_content(&self, _area: Rect, _frame: &mut Frame) {}

        fn handle_event(
            &mut self,
            event: &Event,
            hit: Option<(HitId, HitRegion, HitData)>,
            hit_id: HitId,
        ) -> Option<ModalResultData> {
            if let Event::Mouse(ftui_core::event::MouseEvent {
                kind: ftui_core::event::MouseEventKind::Down(ftui_core::event::MouseButton::Left),
                ..
            }) = event
                && let Some((id, region, _)) = hit
                && id == hit_id
                && region == MODAL_HIT_BACKDROP
            {
                return Some(ModalResultData::Dismissed);
            }

            None
        }

        fn size_constraints(&self) -> ModalSizeConstraints {
            ModalSizeConstraints::new()
                .min_width(10)
                .max_width(10)
                .min_height(3)
                .max_height(3)
        }

        fn backdrop_config(&self) -> BackdropConfig {
            BackdropConfig::default()
        }

        fn close_on_backdrop(&self) -> bool {
            false
        }
    }

    impl StackModal for CloseOnInnerHitModal {
        fn render_content(&self, area: Rect, frame: &mut Frame) {
            if !area.is_empty() {
                frame.register_hit(area, HitId::new(4242), HitRegion::Custom(99), 0);
            }
        }

        fn handle_event(
            &mut self,
            _event: &Event,
            hit: Option<(HitId, HitRegion, HitData)>,
            _hit_id: HitId,
        ) -> Option<ModalResultData> {
            if let Some((id, region, _)) = hit
                && id == HitId::new(4242)
                && region == HitRegion::Custom(99)
            {
                return Some(ModalResultData::Dismissed);
            }

            None
        }

        fn size_constraints(&self) -> ModalSizeConstraints {
            ModalSizeConstraints::new()
                .min_width(10)
                .max_width(10)
                .min_height(3)
                .max_height(3)
        }

        fn backdrop_config(&self) -> BackdropConfig {
            BackdropConfig::default()
        }

        fn close_on_backdrop(&self) -> bool {
            false
        }
    }

    impl StackModal for CloseOnCollidingInnerHitModal {
        fn render_content(&self, area: Rect, frame: &mut Frame) {
            if !area.is_empty() {
                frame.register_hit(area, HitId::new(1000), HitRegion::Custom(100), 0);
            }
        }

        fn handle_event(
            &mut self,
            _event: &Event,
            hit: Option<(HitId, HitRegion, HitData)>,
            _hit_id: HitId,
        ) -> Option<ModalResultData> {
            if let Some((id, region, _)) = hit
                && id == HitId::new(1000)
                && region == HitRegion::Custom(100)
            {
                return Some(ModalResultData::Dismissed);
            }

            None
        }

        fn size_constraints(&self) -> ModalSizeConstraints {
            ModalSizeConstraints::new()
                .min_width(10)
                .max_width(10)
                .min_height(3)
                .max_height(3)
        }

        fn backdrop_config(&self) -> BackdropConfig {
            BackdropConfig::default()
        }

        fn close_on_backdrop(&self) -> bool {
            false
        }
    }

    #[cfg(feature = "tracing")]
    #[derive(Debug, Default)]
    struct TraceState {
        modal_render_seen: bool,
        modal_render_has_modal_type: bool,
        modal_render_has_focus_trapped: bool,
        modal_render_has_backdrop_active: bool,
        modal_render_duration_recorded: bool,
        focus_change_count: usize,
        trap_push_count: usize,
        trap_pop_count: usize,
    }

    #[cfg(feature = "tracing")]
    struct TraceCapture {
        state: Arc<Mutex<TraceState>>,
    }

    #[cfg(feature = "tracing")]
    impl<S> Layer<S> for TraceCapture
    where
        S: Subscriber + for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
    {
        fn on_new_span(
            &self,
            attrs: &tracing::span::Attributes<'_>,
            _id: &tracing::Id,
            _ctx: Context<'_, S>,
        ) {
            if attrs.metadata().name() != "modal.render" {
                return;
            }
            let fields = attrs.metadata().fields();
            let mut state = self.state.lock().expect("trace state lock");
            state.modal_render_seen = true;
            state.modal_render_has_modal_type |= fields.field("modal_type").is_some();
            state.modal_render_has_focus_trapped |= fields.field("focus_trapped").is_some();
            state.modal_render_has_backdrop_active |= fields.field("backdrop_active").is_some();
        }

        fn on_record(
            &self,
            id: &tracing::Id,
            values: &tracing::span::Record<'_>,
            ctx: Context<'_, S>,
        ) {
            let Some(span) = ctx.span(id) else {
                return;
            };
            if span.metadata().name() != "modal.render" {
                return;
            }

            struct DurationVisitor {
                saw_duration: bool,
            }

            impl tracing::field::Visit for DurationVisitor {
                fn record_u64(&mut self, field: &tracing::field::Field, _value: u64) {
                    if field.name() == "render_duration_us" {
                        self.saw_duration = true;
                    }
                }

                fn record_debug(
                    &mut self,
                    field: &tracing::field::Field,
                    _value: &dyn std::fmt::Debug,
                ) {
                    if field.name() == "render_duration_us" {
                        self.saw_duration = true;
                    }
                }
            }

            let mut visitor = DurationVisitor {
                saw_duration: false,
            };
            values.record(&mut visitor);
            if visitor.saw_duration {
                self.state
                    .lock()
                    .expect("trace state lock")
                    .modal_render_duration_recorded = true;
            }
        }

        fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
            struct MessageVisitor {
                message: Option<String>,
            }

            impl tracing::field::Visit for MessageVisitor {
                fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
                    if field.name() == "message" {
                        self.message = Some(value.to_owned());
                    }
                }

                fn record_debug(
                    &mut self,
                    field: &tracing::field::Field,
                    value: &dyn std::fmt::Debug,
                ) {
                    if field.name() == "message" {
                        self.message = Some(format!("{value:?}").trim_matches('"').to_owned());
                    }
                }
            }

            let mut visitor = MessageVisitor { message: None };
            event.record(&mut visitor);

            let Some(message) = visitor.message else {
                return;
            };

            let mut state = self.state.lock().expect("trace state lock");
            match message.as_str() {
                "focus.change" => state.focus_change_count += 1,
                "focus.trap_push" => state.trap_push_count += 1,
                "focus.trap_pop" => state.trap_pop_count += 1,
                _ => {}
            }
        }
    }

    #[test]
    fn empty_stack() {
        let stack = ModalStack::new();
        assert!(stack.is_empty());
        assert_eq!(stack.depth(), 0);
        assert!(stack.top().is_none());
        assert!(stack.top_id().is_none());
    }

    #[test]
    fn push_increases_depth() {
        let mut stack = ModalStack::new();
        let id1 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        assert_eq!(stack.depth(), 1);
        assert!(!stack.is_empty());
        assert!(stack.contains(id1));

        let id2 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        assert_eq!(stack.depth(), 2);
        assert!(stack.contains(id2));
        assert_eq!(stack.top_id(), Some(id2));
    }

    #[test]
    fn pop_lifo_order() {
        let mut stack = ModalStack::new();
        let id1 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        let id2 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        let id3 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));

        let result = stack.pop();
        assert_eq!(result.map(|r| r.id), Some(id3));
        assert_eq!(stack.depth(), 2);

        let result = stack.pop();
        assert_eq!(result.map(|r| r.id), Some(id2));
        assert_eq!(stack.depth(), 1);

        let result = stack.pop();
        assert_eq!(result.map(|r| r.id), Some(id1));
        assert!(stack.is_empty());
    }

    #[test]
    fn pop_empty_returns_none() {
        let mut stack = ModalStack::new();
        assert!(stack.pop().is_none());
    }

    #[test]
    fn pop_by_id() {
        let mut stack = ModalStack::new();
        let id1 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        let id2 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        let id3 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));

        // Pop middle modal
        let result = stack.pop_id(id2);
        assert_eq!(result.map(|r| r.id), Some(id2));
        assert_eq!(stack.depth(), 2);
        assert!(!stack.contains(id2));
        assert!(stack.contains(id1));
        assert!(stack.contains(id3));
    }

    #[test]
    fn pop_by_nonexistent_id() {
        let mut stack = ModalStack::new();
        let _id1 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));

        // Try to pop non-existent ID
        let fake_id = ModalId(999999);
        assert!(stack.pop_id(fake_id).is_none());
        assert_eq!(stack.depth(), 1);
    }

    #[test]
    fn pop_all() {
        let mut stack = ModalStack::new();
        let id1 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        let id2 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        let id3 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));

        let results = stack.pop_all();
        assert_eq!(results.len(), 3);
        // LIFO order: id3, id2, id1
        assert_eq!(results[0].id, id3);
        assert_eq!(results[1].id, id2);
        assert_eq!(results[2].id, id1);
        assert!(stack.is_empty());
    }

    #[test]
    fn z_order_increasing() {
        let mut stack = ModalStack::new();

        // Push multiple modals
        stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        stack.push(Box::new(WidgetModalEntry::new(StubWidget)));

        // Verify z-order is increasing
        let z_indices: Vec<u32> = stack.modals.iter().map(|m| m.z_index).collect();
        for i in 1..z_indices.len() {
            assert!(
                z_indices[i] > z_indices[i - 1],
                "z_index should be strictly increasing"
            );
        }
    }

    #[test]
    fn escape_closes_top_modal() {
        let mut stack = ModalStack::new();
        let id1 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        let id2 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));

        let escape = Event::Key(KeyEvent {
            code: KeyCode::Escape,
            modifiers: Modifiers::empty(),
            kind: KeyEventKind::Press,
        });

        // Escape should close top modal (id2)
        let result = stack.handle_event(&escape, None);
        assert!(result.is_some());
        assert_eq!(result.unwrap().id, id2);
        assert_eq!(stack.depth(), 1);
        assert_eq!(stack.top_id(), Some(id1));
    }

    #[test]
    fn render_does_not_panic() {
        let mut stack = ModalStack::new();
        stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        stack.push(Box::new(WidgetModalEntry::new(StubWidget)));

        let mut pool = GraphemePool::new();
        let mut frame = Frame::new(80, 24, &mut pool);
        let screen = Rect::new(0, 0, 80, 24);

        // Should not panic
        stack.render(&mut frame, screen);
    }

    #[test]
    fn render_empty_stack_no_op() {
        let stack = ModalStack::new();
        let mut pool = GraphemePool::new();
        let mut frame = Frame::new(80, 24, &mut pool);
        let screen = Rect::new(0, 0, 80, 24);

        // Should be a no-op
        stack.render(&mut frame, screen);
    }

    #[test]
    fn contains_after_pop() {
        let mut stack = ModalStack::new();
        let id1 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));

        assert!(stack.contains(id1));
        stack.pop();
        assert!(!stack.contains(id1));
    }

    #[test]
    fn unique_modal_ids() {
        let mut stack = ModalStack::new();
        let id1 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        let id2 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        let id3 = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));

        assert_ne!(id1, id2);
        assert_ne!(id2, id3);
        assert_ne!(id1, id3);
    }

    #[test]
    fn widget_modal_entry_builder() {
        let entry = WidgetModalEntry::new(StubWidget)
            .size(ModalSizeConstraints::new().min_width(40).max_width(80))
            .backdrop(BackdropConfig::new(PackedRgba::rgb(0, 0, 0), 0.8))
            .close_on_escape(false)
            .close_on_backdrop(false);

        assert!(!entry.close_on_escape);
        assert!(!entry.close_on_backdrop);
        assert_eq!(entry.size.min_width, Some(40));
        assert_eq!(entry.size.max_width, Some(80));
    }

    #[test]
    fn escape_disabled_does_not_close() {
        let mut stack = ModalStack::new();
        stack.push(Box::new(
            WidgetModalEntry::new(StubWidget).close_on_escape(false),
        ));

        let escape = Event::Key(KeyEvent {
            code: KeyCode::Escape,
            modifiers: Modifiers::empty(),
            kind: KeyEventKind::Press,
        });

        // Escape should NOT close the modal
        let result = stack.handle_event(&escape, None);
        assert!(result.is_none());
        assert_eq!(stack.depth(), 1);
    }

    #[test]
    fn backdrop_click_closes_top_modal() {
        let mut stack = ModalStack::new();
        let top_id = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));

        let click = Event::Mouse(ftui_core::event::MouseEvent::new(
            ftui_core::event::MouseEventKind::Down(ftui_core::event::MouseButton::Left),
            0,
            0,
        ));
        let hit = Some(HitTestResult::new(
            HitId::new(1000),
            MODAL_HIT_BACKDROP,
            0,
            Some(top_id.id()),
        ));

        let result = stack.handle_event(&click, hit);
        assert!(result.is_some());
        let result = result.unwrap();
        assert_eq!(result.id, top_id);
        assert!(matches!(result.data, Some(ModalResultData::Dismissed)));
        assert!(stack.is_empty());
    }

    #[test]
    fn content_click_does_not_close_top_modal() {
        let mut stack = ModalStack::new();
        stack.push(Box::new(WidgetModalEntry::new(StubWidget)));

        let click = Event::Mouse(ftui_core::event::MouseEvent::new(
            ftui_core::event::MouseEventKind::Down(ftui_core::event::MouseButton::Left),
            5,
            5,
        ));
        let hit = Some(HitTestResult::new(
            HitId::new(1000),
            MODAL_HIT_CONTENT,
            0,
            Some(stack.top_id().unwrap().id()),
        ));

        let result = stack.handle_event(&click, hit);
        assert!(result.is_none());
        assert_eq!(stack.depth(), 1);
    }

    #[test]
    fn custom_modal_receives_backdrop_hit_without_builtin_auto_close() {
        let mut stack = ModalStack::new();
        let top_id = stack.push(Box::new(CloseOnBackdropHitModal));

        let click = Event::Mouse(ftui_core::event::MouseEvent::new(
            ftui_core::event::MouseEventKind::Down(ftui_core::event::MouseButton::Left),
            0,
            0,
        ));
        let hit = Some(HitTestResult::new(
            HitId::new(1000),
            MODAL_HIT_BACKDROP,
            0,
            Some(top_id.id()),
        ));

        let result = stack.handle_event(&click, hit);
        assert!(result.is_some());
        let result = result.unwrap();
        assert_eq!(result.id, top_id);
        assert!(matches!(result.data, Some(ModalResultData::Dismissed)));
        assert!(stack.is_empty());
    }

    #[test]
    fn custom_modal_receives_inner_widget_hit() {
        let mut stack = ModalStack::new();
        let top_id = stack.push(Box::new(CloseOnInnerHitModal));
        let top_hit_id = stack.modals.last().unwrap().hit_id;

        let mut pool = GraphemePool::new();
        let mut frame = Frame::with_hit_grid(20, 10, &mut pool);
        let screen = Rect::new(0, 0, 20, 10);
        stack.render(&mut frame, screen);

        let hit = frame.hit_test_detailed(10, 4);
        assert_eq!(
            hit,
            Some(HitTestResult::new(
                HitId::new(4242),
                HitRegion::Custom(99),
                0,
                Some(top_id.id()),
            ))
        );
        assert_ne!(hit.unwrap().id, top_hit_id);

        let click = Event::Mouse(ftui_core::event::MouseEvent::new(
            ftui_core::event::MouseEventKind::Down(ftui_core::event::MouseButton::Left),
            10,
            4,
        ));

        let result = stack.handle_event(&click, hit);
        assert!(result.is_some());
        let result = result.unwrap();
        assert_eq!(result.id, top_id);
        assert!(matches!(result.data, Some(ModalResultData::Dismissed)));
        assert!(stack.is_empty());
    }

    #[test]
    fn custom_modal_receives_inner_widget_hit_even_when_hit_id_collides_with_lower_modal() {
        let mut stack = ModalStack::new();
        let _lower_id = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        let top_id = stack.push(Box::new(CloseOnCollidingInnerHitModal));
        let top_hit_id = stack.modals.last().unwrap().hit_id;

        let mut pool = GraphemePool::new();
        let mut frame = Frame::with_hit_grid(20, 10, &mut pool);
        let screen = Rect::new(0, 0, 20, 10);
        stack.render(&mut frame, screen);

        let hit = frame.hit_test_detailed(10, 4);
        assert_eq!(
            hit,
            Some(HitTestResult::new(
                HitId::new(1000),
                HitRegion::Custom(100),
                0,
                Some(top_id.id()),
            ))
        );
        assert_ne!(hit.unwrap().id, top_hit_id);

        let click = Event::Mouse(ftui_core::event::MouseEvent::new(
            ftui_core::event::MouseEventKind::Down(ftui_core::event::MouseButton::Left),
            10,
            4,
        ));

        let result = stack.handle_event(&click, hit);
        assert!(result.is_some());
        let result = result.unwrap();
        assert_eq!(result.id, top_id);
        assert!(matches!(result.data, Some(ModalResultData::Dismissed)));
        assert_eq!(stack.depth(), 1);
    }

    #[test]
    fn zero_sized_modal_still_registers_backdrop_hit() {
        let mut stack = ModalStack::new();
        stack.push(Box::new(
            WidgetModalEntry::new(StubWidget)
                .size(ModalSizeConstraints::new().max_width(0).max_height(0)),
        ));

        let hit_id = stack.modals.last().unwrap().hit_id;
        let mut pool = GraphemePool::new();
        let mut frame = Frame::with_hit_grid(20, 10, &mut pool);
        let screen = Rect::new(0, 0, 20, 10);

        stack.render(&mut frame, screen);

        assert_eq!(
            frame.hit_test_detailed(0, 0),
            Some(HitTestResult::new(
                hit_id,
                MODAL_HIT_BACKDROP,
                0,
                Some(stack.top_id().unwrap().id()),
            ))
        );
    }

    #[test]
    fn foreign_lower_modal_hit_is_not_routed_to_top_modal() {
        let mut stack = ModalStack::new();
        let _lower_id = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        let top_id = stack.push(Box::new(CloseOnAnyHitModal));

        let click = Event::Mouse(ftui_core::event::MouseEvent::new(
            ftui_core::event::MouseEventKind::Down(ftui_core::event::MouseButton::Left),
            0,
            0,
        ));
        let lower_backdrop_hit = Some(HitTestResult::new(
            HitId::new(1000),
            MODAL_HIT_BACKDROP,
            0,
            Some(_lower_id.id()),
        ));

        let result = stack.handle_event(&click, lower_backdrop_hit);
        assert!(result.is_none());
        assert_eq!(stack.depth(), 2);
        assert_eq!(stack.top_id(), Some(top_id));
    }

    // --- Focus group integration tests ---

    #[test]
    fn push_with_focus_tracks_group_id() {
        let mut stack = ModalStack::new();
        let modal_id = stack.push_with_focus(Box::new(WidgetModalEntry::new(StubWidget)), Some(42));

        assert_eq!(stack.focus_group_id(modal_id), Some(42));
        assert_eq!(stack.top_focus_group_id(), Some(42));
    }

    #[test]
    fn pop_returns_focus_group_id() {
        let mut stack = ModalStack::new();
        stack.push_with_focus(Box::new(WidgetModalEntry::new(StubWidget)), Some(99));

        let result = stack.pop();
        assert!(result.is_some());
        assert_eq!(result.unwrap().focus_group_id, Some(99));
    }

    #[test]
    fn pop_id_returns_focus_group_id() {
        let mut stack = ModalStack::new();
        let id1 = stack.push_with_focus(Box::new(WidgetModalEntry::new(StubWidget)), Some(10));
        let _id2 = stack.push_with_focus(Box::new(WidgetModalEntry::new(StubWidget)), Some(20));

        let result = stack.pop_id(id1);
        assert!(result.is_some());
        assert_eq!(result.unwrap().focus_group_id, Some(10));
    }

    #[test]
    fn handle_event_returns_focus_group_id() {
        let mut stack = ModalStack::new();
        stack.push_with_focus(Box::new(WidgetModalEntry::new(StubWidget)), Some(77));

        let escape = Event::Key(KeyEvent {
            code: KeyCode::Escape,
            modifiers: Modifiers::empty(),
            kind: KeyEventKind::Press,
        });

        let result = stack.handle_event(&escape, None);
        assert!(result.is_some());
        assert_eq!(result.unwrap().focus_group_id, Some(77));
    }

    #[test]
    fn push_without_focus_has_none_group_id() {
        let mut stack = ModalStack::new();
        let modal_id = stack.push(Box::new(WidgetModalEntry::new(StubWidget)));

        assert_eq!(stack.focus_group_id(modal_id), None);
        assert_eq!(stack.top_focus_group_id(), None);
    }

    #[test]
    fn nested_focus_groups_track_correctly() {
        let mut stack = ModalStack::new();
        let _id1 = stack.push_with_focus(Box::new(WidgetModalEntry::new(StubWidget)), Some(1));
        let id2 = stack.push_with_focus(Box::new(WidgetModalEntry::new(StubWidget)), Some(2));
        let _id3 = stack.push_with_focus(Box::new(WidgetModalEntry::new(StubWidget)), Some(3));

        // Top should be group 3
        assert_eq!(stack.top_focus_group_id(), Some(3));

        // Pop top, now group 2 is on top
        stack.pop();
        assert_eq!(stack.top_focus_group_id(), Some(2));

        // Query specific modal
        assert_eq!(stack.focus_group_id(id2), Some(2));
    }

    // --- ARIA modal tests (bd-39vx.5) ---

    #[test]
    fn default_aria_modal_is_true() {
        let entry = WidgetModalEntry::new(StubWidget);
        assert!(entry.aria_modal);
    }

    #[test]
    fn aria_modal_builder() {
        let entry = WidgetModalEntry::new(StubWidget).with_aria_modal(false);
        assert!(!entry.aria_modal);
    }

    #[test]
    fn focusable_ids_builder() {
        let entry = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![1, 2, 3]);
        assert_eq!(entry.focusable_ids, Some(vec![1, 2, 3]));
    }

    #[test]
    fn stack_modal_aria_modal_trait() {
        let entry = WidgetModalEntry::new(StubWidget);
        assert!(StackModal::aria_modal(&entry)); // Default true

        let entry_non_aria = WidgetModalEntry::new(StubWidget).with_aria_modal(false);
        assert!(!StackModal::aria_modal(&entry_non_aria));
    }

    #[test]
    fn stack_modal_focusable_ids_trait() {
        let entry = WidgetModalEntry::new(StubWidget);
        assert!(StackModal::focusable_ids(&entry).is_none()); // Default none

        let entry_with_ids = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![10, 20]);
        assert_eq!(
            StackModal::focusable_ids(&entry_with_ids),
            Some(vec![10, 20])
        );
    }

    // --- ModalFocusIntegration tests ---

    #[test]
    fn focus_integration_push_creates_trap() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        // Register focusable nodes
        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(2, Rect::new(0, 1, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(100, Rect::new(0, 10, 10, 1))); // Outside modal

        // Focus outside modal initially
        focus.focus(100);
        assert_eq!(focus.current(), Some(100));

        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);

            // Push modal with focusable IDs
            let modal = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![1, 2]);
            let _modal_id = integrator.push_with_focus(Box::new(modal));

            // Focus should now be trapped
            assert!(integrator.is_focus_trapped());

            // Focus should move to first focusable in modal
            assert_eq!(integrator.focus().current(), Some(1));
        }
    }

    #[test]
    fn focus_integration_pop_restores_focus() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        // Register focusable nodes
        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(2, Rect::new(0, 1, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(100, Rect::new(0, 10, 10, 1))); // Trigger element

        // Focus the trigger element before opening modal
        focus.focus(100);
        assert_eq!(focus.current(), Some(100));

        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);

            // Push modal
            let modal = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![1, 2]);
            integrator.push_with_focus(Box::new(modal));

            // Focus is in modal
            assert!(integrator.is_focus_trapped());

            // Pop modal
            let result = integrator.pop_with_focus();
            assert!(result.is_some());

            // Focus should be restored to trigger element
            assert!(!integrator.is_focus_trapped());
            assert_eq!(integrator.focus().current(), Some(100));
        }
    }

    #[test]
    fn focus_integration_pop_id_with_focus_preserves_top_trap_and_restores_base_after_last_pop() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(2, Rect::new(0, 1, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(100, Rect::new(0, 10, 10, 1)));
        focus.focus(100);

        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
            let lower = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![1]);
            let lower_id = integrator.push_with_focus(Box::new(lower));
            let top = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![2]);
            integrator.push_with_focus(Box::new(top));

            let removed = integrator.pop_id_with_focus(lower_id);
            assert!(removed.is_some());
            assert!(integrator.is_focus_trapped());
            assert_eq!(integrator.focus().current(), Some(2));

            let final_result = integrator.pop_with_focus();
            assert!(final_result.is_some());
            assert!(!integrator.is_focus_trapped());
            assert_eq!(integrator.focus().current(), Some(100));
        }
    }

    #[test]
    fn focus_integration_pop_id_with_focus_preserves_unfocused_base_across_helper_instances() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(2, Rect::new(0, 1, 10, 1)));

        let lower_id;
        let upper_id;
        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
            let lower = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![1]);
            let upper = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![2]);
            lower_id = integrator.push_with_focus(Box::new(lower));
            upper_id = integrator.push_with_focus(Box::new(upper));
            assert_eq!(integrator.focus().current(), Some(2));
        }

        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
            let removed = integrator.pop_id_with_focus(lower_id);
            assert_eq!(removed.map(|result| result.id), Some(lower_id));
            assert_eq!(integrator.focus().current(), Some(2));
            assert!(integrator.is_focus_trapped());

            let closed = integrator.pop_with_focus();
            assert_eq!(closed.map(|result| result.id), Some(upper_id));
        }

        assert_eq!(focus.current(), None);
        assert!(!focus.is_trapped());
    }

    #[test]
    fn focus_integration_resync_focus_state_recovers_after_manual_stack_mutation() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(2, Rect::new(0, 1, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(100, Rect::new(0, 10, 10, 1)));
        focus.focus(100);

        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
            let modal = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![1, 2]);
            integrator.push_with_focus(Box::new(modal));
            assert!(integrator.is_focus_trapped());
            assert_eq!(integrator.focus().current(), Some(1));

            let result = integrator.stack_mut().pop();
            assert!(result.is_some());
            assert!(integrator.is_focus_trapped());

            integrator.resync_focus_state();
            assert!(!integrator.is_focus_trapped());
            assert_eq!(integrator.focus().current(), Some(100));
        }
    }

    #[test]
    fn focus_integration_resync_updates_inactive_modal_restore_targets_after_manual_focus_change() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        for id in 1..=4 {
            focus
                .graph_mut()
                .insert(FocusNode::new(id, Rect::new(0, 0, 10, 1)).with_tab_index(id as i32));
        }

        focus.focus(1);

        let upper_id;
        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
            let lower = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![2, 3]);
            integrator.push_with_focus(Box::new(lower));
            integrator.focus_mut().focus(3);

            let upper = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![4]);
            upper_id = integrator.push_with_focus(Box::new(upper));

            let _ = integrator.focus_mut().graph_mut().remove(4);
            integrator.resync_focus_state();
            assert_eq!(integrator.focus().current(), Some(3));

            integrator.focus_mut().focus(2);
            integrator.resync_focus_state();
            assert_eq!(integrator.focus().current(), Some(2));

            integrator
                .focus_mut()
                .graph_mut()
                .insert(FocusNode::new(4, Rect::new(0, 0, 10, 1)).with_tab_index(4));
            integrator.resync_focus_state();
            assert_eq!(integrator.focus().current(), Some(4));

            let result = integrator.pop_id_with_focus(upper_id);
            assert_eq!(result.map(|closed| closed.id), Some(upper_id));
            assert_eq!(integrator.focus().current(), Some(2));
            assert!(integrator.is_focus_trapped());
        }
    }

    #[test]
    fn focus_integration_pop_skips_closed_modal_focus_ids_when_background_focus_disappears() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(50, Rect::new(0, 1, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(100, Rect::new(0, 10, 10, 1)));
        focus.focus(100);

        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
            let modal = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![1]);
            integrator.push_with_focus(Box::new(modal));
            let _ = integrator.focus_mut().graph_mut().remove(100);

            let result = integrator.pop_with_focus();
            assert!(result.is_some());
            assert_eq!(integrator.focus().current(), Some(50));
            assert!(!integrator.is_focus_trapped());
        }
    }

    #[test]
    fn focus_integration_pop_removes_closed_modal_focus_group() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(2, Rect::new(0, 1, 10, 1)));

        focus.focus(1);

        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
            let modal = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![2]);
            integrator.push_with_focus(Box::new(modal));

            let result = integrator.pop_with_focus().unwrap();
            let group_id = result.focus_group_id.unwrap();

            assert!(!integrator.focus_mut().push_trap(group_id));
            assert!(!integrator.is_focus_trapped());
            assert_eq!(integrator.focus().current(), Some(1));
        }
    }

    #[test]
    fn focus_integration_escape_restores_focus() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(100, Rect::new(0, 10, 10, 1)));

        focus.focus(100);

        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);

            let modal = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![1]);
            integrator.push_with_focus(Box::new(modal));

            assert!(integrator.is_focus_trapped());

            // Simulate Escape key
            let escape = Event::Key(KeyEvent {
                code: KeyCode::Escape,
                modifiers: Modifiers::empty(),
                kind: KeyEventKind::Press,
            });
            let result = integrator.handle_event(&escape, None);

            assert!(result.is_some());
            assert!(!integrator.is_focus_trapped());
            assert_eq!(integrator.focus().current(), Some(100));
        }
    }

    #[test]
    fn focus_integration_applies_host_focus_events() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(2, Rect::new(0, 1, 10, 1)));
        focus.focus(2);

        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
            let modal = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![1]);
            integrator.push_with_focus(Box::new(modal));
            assert_eq!(integrator.focus().current(), Some(1));

            let blur = Event::Focus(false);
            assert!(integrator.handle_event(&blur, None).is_none());
            assert_eq!(integrator.focus().current(), None);

            let gain = Event::Focus(true);
            assert!(integrator.handle_event(&gain, None).is_none());
            assert_eq!(integrator.focus().current(), Some(1));
        }
    }

    #[test]
    fn focus_integration_non_aria_modal_no_trap() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(100, Rect::new(0, 10, 10, 1)));

        focus.focus(100);

        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);

            // Push non-ARIA modal (aria_modal = false)
            let modal = WidgetModalEntry::new(StubWidget)
                .with_aria_modal(false)
                .with_focusable_ids(vec![1]);
            integrator.push_with_focus(Box::new(modal));

            // Focus should NOT be trapped for non-ARIA modals
            assert!(!integrator.is_focus_trapped());
        }
    }

    #[test]
    fn focus_integration_rejected_empty_trap_does_not_leave_focus_group_behind() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus.focus(1);

        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
            let modal = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![]);
            integrator.push_with_focus(Box::new(modal));

            assert!(!integrator.is_focus_trapped());
            assert!(!integrator.focus_mut().push_trap(1));
            assert_eq!(integrator.focus().current(), Some(1));
        }
    }

    #[test]
    fn recreated_focus_integration_does_not_reuse_live_group_ids() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(2, Rect::new(0, 1, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(100, Rect::new(0, 10, 10, 1)));

        focus.focus(100);

        let first_group_id = {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
            let modal = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![1]);
            let modal_id = integrator.push_with_focus(Box::new(modal));
            integrator.stack().focus_group_id(modal_id).unwrap()
        };

        let second_group_id = {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
            let modal = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![2]);
            let modal_id = integrator.push_with_focus(Box::new(modal));
            integrator.stack().focus_group_id(modal_id).unwrap()
        };

        assert_ne!(first_group_id, second_group_id);

        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
            let top = integrator.pop_with_focus().unwrap();
            assert_eq!(top.focus_group_id, Some(second_group_id));
            assert!(integrator.is_focus_trapped());
            assert_eq!(integrator.focus().current(), Some(1));

            let lower = integrator.pop_with_focus().unwrap();
            assert_eq!(lower.focus_group_id, Some(first_group_id));
            assert!(!integrator.is_focus_trapped());
            assert_eq!(integrator.focus().current(), Some(100));
        }
    }

    #[test]
    fn focus_integration_does_not_collide_with_existing_group_ids() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(99, Rect::new(0, 1, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(100, Rect::new(0, 10, 10, 1)));
        focus.create_group(1000, vec![99]);
        focus.focus(100);

        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
            let modal = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![1]);
            integrator.push_with_focus(Box::new(modal));
            let _ = integrator.pop_with_focus().unwrap();
            assert!(integrator.focus_mut().push_trap(1000));
            assert_eq!(integrator.focus().current(), Some(99));
        }
    }

    #[test]
    fn focus_integration_nested_modals() {
        use crate::focus::{FocusManager, FocusNode};
        use ftui_core::geometry::Rect;

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();

        // Register nodes for both modals and background
        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(2, Rect::new(0, 1, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(10, Rect::new(0, 5, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(11, Rect::new(0, 6, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(100, Rect::new(0, 10, 10, 1)));

        focus.focus(100);

        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);

            // Push first modal
            let modal1 = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![1, 2]);
            integrator.push_with_focus(Box::new(modal1));
            assert_eq!(integrator.focus().current(), Some(1));

            // Push second modal (nested)
            let modal2 = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![10, 11]);
            integrator.push_with_focus(Box::new(modal2));
            assert_eq!(integrator.focus().current(), Some(10));

            // Pop second modal - should restore to first modal's focus
            integrator.pop_with_focus();
            assert_eq!(integrator.focus().current(), Some(1));

            // Pop first modal - should restore to original focus
            integrator.pop_with_focus();
            assert_eq!(integrator.focus().current(), Some(100));
        }
    }

    #[cfg(feature = "tracing")]
    #[test]
    fn tracing_modal_render_span_has_required_fields() {
        let state = Arc::new(Mutex::new(TraceState::default()));
        let _trace_test_guard = crate::tracing_test_support::acquire();
        let subscriber = tracing_subscriber::registry().with(TraceCapture {
            state: Arc::clone(&state),
        });
        let _guard = tracing::subscriber::set_default(subscriber);

        tracing::callsite::rebuild_interest_cache();
        let mut stack = ModalStack::new();
        stack.push(Box::new(WidgetModalEntry::new(StubWidget)));
        let mut pool = GraphemePool::new();
        let mut frame = Frame::new(80, 24, &mut pool);
        stack.render(&mut frame, Rect::new(0, 0, 80, 24));
        tracing::callsite::rebuild_interest_cache();

        let snapshot = state.lock().expect("trace state lock");
        assert!(snapshot.modal_render_seen, "expected modal.render span");
        assert!(
            snapshot.modal_render_has_modal_type,
            "modal.render missing modal_type field"
        );
        assert!(
            snapshot.modal_render_has_focus_trapped,
            "modal.render missing focus_trapped field"
        );
        assert!(
            snapshot.modal_render_has_backdrop_active,
            "modal.render missing backdrop_active field"
        );
        assert!(
            snapshot.modal_render_duration_recorded,
            "modal.render did not record render_duration_us"
        );
    }

    #[cfg(feature = "tracing")]
    #[test]
    fn tracing_focus_change_and_trap_events_emitted_for_modal_lifecycle() {
        use crate::focus::{FocusManager, FocusNode};

        let state = Arc::new(Mutex::new(TraceState::default()));
        let _trace_test_guard = crate::tracing_test_support::acquire();
        let subscriber = tracing_subscriber::registry().with(TraceCapture {
            state: Arc::clone(&state),
        });
        let _guard = tracing::subscriber::set_default(subscriber);

        let mut stack = ModalStack::new();
        let mut focus = FocusManager::new();
        focus
            .graph_mut()
            .insert(FocusNode::new(1, Rect::new(0, 0, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(2, Rect::new(0, 1, 10, 1)));
        focus
            .graph_mut()
            .insert(FocusNode::new(100, Rect::new(0, 10, 10, 1)));
        focus.focus(100);

        tracing::callsite::rebuild_interest_cache();
        {
            let mut integrator = ModalFocusIntegration::new(&mut stack, &mut focus);
            let modal = WidgetModalEntry::new(StubWidget).with_focusable_ids(vec![1, 2]);
            tracing::callsite::rebuild_interest_cache();
            integrator.push_with_focus(Box::new(modal));

            let escape = Event::Key(KeyEvent {
                code: KeyCode::Escape,
                modifiers: Modifiers::empty(),
                kind: KeyEventKind::Press,
            });
            tracing::callsite::rebuild_interest_cache();
            let _ = integrator.handle_event(&escape, None);
        }
        tracing::callsite::rebuild_interest_cache();

        let snapshot = state.lock().expect("trace state lock");
        assert!(
            snapshot.focus_change_count >= 2,
            "expected focus.change events for trap lifecycle, got {}",
            snapshot.focus_change_count
        );
        assert!(
            snapshot.trap_push_count >= 1,
            "expected focus.trap_push event"
        );
        assert!(
            snapshot.trap_pop_count >= 1,
            "expected focus.trap_pop event"
        );
    }
}
