//! Flex module.
//!
//! This public module implements the Liora flexbox layout wrapper with Liora-friendly builder helpers. It keeps the reusable
//! component logic inside `liora-components` rather than Gallery or Docs so
//! downstream GPUI applications can compose the same behavior with their own
//! app state, assets, and release policy.
//!
//! ## Usage model
//!
//! Components in this module render native GPUI element trees. Stateless builder
//! values can be constructed inline, while controls with focus, selection,
//! popup, drag, or editing state should be stored as `gpui::Entity<T>` fields in
//! the parent view so state survives GPUI render passes.
//!
//! ## Design contract
//!
//! The implementation should use Liora theme tokens from `liora-core` and
//! `liora-theme`, keep accessibility-oriented keyboard/pointer behavior close to
//! the component, and avoid app-specific Gallery/Docs resources in this SDK
//! crate.

use gpui::{
    AnyElement, App, Component, ElementId, Hsla, IntoElement, Pixels, RenderOnce, ScrollHandle,
    Window, div, prelude::*, px,
};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum FlexDirection {
    Row,
    Column,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum CrossAlign {
    Start,
    Center,
    End,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum MainAlign {
    Start,
    Center,
    End,
    Between,
}

/// Fluent native GPUI component for rendering Liora flex.
pub struct Flex {
    children: Vec<AnyElement>,
    direction: Option<FlexDirection>,
    wrap: bool,
    gap: Option<Pixels>,
    padding: Option<Pixels>,
    padding_x: Option<Pixels>,
    padding_y: Option<Pixels>,
    margin_y: Option<Pixels>,
    height: Option<Pixels>,
    width: Option<Pixels>,
    width_percent: Option<f32>,
    h_full: bool,
    w_full: bool,
    size_full: bool,
    flex_1: bool,
    flex_none: bool,
    min_h_0: bool,
    bg: Option<Hsla>,
    text_color: Option<Hsla>,
    text_size: Option<Pixels>,
    bold: bool,
    border: bool,
    border_color: Option<Hsla>,
    rounded: Option<Pixels>,
    relative: bool,
    overflow_hidden: bool,
    overflow_y_scroll: bool,
    id: Option<ElementId>,
    scroll_handle: Option<ScrollHandle>,
    align: Option<CrossAlign>,
    justify: Option<MainAlign>,
}

impl Flex {
    /// Creates `Flex` with default theme-driven styling and no optional callbacks attached.
    pub fn new() -> Self {
        Self {
            children: Vec::new(),
            direction: None,
            wrap: false,
            gap: None,
            padding: None,
            padding_x: None,
            padding_y: None,
            margin_y: None,
            height: None,
            width: None,
            width_percent: None,
            h_full: false,
            w_full: false,
            size_full: false,
            flex_1: false,
            flex_none: false,
            min_h_0: false,
            bg: None,
            text_color: None,
            text_size: None,
            bold: false,
            border: false,
            border_color: None,
            rounded: None,
            relative: false,
            overflow_hidden: false,
            overflow_y_scroll: false,
            id: None,
            scroll_handle: None,
            align: None,
            justify: None,
        }
    }

    /// Sets the row value used by the component.
    pub fn row(mut self) -> Self {
        self.direction = Some(FlexDirection::Row);
        self
    }

    /// Sets the column value used by the component.
    pub fn column(mut self) -> Self {
        self.direction = Some(FlexDirection::Column);
        self
    }

    /// Allows child content to wrap onto additional lines.
    pub fn wrap(mut self) -> Self {
        self.wrap = true;
        self
    }

    /// Sets gap using raw pixel units.
    pub fn gap_px(mut self, gap: f32) -> Self {
        self.gap = Some(px(gap));
        self
    }

    /// Applies the predefined gap sm sizing preset.
    pub fn gap_sm(self) -> Self {
        self.gap_px(8.0)
    }

    /// Applies the predefined gap md sizing preset.
    pub fn gap_md(self) -> Self {
        self.gap_px(12.0)
    }

    /// Applies the predefined gap lg sizing preset.
    pub fn gap_lg(self) -> Self {
        self.gap_px(16.0)
    }

    /// Applies the predefined gap xl sizing preset.
    pub fn gap_xl(self) -> Self {
        self.gap_px(24.0)
    }

    /// Sets padding using raw pixel units.
    pub fn padding_px(mut self, padding: f32) -> Self {
        self.padding = Some(px(padding));
        self
    }

    /// Applies the predefined padding sm sizing preset.
    pub fn padding_sm(self) -> Self {
        self.padding_px(8.0)
    }

    /// Applies the predefined padding md sizing preset.
    pub fn padding_md(self) -> Self {
        self.padding_px(16.0)
    }

    /// Applies the predefined padding lg sizing preset.
    pub fn padding_lg(self) -> Self {
        self.padding_px(24.0)
    }

    /// Sets padding x using raw pixel units.
    pub fn padding_x_px(mut self, padding: f32) -> Self {
        self.padding_x = Some(px(padding));
        self
    }

    /// Sets padding x using design-system unit values.
    pub fn padding_x_units(self, padding: f32) -> Self {
        self.padding_x_px(padding)
    }

    /// Sets padding y using raw pixel units.
    pub fn padding_y_px(mut self, padding: f32) -> Self {
        self.padding_y = Some(px(padding));
        self
    }

    /// Sets margin y using raw pixel units.
    pub fn margin_y_px(mut self, margin: f32) -> Self {
        self.margin_y = Some(px(margin));
        self
    }

    /// Sets margin y using design-system unit values.
    pub fn margin_y_units(self, margin: f32) -> Self {
        self.margin_y_px(margin)
    }

    /// Applies the predefined height px sizing preset.
    pub fn height_px(mut self, height: f32) -> Self {
        self.height = Some(px(height));
        self
    }

    /// Applies the predefined height units sizing preset.
    pub fn height_units(self, height: f32) -> Self {
        self.height_px(height)
    }

    /// Applies the predefined width px sizing preset.
    pub fn width_px(mut self, width: f32) -> Self {
        self.width = Some(px(width));
        self
    }

    /// Applies the predefined width percent sizing preset.
    pub fn width_percent(mut self, percent: f32) -> Self {
        self.width_percent = Some((percent / 100.0).clamp(0.0, 1.0));
        self
    }

    /// Sets the h full value used by the component.
    pub fn h_full(mut self) -> Self {
        self.h_full = true;
        self
    }

    /// Sets the w full value used by the component.
    pub fn w_full(mut self) -> Self {
        self.w_full = true;
        self
    }

    /// Applies the predefined size full sizing preset.
    pub fn size_full(mut self) -> Self {
        self.size_full = true;
        self
    }

    /// Sets the flex 1 value used by the component.
    pub fn flex_1(mut self) -> Self {
        self.flex_1 = true;
        self
    }

    /// Sets the flex none value used by the component.
    pub fn flex_none(mut self) -> Self {
        self.flex_none = true;
        self
    }

    /// Sets the minimum h 0 limit.
    pub fn min_h_0(mut self) -> Self {
        self.min_h_0 = true;
        self
    }

    /// Sets the bg used by the rendered component.
    pub fn bg(mut self, color: Hsla) -> Self {
        self.bg = Some(color);
        self
    }

    /// Applies the foreground text color.
    pub fn text_color(mut self, color: Hsla) -> Self {
        self.text_color = Some(color);
        self
    }

    /// Sets text size using raw pixel units.
    pub fn text_size_px(mut self, size: f32) -> Self {
        self.text_size = Some(px(size));
        self
    }

    /// Applies the predefined `text_xs` sizing preset.
    pub fn text_xs(self) -> Self {
        self.text_size_px(12.0)
    }

    /// Applies the predefined text sm sizing preset.
    pub fn text_sm(self) -> Self {
        self.text_size_px(14.0)
    }

    /// Applies bold font weight.
    pub fn bold(mut self) -> Self {
        self.bold = true;
        self
    }

    /// Toggles or applies the component border treatment.
    pub fn border(mut self) -> Self {
        self.border = true;
        self
    }

    /// Sets the default border color.
    pub fn border_color(mut self, color: Hsla) -> Self {
        self.border_color = Some(color);
        self
    }

    /// Sets rounded using raw pixel units.
    pub fn rounded_px(mut self, radius: f32) -> Self {
        self.rounded = Some(px(radius));
        self
    }

    /// Sets rounded using design-system unit values.
    pub fn rounded_units(self, radius: f32) -> Self {
        self.rounded_px(radius)
    }

    /// Applies the predefined rounded md sizing preset.
    pub fn rounded_md(mut self) -> Self {
        self.rounded = Some(px(8.0));
        self
    }

    /// Sets the rounded pill value used by the component.
    pub fn rounded_pill(mut self) -> Self {
        self.rounded = Some(px(999.0));
        self
    }

    /// Sets the relative value used by the component.
    pub fn relative(mut self) -> Self {
        self.relative = true;
        self
    }

    /// Sets the overflow hidden value used by the component.
    pub fn overflow_hidden(mut self) -> Self {
        self.overflow_hidden = true;
        self
    }

    /// Sets the overflow y-scroll value used by the component.
    pub fn overflow_y_scroll(mut self) -> Self {
        self.overflow_y_scroll = true;
        self
    }

    /// Assigns a stable element id used by GPUI state, hit testing, and automated interaction tests.
    pub fn id(mut self, id: impl Into<ElementId>) -> Self {
        self.id = Some(id.into());
        self
    }

    /// Sets the track scroll value used by the component.
    pub fn track_scroll(mut self, handle: &ScrollHandle) -> Self {
        self.scroll_handle = Some(handle.clone());
        self
    }

    /// Sets the align start value used by the component.
    pub fn align_start(mut self) -> Self {
        self.align = Some(CrossAlign::Start);
        self
    }

    /// Sets the align center value used by the component.
    pub fn align_center(mut self) -> Self {
        self.align = Some(CrossAlign::Center);
        self
    }

    /// Sets the align end value used by the component.
    pub fn align_end(mut self) -> Self {
        self.align = Some(CrossAlign::End);
        self
    }

    /// Sets the justify start value used by the component.
    pub fn justify_start(mut self) -> Self {
        self.justify = Some(MainAlign::Start);
        self
    }

    /// Sets the justify center value used by the component.
    pub fn justify_center(mut self) -> Self {
        self.justify = Some(MainAlign::Center);
        self
    }

    /// Sets the justify end value used by the component.
    pub fn justify_end(mut self) -> Self {
        self.justify = Some(MainAlign::End);
        self
    }

    /// Sets the justify between value used by the component.
    pub fn justify_between(mut self) -> Self {
        self.justify = Some(MainAlign::Between);
        self
    }

    /// Centers content on both layout axes.
    pub fn center(self) -> Self {
        self.align_center().justify_center()
    }

    /// Adds a child element to the component body.
    pub fn child(mut self, child: impl IntoElement) -> Self {
        self.children.push(child.into_any_element());
        self
    }

    /// Replaces or appends child elements rendered by the component.
    pub fn children(mut self, children: impl IntoIterator<Item = impl IntoElement>) -> Self {
        self.children
            .extend(children.into_iter().map(|child| child.into_any_element()));
        self
    }
}

impl RenderOnce for Flex {
    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
        let mut el = div();

        if self.direction.is_some()
            || self.align.is_some()
            || self.justify.is_some()
            || self.gap.is_some()
        {
            el = el.flex();
        }

        match self.direction {
            Some(FlexDirection::Row) => el = el.flex_row(),
            Some(FlexDirection::Column) => el = el.flex_col(),
            None => {}
        }

        if self.wrap {
            el = el.flex_wrap();
        }
        if let Some(gap) = self.gap {
            el = el.gap(gap);
        }
        if let Some(padding) = self.padding {
            el = el.p(padding);
        }
        if let Some(padding_x) = self.padding_x {
            el = el.px(padding_x);
        }
        if let Some(padding_y) = self.padding_y {
            el = el.py(padding_y);
        }
        if let Some(margin_y) = self.margin_y {
            el = el.my(margin_y);
        }
        if let Some(height) = self.height {
            el = el.h(height);
        }
        if let Some(width) = self.width {
            el = el.w(width);
        }
        if let Some(width_percent) = self.width_percent {
            el = el.w(gpui::relative(width_percent));
        }
        if self.h_full {
            el = el.h_full();
        }
        if self.w_full {
            el = el.w_full();
        }
        if self.size_full {
            el = el.size_full();
        }
        if self.flex_1 {
            el = el.flex_1();
        }
        if self.flex_none {
            el = el.flex_none();
        }
        if self.min_h_0 {
            el = el.min_h_0();
        }
        if let Some(bg) = self.bg {
            el = el.bg(bg);
        }
        if let Some(color) = self.text_color {
            el = el.text_color(color);
        }
        if let Some(size) = self.text_size {
            el = el.text_size(size);
        }
        if self.bold {
            el = el.font_weight(gpui::FontWeight::BOLD);
        }
        if self.border {
            el = el.border_1();
        }
        if let Some(color) = self.border_color {
            el = el.border_color(color);
        }
        if let Some(radius) = self.rounded {
            el = el.rounded(radius);
        }
        if self.relative {
            el = el.relative();
        }
        if self.overflow_hidden {
            el = el.overflow_hidden();
        }

        match self.align {
            Some(CrossAlign::Start) => el = el.items_start(),
            Some(CrossAlign::Center) => el = el.items_center(),
            Some(CrossAlign::End) => el = el.items_end(),
            None => {}
        }
        match self.justify {
            Some(MainAlign::Start) => el = el.justify_start(),
            Some(MainAlign::Center) => el = el.justify_center(),
            Some(MainAlign::End) => el = el.justify_end(),
            Some(MainAlign::Between) => el = el.justify_between(),
            None => {}
        }

        if let Some(id) = self.id {
            let mut stateful = el.id(id);
            if self.overflow_y_scroll {
                stateful = stateful.overflow_y_scroll();
            }
            if let Some(scroll_handle) = self.scroll_handle {
                stateful = stateful.track_scroll(&scroll_handle);
            }
            stateful.children(self.children).into_any_element()
        } else {
            el.children(self.children).into_any_element()
        }
    }
}

impl IntoElement for Flex {
    type Element = Component<Self>;
    fn into_element(self) -> Self::Element {
        Component::new(self)
    }
}

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

    #[test]
    fn flex_tracks_scroll_container_configuration() {
        let handle = ScrollHandle::new();
        let flex = Flex::new()
            .column()
            .height_px(320.0)
            .id("test-scroll")
            .overflow_y_scroll()
            .track_scroll(&handle);

        assert_eq!(flex.direction, Some(FlexDirection::Column));
        assert_eq!(flex.height, Some(px(320.0)));
        assert!(flex.overflow_y_scroll);
        assert!(flex.scroll_handle.is_some());
    }

    #[test]
    fn flex_tracks_visual_box_configuration() {
        let flex = Flex::new()
            .row()
            .wrap()
            .gap_lg()
            .padding_md()
            .width_percent(75.0)
            .rounded_md()
            .border();

        assert_eq!(flex.direction, Some(FlexDirection::Row));
        assert!(flex.wrap);
        assert_eq!(flex.gap, Some(px(16.0)));
        assert_eq!(flex.padding, Some(px(16.0)));
        assert_eq!(flex.width_percent, Some(0.75));
        assert_eq!(flex.rounded, Some(px(8.0)));
        assert!(flex.border);
    }
}
