//! # Component
//!
//! This module provides the Component trait, which is used to define
//! components for visual testing and debugging.
//!
//! Additionally, it includes layouts for rendering component examples
//! and example groups, as well as the distributed slice mechanism for
//! registering components.

mod component_layout;

use std::sync::LazyLock;

pub use component_layout::*;

use collections::HashMap;
use gpui::{AnyElement, App, SharedString, Window};
use parking_lot::RwLock;
use strum::{Display, EnumString};

pub fn components() -> ComponentRegistry {
    COMPONENT_DATA.read().clone()
}

pub fn init() {
    for f in inventory::iter::<ComponentFn>() {
        (f.0)();
    }
}

pub struct ComponentFn(fn());

impl ComponentFn {
    pub const fn new(f: fn()) -> Self {
        Self(f)
    }
}

inventory::collect!(ComponentFn);

/// Private internals for macros.
#[doc(hidden)]
pub mod __private {
    pub use inventory;
}

pub fn register_component<T: Component>() {
    let id = T::id();
    let metadata = ComponentMetadata {
        id: id.clone(),
        description: T::description().map(Into::into),
        name: SharedString::new_static(T::name()),
        preview: Some(T::preview),
        scope: T::scope(),
        sort_name: SharedString::new_static(T::sort_name()),
        status: T::status(),
    };

    let mut data = COMPONENT_DATA.write();
    data.components.insert(id, metadata);
}

pub static COMPONENT_DATA: LazyLock<RwLock<ComponentRegistry>> =
    LazyLock::new(|| RwLock::new(ComponentRegistry::default()));

#[derive(Default, Clone)]
pub struct ComponentRegistry {
    components: HashMap<ComponentId, ComponentMetadata>,
}

impl ComponentRegistry {
    pub fn previews(&self) -> Vec<&ComponentMetadata> {
        self.components
            .values()
            .filter(|c| c.preview.is_some())
            .collect()
    }

    pub fn sorted_previews(&self) -> Vec<ComponentMetadata> {
        let mut previews: Vec<ComponentMetadata> = self.previews().into_iter().cloned().collect();
        previews.sort_by_key(|a| a.name());
        previews
    }

    pub fn components(&self) -> Vec<&ComponentMetadata> {
        self.components.values().collect()
    }

    pub fn sorted_components(&self) -> Vec<ComponentMetadata> {
        let mut components: Vec<ComponentMetadata> =
            self.components().into_iter().cloned().collect();
        components.sort_by_key(|a| a.name());
        components
    }

    pub fn component_map(&self) -> HashMap<ComponentId, ComponentMetadata> {
        self.components.clone()
    }

    pub fn get(&self, id: &ComponentId) -> Option<&ComponentMetadata> {
        self.components.get(id)
    }

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

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ComponentId(pub &'static str);

#[derive(Clone)]
pub struct ComponentMetadata {
    id: ComponentId,
    description: Option<SharedString>,
    name: SharedString,
    preview: Option<fn(&mut Window, &mut App) -> Option<AnyElement>>,
    scope: ComponentScope,
    sort_name: SharedString,
    status: ComponentStatus,
}

impl ComponentMetadata {
    pub fn id(&self) -> ComponentId {
        self.id.clone()
    }

    pub fn description(&self) -> Option<SharedString> {
        self.description.clone()
    }

    pub fn name(&self) -> SharedString {
        self.name.clone()
    }

    pub fn preview(&self) -> Option<fn(&mut Window, &mut App) -> Option<AnyElement>> {
        self.preview
    }

    pub fn scope(&self) -> ComponentScope {
        self.scope.clone()
    }

    pub fn sort_name(&self) -> SharedString {
        self.sort_name.clone()
    }

    pub fn scopeless_name(&self) -> SharedString {
        self.name
            .clone()
            .split("::")
            .last()
            .unwrap_or(&self.name)
            .to_string()
            .into()
    }

    pub fn status(&self) -> ComponentStatus {
        self.status.clone()
    }
}

/// Implement this trait to define a UI component. This will allow you to
/// derive `RegisterComponent` on it, in turn allowing you to preview the
/// contents of the preview fn in `workspace: open component preview`.
///
/// This can be useful for visual debugging and testing, documenting UI
/// patterns, or simply showing all the variants of a component.
///
/// Generally you will want to implement at least `scope` and `preview`
/// from this trait, so you can preview the component, and it will show up
/// in a section that makes sense.
pub trait Component {
    /// The component's unique identifier.
    ///
    /// Used to access previews, or state for more
    /// complex, stateful components.
    fn id() -> ComponentId {
        ComponentId(Self::name())
    }
    /// Returns the scope of the component.
    ///
    /// This scope is used to determine how components and
    /// their previews are displayed and organized.
    fn scope() -> ComponentScope {
        ComponentScope::None
    }
    /// The ready status of this component.
    ///
    /// Use this to mark when components are:
    /// - `WorkInProgress`: Still being designed or are partially implemented.
    /// - `EngineeringReady`: Ready to be implemented.
    /// - `Deprecated`: No longer recommended for use.
    ///
    /// Defaults to [`Live`](ComponentStatus::Live).
    fn status() -> ComponentStatus {
        ComponentStatus::Live
    }
    /// The name of the component.
    ///
    /// This name is used to identify the component
    /// and is usually derived from the component's type.
    fn name() -> &'static str {
        std::any::type_name::<Self>()
    }
    /// Returns a name that the component should be sorted by.
    ///
    /// Implement this if the component should be sorted in an alternate order than its name.
    ///
    /// Example:
    ///
    /// For example, to group related components together when sorted:
    ///
    /// - Button      -> ButtonA
    /// - IconButton  -> ButtonBIcon
    /// - ToggleButton -> ButtonCToggle
    ///
    /// This naming scheme keeps these components together and allows them to /// be sorted in a logical order.
    fn sort_name() -> &'static str {
        Self::name()
    }
    /// An optional description of the component.
    ///
    /// This will be displayed in the component's preview. To show a
    /// component's doc comment as it's description, derive `Documented`.
    ///
    /// Example:
    ///
    /// ```
    /// use documented::Documented;
    ///
    /// /// This is a doc comment.
    /// #[derive(Documented)]
    /// struct MyComponent;
    ///
    /// impl MyComponent {
    ///     fn description() -> Option<&'static str> {
    ///         Some(Self::DOCS)
    ///     }
    /// }
    /// ```
    ///
    /// This will result in "This is a doc comment." being passed
    /// to the component's description.
    fn description() -> Option<&'static str> {
        None
    }
    /// The component's preview.
    ///
    /// An element returned here will be shown in the component's preview.
    ///
    /// Useful component helpers:
    /// - [`component::single_example`]
    /// - [`component::component_group`]
    /// - [`component::component_group_with_title`]
    ///
    /// Note: Any arbitrary element can be returned here.
    ///
    /// This is useful for displaying related UI to the component you are
    /// trying to preview, such as a button that opens a modal or shows a
    /// tooltip on hover, or a grid of icons showcasing all the icons available.
    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
        None
    }
}

/// The ready status of this component.
///
/// Use this to mark when components are:
/// - `WorkInProgress`: Still being designed or are partially implemented.
/// - `EngineeringReady`: Ready to be implemented.
/// - `Deprecated`: No longer recommended for use.
///
/// Defaults to [`Live`](ComponentStatus::Live).
#[derive(Debug, Clone, PartialEq, Eq, Hash, Display, EnumString)]
pub enum ComponentStatus {
    #[strum(serialize = "Work In Progress")]
    WorkInProgress,
    #[strum(serialize = "Ready To Build")]
    EngineeringReady,
    Live,
    Deprecated,
}

impl ComponentStatus {
    pub fn description(&self) -> &str {
        match self {
            ComponentStatus::WorkInProgress => {
                "These components are still being designed or refined. They shouldn't be used in the app yet."
            }
            ComponentStatus::EngineeringReady => {
                "These components are design complete or partially implemented, and are ready for an engineer to complete their implementation."
            }
            ComponentStatus::Live => "These components are ready for use in the app.",
            ComponentStatus::Deprecated => {
                "These components are no longer recommended for use in the app, and may be removed in a future release."
            }
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Display, EnumString)]
pub enum ComponentScope {
    Agent,
    Collaboration,
    #[strum(serialize = "Data Display")]
    DataDisplay,
    Editor,
    #[strum(serialize = "Images & Icons")]
    Images,
    #[strum(serialize = "Forms & Input")]
    Input,
    #[strum(serialize = "Layout & Structure")]
    Layout,
    #[strum(serialize = "Loading & Progress")]
    Loading,
    Navigation,
    #[strum(serialize = "Unsorted")]
    None,
    Notification,
    #[strum(serialize = "Overlays & Layering")]
    Overlays,
    Onboarding,
    Status,
    Typography,
    Utilities,
    #[strum(serialize = "Version Control")]
    VersionControl,
}
