//! Composable trait and identifier types.

use super::{Bounds, ZGroup, ZOrder};

// Local types from this crate
pub use crate::{frame::FrameBuffer, highlight::Style};

/// Unique identifier for composable elements.
///
/// Each composable element has a unique ID that allows it to be
/// registered, retrieved, and manipulated in the compositor.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ComposableId {
    /// Base layer (background)
    Base,
    /// Tab line at the top
    TabLine,
    /// Status line at the bottom
    StatusLine,
    /// Editor window by index
    Window(usize),
    /// Floating window by index
    FloatingWindow(usize),
    /// Custom plugin identifier
    Custom(&'static str),
}

impl ComposableId {
    /// Get the default z-group for this ID type.
    ///
    /// This determines the initial z-order group when a composable
    /// is registered without an explicit z-order.
    #[must_use]
    pub const fn default_group(&self) -> ZGroup {
        match self {
            Self::Base | Self::TabLine | Self::StatusLine => ZGroup::Base,
            Self::Window(_) | Self::Custom(_) => ZGroup::Editor,
            Self::FloatingWindow(_) => ZGroup::Floating,
        }
    }
}

/// Trait for elements that can be composited by the compositor.
///
/// Composables are renderable UI elements with z-ordering support.
/// They can capture keyboard input and provide cursor positions.
pub trait Composable: std::fmt::Debug + Send + Sync {
    /// Get the unique identifier for this composable.
    fn id(&self) -> ComposableId;

    /// Get the current z-order.
    fn z_order(&self) -> ZOrder;

    /// Set the z-order.
    ///
    /// Called by the compositor when `bring_to_front`/`send_to_back`/`set_z_order`
    /// operations are performed.
    fn set_z_order(&mut self, z: ZOrder);

    /// Check if this composable is visible.
    ///
    /// Invisible composables are skipped during rendering.
    fn is_visible(&self) -> bool;

    /// Compute bounds based on screen dimensions.
    ///
    /// The bounds may depend on the screen size (e.g., for full-screen elements
    /// or elements positioned relative to the screen edges).
    fn bounds(&self, screen_width: u16, screen_height: u16) -> Bounds;

    /// Render this composable to the frame buffer.
    ///
    /// # Arguments
    ///
    /// * `buffer` - The frame buffer to render to
    /// * `default_style` - The default style to use for unstyled content
    fn render(&self, buffer: &mut FrameBuffer, default_style: &Style);

    /// Check if this composable captures keyboard input.
    ///
    /// When true, this composable can be the keyboard target.
    /// Default is true.
    fn captures_keyboard(&self) -> bool {
        true
    }

    /// Get the cursor position if this composable owns the cursor.
    ///
    /// Returns the (x, y) position in screen coordinates, or None if
    /// this composable doesn't own the cursor.
    fn cursor_position(&self) -> Option<(u16, u16)> {
        None
    }
}

#[cfg(test)]
#[path = "composable_tests.rs"]
mod tests;
