use std::rc::Rc;

use crate::element::{
    content::TextContent,
    style::{
        BoxShadow, Color, ColorSource, CornerRadii, Edges, LinearGradient, Overflow, PaintProps,
        StyleProps, TextStyle,
    },
    types::{BoxElement, ComponentElement, ComponentFn, Key, TextElement},
    Element,
};

// ── Macro to generate container builders (Column, Row, View) ──────────────

macro_rules! container_builder {
    ($name:ident, $variant:ident, $doc:expr) => {
        #[doc = $doc]
        pub struct $name(BoxElement);

        impl $name {
            /// Creates an empty container; add children with [`.child`](Self::child).
            pub fn new() -> Self {
                $name(BoxElement::default())
            }

            /// Uniform gap between flex children, in logical points (both row and column axes).
            pub fn gap(mut self, v: f32) -> Self {
                self.0.style.gap = Some(v);
                self
            }
            /// Gap between columns (Taffy `gap.width`), overriding the uniform [`gap`](Self::gap)
            /// for this axis.
            ///
            /// In a `Column` container this is the cross-axis gap; in a `Row` it is the main-axis
            /// gap between children.
            pub fn column_gap(mut self, v: f32) -> Self {
                self.0.style.column_gap = Some(v);
                self
            }
            /// Gap between rows (Taffy `gap.height`), overriding the uniform [`gap`](Self::gap)
            /// for this axis.
            ///
            /// In a `Column` container this is the main-axis gap between children; in a `Row` it
            /// is the cross-axis gap.
            pub fn row_gap(mut self, v: f32) -> Self {
                self.0.style.row_gap = Some(v);
                self
            }
            /// Sets a paint-only opacity multiplier for this container and its descendants.
            ///
            /// Values are clamped to `0.0..=1.0`; non-finite inputs fall back to `1.0`.
            pub fn opacity(mut self, v: f32) -> Self {
                self.0.style.opacity = if v.is_finite() {
                    v.clamp(0.0, 1.0)
                } else {
                    1.0
                };
                self
            }
            /// Uniform padding on all sides, in logical points.
            pub fn padding(mut self, v: f32) -> Self {
                self.0.style.padding = Some(Edges::all(v));
                self
            }
            /// Padding on the left and right sides, in logical points.
            pub fn padding_x(mut self, v: f32) -> Self {
                let mut edges = self.0.style.padding.take().unwrap_or_default();
                edges.left = v;
                edges.right = v;
                self.0.style.padding = Some(edges);
                self
            }
            /// Padding on the top and bottom sides, in logical points.
            pub fn padding_y(mut self, v: f32) -> Self {
                let mut edges = self.0.style.padding.take().unwrap_or_default();
                edges.top = v;
                edges.bottom = v;
                self.0.style.padding = Some(edges);
                self
            }
            /// Padding on the top side only, in logical points.
            pub fn padding_top(mut self, v: f32) -> Self {
                let mut edges = self.0.style.padding.take().unwrap_or_default();
                edges.top = v;
                self.0.style.padding = Some(edges);
                self
            }
            /// Padding on the right side only, in logical points.
            pub fn padding_right(mut self, v: f32) -> Self {
                let mut edges = self.0.style.padding.take().unwrap_or_default();
                edges.right = v;
                self.0.style.padding = Some(edges);
                self
            }
            /// Padding on the bottom side only, in logical points.
            pub fn padding_bottom(mut self, v: f32) -> Self {
                let mut edges = self.0.style.padding.take().unwrap_or_default();
                edges.bottom = v;
                self.0.style.padding = Some(edges);
                self
            }
            /// Padding on the left side only, in logical points.
            pub fn padding_left(mut self, v: f32) -> Self {
                let mut edges = self.0.style.padding.take().unwrap_or_default();
                edges.left = v;
                self.0.style.padding = Some(edges);
                self
            }
            /// Fixed width in logical points.
            pub fn width(mut self, v: f32) -> Self {
                self.0.style.width = Some(crate::element::style::Dimension::Points(v));
                self
            }
            /// Fixed height in logical points.
            pub fn height(mut self, v: f32) -> Self {
                self.0.style.height = Some(crate::element::style::Dimension::Points(v));
                self
            }
            pub fn flex_grow(mut self, v: f32) -> Self {
                self.0.style.flex_grow = Some(v);
                self
            }
            pub fn align_items(mut self, v: crate::element::style::Align) -> Self {
                self.0.style.align_items = Some(v);
                self
            }
            pub fn justify_content(mut self, v: crate::element::style::Justify) -> Self {
                self.0.style.justify_content = Some(v);
                self
            }
            pub fn overflow(mut self, v: Overflow) -> Self {
                self.0.style.overflow = v;
                self
            }
            /// Paint-only z-order among siblings (`0` keeps normal traversal order).
            ///
            /// Non-zero values are deferred to a sorted paint pass (ascending `z_index`) and do
            /// not affect layout.
            pub fn z_index(mut self, z: i32) -> Self {
                self.0.style.z_index = z;
                self
            }
            /// Solid fill painted inside this container’s rounded bounds.
            ///
            /// Calling this clears any previously configured [`linear_gradient`](Self::linear_gradient).
            pub fn background(mut self, c: impl Into<ColorSource>) -> Self {
                self.0.paint.background = Some(c.into());
                self.0.paint.background_gradient = None;
                self
            }
            /// Two-stop linear gradient fill painted inside this container’s rounded bounds.
            pub fn linear_gradient(mut self, gradient: LinearGradient) -> Self {
                self.0.paint.background_gradient = Some(gradient);
                self.0.paint.background = None;
                self
            }
            /// Blurred outer shadow painted before the background and borders.
            pub fn box_shadow(mut self, shadow: BoxShadow) -> Self {
                self.0.paint.box_shadow = Some(shadow);
                self
            }
            /// Border color and uniform width on all sides, in logical points.
            pub fn border(mut self, color: Color, width: f32) -> Self {
                self.0.paint.border_color = Edges::all(Some(ColorSource::Static(color)));
                self.0.paint.border_width = Edges::all(width);
                self
            }
            /// Border on the top side only (sets color and width for that side).
            pub fn border_top(mut self, color: Color, width: f32) -> Self {
                self.0.paint.border_color.top = Some(ColorSource::Static(color));
                self.0.paint.border_width.top = width;
                self
            }
            /// Border on the right side only.
            pub fn border_right(mut self, color: Color, width: f32) -> Self {
                self.0.paint.border_color.right = Some(ColorSource::Static(color));
                self.0.paint.border_width.right = width;
                self
            }
            /// Border on the bottom side only.
            pub fn border_bottom(mut self, color: Color, width: f32) -> Self {
                self.0.paint.border_color.bottom = Some(ColorSource::Static(color));
                self.0.paint.border_width.bottom = width;
                self
            }
            /// Border on the left side only.
            pub fn border_left(mut self, color: Color, width: f32) -> Self {
                self.0.paint.border_color.left = Some(ColorSource::Static(color));
                self.0.paint.border_width.left = width;
                self
            }
            /// Border on the left and right sides.
            pub fn border_x(mut self, color: Color, width: f32) -> Self {
                self.0.paint.border_color.left = Some(ColorSource::Static(color));
                self.0.paint.border_color.right = Some(ColorSource::Static(color));
                self.0.paint.border_width.left = width;
                self.0.paint.border_width.right = width;
                self
            }
            /// Border on the top and bottom sides.
            pub fn border_y(mut self, color: Color, width: f32) -> Self {
                self.0.paint.border_color.top = Some(ColorSource::Static(color));
                self.0.paint.border_color.bottom = Some(ColorSource::Static(color));
                self.0.paint.border_width.top = width;
                self.0.paint.border_width.bottom = width;
                self
            }
            /// Uniform border color on all sides, preserving existing widths.
            pub fn border_color(mut self, color: Color) -> Self {
                self.0.paint.border_color = Edges::all(Some(ColorSource::Static(color)));
                self
            }
            /// Top border color only.
            pub fn border_color_top(mut self, color: Color) -> Self {
                self.0.paint.border_color.top = Some(ColorSource::Static(color));
                self
            }
            /// Right border color only.
            pub fn border_color_right(mut self, color: Color) -> Self {
                self.0.paint.border_color.right = Some(ColorSource::Static(color));
                self
            }
            /// Bottom border color only.
            pub fn border_color_bottom(mut self, color: Color) -> Self {
                self.0.paint.border_color.bottom = Some(ColorSource::Static(color));
                self
            }
            /// Left border color only.
            pub fn border_color_left(mut self, color: Color) -> Self {
                self.0.paint.border_color.left = Some(ColorSource::Static(color));
                self
            }
            /// Top border width only (other sides unchanged; set [`border`](Self::border) color first).
            pub fn border_width_top(mut self, width: f32) -> Self {
                self.0.paint.border_width.top = width;
                self
            }
            /// Right border width only.
            pub fn border_width_right(mut self, width: f32) -> Self {
                self.0.paint.border_width.right = width;
                self
            }
            /// Bottom border width only.
            pub fn border_width_bottom(mut self, width: f32) -> Self {
                self.0.paint.border_width.bottom = width;
                self
            }
            /// Left border width only.
            pub fn border_width_left(mut self, width: f32) -> Self {
                self.0.paint.border_width.left = width;
                self
            }
            /// Uniform corner radius on all four corners, in logical points.
            pub fn radius(mut self, r: f32) -> Self {
                self.0.paint.radius = CornerRadii::all(r);
                self
            }
            /// Top-left corner radius only.
            pub fn radius_top_left(mut self, r: f32) -> Self {
                self.0.paint.radius.top_left = r;
                self
            }
            /// Top-right corner radius only.
            pub fn radius_top_right(mut self, r: f32) -> Self {
                self.0.paint.radius.top_right = r;
                self
            }
            /// Bottom-right corner radius only.
            pub fn radius_bottom_right(mut self, r: f32) -> Self {
                self.0.paint.radius.bottom_right = r;
                self
            }
            /// Bottom-left corner radius only.
            pub fn radius_bottom_left(mut self, r: f32) -> Self {
                self.0.paint.radius.bottom_left = r;
                self
            }
            /// Radius on both top corners (left and right).
            pub fn radius_top(mut self, r: f32) -> Self {
                self.0.paint.radius.top_left = r;
                self.0.paint.radius.top_right = r;
                self
            }
            /// Radius on both bottom corners.
            pub fn radius_bottom(mut self, r: f32) -> Self {
                self.0.paint.radius.bottom_left = r;
                self.0.paint.radius.bottom_right = r;
                self
            }
            /// Radius on both left corners (top and bottom).
            pub fn radius_left(mut self, r: f32) -> Self {
                self.0.paint.radius.top_left = r;
                self.0.paint.radius.bottom_left = r;
                self
            }
            /// Radius on both right corners (top and bottom).
            pub fn radius_right(mut self, r: f32) -> Self {
                self.0.paint.radius.top_right = r;
                self.0.paint.radius.bottom_right = r;
                self
            }
            /// Sets an image to draw inside this container using object-fit: contain scaling.
            ///
            /// The image is scaled uniformly to fit within the container's layout rectangle while
            /// preserving its aspect ratio, then centered. This is equivalent to CSS
            /// `object-fit: contain`.
            ///
            /// ```no_run
            /// use lemon::{Column, ImageHandle};
            ///
            /// # fn example(handle: ImageHandle) {
            /// Column::new()
            ///     .width(200.0)
            ///     .height(150.0)
            ///     .image(handle)
            ///     .into_element();
            /// # }
            /// ```
            pub fn image(mut self, handle: crate::asset::ImageHandle) -> Self {
                self.0.paint.image = Some(handle);
                self
            }
            /// Appends a child widget; call repeatedly to build a list of children.
            pub fn child(mut self, el: impl Into<Element>) -> Self {
                self.0.children.push(el.into());
                self
            }
            /// Appends multiple children in order.
            ///
            /// For a list of mixed widget types, use the [`children!`](crate::children) macro:
            /// `.children(children![Text::new("a"), Button::new("b")])`.
            pub fn children(mut self, items: impl IntoIterator<Item = impl Into<Element>>) -> Self {
                self.0.children.extend(items.into_iter().map(Into::into));
                self
            }
            /// Stable identity for diffing when this child is inserted, removed, or reordered.
            ///
            /// All siblings under the same parent must use keys if any sibling uses one.
            pub fn key(mut self, key: u64) -> Self {
                self.0.key = Some(Key(key));
                self
            }
            /// Mouse click handler (logical coordinates, hit-tested against this node’s bounds).
            pub fn on_click(mut self, f: impl Fn() + 'static) -> Self {
                self.0.handlers.on_click = Some(Rc::new(f));
                self
            }
            pub fn on_key_down(
                mut self,
                f: impl Fn(crate::element::events::KeyEvent) + 'static,
            ) -> Self {
                self.0.handlers.on_key_down = Some(Rc::new(f));
                self
            }
            pub fn on_key_up(
                mut self,
                f: impl Fn(crate::element::events::KeyEvent) + 'static,
            ) -> Self {
                self.0.handlers.on_key_up = Some(Rc::new(f));
                self
            }
            pub fn on_hover_enter(mut self, f: impl Fn() + 'static) -> Self {
                self.0.handlers.on_hover_enter = Some(Rc::new(f));
                self
            }
            pub fn on_hover_leave(mut self, f: impl Fn() + 'static) -> Self {
                self.0.handlers.on_hover_leave = Some(Rc::new(f));
                self
            }
            /// Mouse wheel handler; `delta` is the vertical scroll amount in logical pixels.
            ///
            /// Used by scrollable regions (see [`Scroll`](crate::widget::Scroll)). The platform
            /// dispatches wheel events to the deepest node under the cursor with this handler.
            pub fn on_scroll(mut self, f: impl Fn(f64) + 'static) -> Self {
                self.0.handlers.on_scroll = Some(Rc::new(f));
                self
            }
            /// Called when a click lands outside this node's layout bounds while it is mounted.
            pub fn on_click_outside(mut self, f: impl Fn() + 'static) -> Self {
                self.0.handlers.on_click_outside = Some(Rc::new(f));
                self
            }
            /// Pointer press handler with normalized coordinates in this node's bounds (`0.0..=1.0`).
            ///
            /// ```no_run
            /// # use lemon::{View, Color};
            /// View::new()
            ///     .width(200.0)
            ///     .height(100.0)
            ///     .on_pointer_down(|x, y| {
            ///         let local_px_x = x * 200.0;
            ///         let local_px_y = y * 100.0;
            ///         let _ = (local_px_x, local_px_y, Color::rgb8(255, 0, 0));
            ///     })
            ///     .into_element();
            /// ```
            pub fn on_pointer_down(mut self, f: impl Fn(f32, f32) + 'static) -> Self {
                self.0.handlers.on_pointer_down = Some(Rc::new(f));
                self
            }
            /// Pointer move handler with normalized coordinates in this node's bounds (`0.0..=1.0`).
            pub fn on_pointer_move(mut self, f: impl Fn(f32, f32) + 'static) -> Self {
                self.0.handlers.on_pointer_move = Some(Rc::new(f));
                self
            }
            /// Pointer release handler with normalized coordinates in this node's bounds (`0.0..=1.0`).
            pub fn on_pointer_up(mut self, f: impl Fn(f32, f32) + 'static) -> Self {
                self.0.handlers.on_pointer_up = Some(Rc::new(f));
                self
            }
            /// Binds a cell updated after layout for scroll clamping (used by `Scroll` widget).
            pub fn scroll_layout_max(mut self, cell: std::rc::Rc<std::cell::Cell<f64>>) -> Self {
                self.0.handlers.scroll_layout_max = Some(cell);
                self
            }
            /// Uniform margin on all sides, in logical points.
            pub fn margin(mut self, v: f32) -> Self {
                self.0.style.margin = Some(Edges::all(v));
                self
            }
            /// Margin on the left and right sides, in logical points.
            pub fn margin_x(mut self, v: f32) -> Self {
                let mut edges = self.0.style.margin.take().unwrap_or_default();
                edges.left = v;
                edges.right = v;
                self.0.style.margin = Some(edges);
                self
            }
            /// Margin on the top and bottom sides, in logical points.
            pub fn margin_y(mut self, v: f32) -> Self {
                let mut edges = self.0.style.margin.take().unwrap_or_default();
                edges.top = v;
                edges.bottom = v;
                self.0.style.margin = Some(edges);
                self
            }
            /// Sets top margin in logical points (other sides unchanged).
            ///
            /// Negative values shift content up without changing layout size — useful for
            /// scroll offsets on inner content inside a clipped viewport.
            pub fn margin_top(mut self, v: f32) -> Self {
                let mut edges = self.0.style.margin.take().unwrap_or_default();
                edges.top = v;
                self.0.style.margin = Some(edges);
                self
            }
            /// Sets right margin in logical points (other sides unchanged).
            pub fn margin_right(mut self, v: f32) -> Self {
                let mut edges = self.0.style.margin.take().unwrap_or_default();
                edges.right = v;
                self.0.style.margin = Some(edges);
                self
            }
            /// Sets bottom margin in logical points (other sides unchanged).
            pub fn margin_bottom(mut self, v: f32) -> Self {
                let mut edges = self.0.style.margin.take().unwrap_or_default();
                edges.bottom = v;
                self.0.style.margin = Some(edges);
                self
            }
            /// Sets left margin in logical points (other sides unchanged).
            pub fn margin_left(mut self, v: f32) -> Self {
                let mut edges = self.0.style.margin.take().unwrap_or_default();
                edges.left = v;
                self.0.style.margin = Some(edges);
                self
            }
            /// Sets the top inset in logical points.
            ///
            /// Use with [`.absolute()`](Self::absolute) to position an overlay relative to its
            /// containing flex ancestor without taking up normal flex space.
            pub fn top(mut self, v: f32) -> Self {
                let mut edges = self.0.style.inset.take().unwrap_or_default();
                edges.top = v;
                self.0.style.inset = Some(edges);
                self
            }
            /// Sets the left inset in logical points.
            ///
            /// Use with [`.absolute()`](Self::absolute) to pin an overlay's horizontal position
            /// relative to its containing flex ancestor.
            pub fn left(mut self, v: f32) -> Self {
                let mut edges = self.0.style.inset.take().unwrap_or_default();
                edges.left = v;
                self.0.style.inset = Some(edges);
                self
            }
            /// Includes this node in keyboard focus traversal (Tab / Shift+Tab).
            pub fn focusable(mut self) -> Self {
                self.0.style.focusable = true;
                self
            }
            /// Attaches text-field metadata used by the paint pass (caret, focus ring).
            pub fn text_input(mut self, meta: crate::element::types::TextInputMeta) -> Self {
                self.0.text_input = Some(meta);
                self
            }
            /// Marks this node as a vertical scroll viewport (scrollbar when content overflows).
            pub fn scroll_viewport(mut self) -> Self {
                self.0.scroll_viewport = true;
                self
            }
            /// Paints a widget-style scrollbar track and thumb from layout measurements.
            pub fn scroll_bar(mut self) -> Self {
                self.0.scroll_bar = true;
                self
            }
            /// Removes this node from normal flex flow.
            ///
            /// The node is positioned by Taffy's absolute-position algorithm relative to its
            /// nearest flex ancestor. Pair this with inset builders such as [`.top()`](Self::top)
            /// and [`.left()`](Self::left) when the absolute node must anchor to a specific edge.
            pub fn absolute(mut self) -> Self {
                self.0.style.position_absolute = true;
                self
            }
            /// Cursor shown when the pointer is over this node.
            pub fn cursor(mut self, c: crate::element::events::Cursor) -> Self {
                self.0.style.cursor = c;
                self
            }
            /// Finishes the builder and returns an [`Element`] for use in [`.child`](Self::child) or the root view.
            pub fn into_element(self) -> Element {
                Element::$variant(self.0)
            }
        }

        impl Default for $name {
            fn default() -> Self {
                $name::new()
            }
        }
        impl From<$name> for Element {
            fn from(b: $name) -> Self {
                b.into_element()
            }
        }
    };
}

container_builder!(
    Column,
    Column,
    "Vertical flex container: children stack from top to bottom."
);
container_builder!(
    Row,
    Row,
    "Horizontal flex container: children stack from left to right."
);
container_builder!(
    View,
    View,
    "Generic flex container; use when you do not need an explicit row or column axis."
);

// ── Text ──────────────────────────────────────────────────────────────────

/// Single-line or wrapped text label.
///
/// Pass a `&str` / `String` for static copy, or a `Fn() -> String` closure to re-read signals
/// on each render:
///
/// ```no_run
/// # use lemon::{Signal, element::builders::Text};
/// # let count = Signal::new(0);
/// # let c = count.clone();
/// Text::new(move || format!("{}", c.get()));
/// ```
pub struct Text {
    content: TextContent,
    style: TextStyle,
    key: Option<Key>,
}

impl Text {
    /// Creates text from static content or a reactive closure (see [`TextContent`]).
    ///
    /// Typography defaults (size/family/line-height/letter-spacing) are read from the active
    /// theme (`Cx::use_theme` / [`crate::theme::current_theme`]) at build time.
    pub fn new(content: impl Into<TextContent>) -> Self {
        Text {
            content: content.into(),
            style: TextStyle::default(),
            key: None,
        }
    }
    /// Stable key when this text node is a keyed sibling (see [`Column::key`]).
    pub fn key(mut self, key: u64) -> Self {
        self.key = Some(Key(key));
        self
    }
    /// Font size in logical points.
    pub fn font_size(mut self, size: f32) -> Self {
        self.style.font_size = size;
        self
    }
    /// Preferred font family list (for example `"Inter, system-ui"`).
    pub fn font_family(mut self, family: impl Into<String>) -> Self {
        self.style.font_family = family.into();
        self
    }
    /// Unitless line-height multiplier relative to font size.
    pub fn line_height(mut self, line_height: f32) -> Self {
        self.style.line_height = line_height;
        self
    }
    pub fn weight(mut self, w: u16) -> Self {
        self.style.font_weight = w;
        self
    }
    pub fn color(mut self, c: Color) -> Self {
        self.style.color = Some(c);
        self
    }
    /// Finishes the builder and returns an [`Element`].
    pub fn into_element(self) -> Element {
        Element::Text(TextElement {
            content: self.content,
            style: self.style,
            key: self.key,
        })
    }
}

impl From<Text> for Element {
    fn from(b: Text) -> Self {
        b.into_element()
    }
}

// ── Button ────────────────────────────────────────────────────────────────

/// Clickable control with a text label and default padding / background.
pub struct Button {
    label: TextContent,
    style: StyleProps,
    paint: PaintProps,
    on_click: Option<Rc<dyn Fn()>>,
}

impl Button {
    /// Creates a button; label accepts the same static or closure forms as [`Text::new`].
    pub fn new(label: impl Into<TextContent>) -> Self {
        Button {
            label: label.into(),
            style: StyleProps {
                padding: Some(Edges::all(10.0)),
                align_self: Some(crate::element::style::Align::Start),
                ..Default::default()
            },
            paint: PaintProps {
                background: Some(Color::rgb8(55, 120, 220).into()),
                radius: CornerRadii::all(6.0),
                ..Default::default()
            },
            on_click: None,
        }
    }
    /// Called when the button is clicked (after hit-testing).
    pub fn on_click(mut self, f: impl Fn() + 'static) -> Self {
        self.on_click = Some(Rc::new(f));
        self
    }
    /// Uniform padding on all sides, in logical points.
    pub fn padding(mut self, v: f32) -> Self {
        self.style.padding = Some(Edges::all(v));
        self
    }
    /// Solid fill behind the label ([`Color`] or reactive [`ColorSource`]).
    ///
    /// Calling this clears any previously configured [`linear_gradient`](Self::linear_gradient).
    pub fn background(mut self, c: impl Into<ColorSource>) -> Self {
        self.paint.background = Some(c.into());
        self.paint.background_gradient = None;
        self
    }
    /// Two-stop linear gradient fill painted behind the label.
    pub fn linear_gradient(mut self, gradient: LinearGradient) -> Self {
        self.paint.background_gradient = Some(gradient);
        self.paint.background = None;
        self
    }
    /// Blurred outer shadow painted before the button background.
    pub fn box_shadow(mut self, shadow: BoxShadow) -> Self {
        self.paint.box_shadow = Some(shadow);
        self
    }
    pub fn radius(mut self, r: f32) -> Self {
        self.paint.radius = CornerRadii::all(r);
        self
    }
    pub fn width(mut self, v: f32) -> Self {
        self.style.width = Some(crate::element::style::Dimension::Points(v));
        self
    }
    pub fn height(mut self, v: f32) -> Self {
        self.style.height = Some(crate::element::style::Dimension::Points(v));
        self
    }
    /// Finishes the builder and returns an [`Element`].
    pub fn into_element(self) -> Element {
        Element::Button(crate::element::types::ButtonElement {
            label: self.label,
            style: self.style,
            paint: self.paint,
            on_click: self.on_click,
            key: None,
        })
    }
}

impl From<Button> for Element {
    fn from(b: Button) -> Self {
        b.into_element()
    }
}

// ── Component ─────────────────────────────────────────────────────────────

/// Nested view with its own [`Cx`](crate::Cx) hook state (signals, effects).
///
/// `view` must be a **function pointer** (`fn(&Cx) -> Element`), not a closure that captures
/// environment data. Identity is the function address plus an optional [`.key`](Self::key).
///
/// For list rows that need per-item callbacks, build keyed [`Row`] / [`Column`] children instead
/// of capturing state inside [`Component::new`].
pub struct Component(ComponentElement);

impl Component {
    /// Wraps a sub-view function; hooks inside `view` persist across parent re-renders.
    pub fn new(view: ComponentFn) -> Self {
        Self(ComponentElement::from_component_fn(view))
    }

    /// Wraps a typed sub-view function and props; hooks inside `view` persist across re-renders.
    ///
    /// `view` must be a function pointer. Use this when the component needs strongly typed,
    /// comparable props.
    ///
    /// ```
    /// use lemon::prelude::*;
    ///
    /// #[derive(Clone, PartialEq)]
    /// struct Props {
    ///     label: &'static str,
    /// }
    ///
    /// fn child(_cx: &Cx, props: &Props) -> Element {
    ///     Text::new(props.label).into_element()
    /// }
    ///
    /// let _ = Component::new_with_props(child, Props { label: "hello" });
    /// ```
    pub fn new_with_props<P: Clone + PartialEq + 'static>(
        view: fn(&crate::runtime::cx::Cx, &P) -> Element,
        props: P,
    ) -> Self {
        Self(ComponentElement::from_component_fn_with_props(view, props))
    }

    /// Stable key when this component is one of several keyed siblings.
    pub fn key(mut self, key: u64) -> Self {
        self.0 = self.0.with_key(Key(key));
        self
    }

    /// Finishes the builder and returns an [`Element`].
    pub fn into_element(self) -> Element {
        Element::Component(self.0)
    }
}

impl From<Component> for Element {
    fn from(component: Component) -> Self {
        component.into_element()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::element::types::Key;
    use crate::theme::{current_theme, set_active_theme, Theme};
    use std::cell::Cell;
    use std::rc::Rc;

    #[test]
    fn column_builder_sets_gap() {
        let Element::Column(el) = Column::new().gap(8.0).into_element() else {
            panic!()
        };
        assert_eq!(el.style.gap, Some(8.0));
    }

    #[test]
    fn column_builder_sets_row_gap_and_column_gap() {
        let Element::Column(el) = Column::new().row_gap(6.0).column_gap(3.0).into_element() else {
            panic!()
        };
        assert_eq!(el.style.row_gap, Some(6.0));
        assert_eq!(el.style.column_gap, Some(3.0));
        assert_eq!(el.style.gap, None);
    }

    #[test]
    fn column_builder_sets_padding_per_side() {
        let Element::Column(el) = Column::new()
            .padding_top(1.0)
            .padding_right(2.0)
            .padding_bottom(3.0)
            .padding_left(4.0)
            .into_element()
        else {
            panic!()
        };
        let p = el.style.padding.as_ref().expect("padding");
        assert_eq!(p.top, 1.0);
        assert_eq!(p.right, 2.0);
        assert_eq!(p.bottom, 3.0);
        assert_eq!(p.left, 4.0);
    }

    #[test]
    fn column_builder_padding_x_and_y() {
        let Element::Column(el) = Column::new().padding_x(8.0).padding_y(4.0).into_element() else {
            panic!()
        };
        let p = el.style.padding.as_ref().expect("padding");
        assert_eq!(p.left, 8.0);
        assert_eq!(p.right, 8.0);
        assert_eq!(p.top, 4.0);
        assert_eq!(p.bottom, 4.0);
    }

    #[test]
    fn column_builder_sets_margin_uniform() {
        let Element::Column(el) = Column::new().margin(10.0).into_element() else {
            panic!()
        };
        let m = el.style.margin.as_ref().expect("margin");
        assert_eq!(m.top, 10.0);
        assert_eq!(m.right, 10.0);
        assert_eq!(m.bottom, 10.0);
        assert_eq!(m.left, 10.0);
    }

    #[test]
    fn column_builder_sets_margin_per_side() {
        let Element::Column(el) = Column::new()
            .margin_top(1.0)
            .margin_right(2.0)
            .margin_bottom(3.0)
            .margin_left(4.0)
            .into_element()
        else {
            panic!()
        };
        let m = el.style.margin.as_ref().expect("margin");
        assert_eq!(m.top, 1.0);
        assert_eq!(m.right, 2.0);
        assert_eq!(m.bottom, 3.0);
        assert_eq!(m.left, 4.0);
    }

    #[test]
    fn column_builder_margin_x_and_y() {
        let Element::Column(el) = Column::new().margin_x(6.0).margin_y(2.0).into_element() else {
            panic!()
        };
        let m = el.style.margin.as_ref().expect("margin");
        assert_eq!(m.left, 6.0);
        assert_eq!(m.right, 6.0);
        assert_eq!(m.top, 2.0);
        assert_eq!(m.bottom, 2.0);
    }

    #[test]
    fn margin_top_does_not_overwrite_other_sides() {
        let Element::Column(el) = Column::new().margin(5.0).margin_top(20.0).into_element() else {
            panic!()
        };
        let m = el.style.margin.as_ref().expect("margin");
        assert_eq!(m.top, 20.0);
        assert_eq!(m.right, 5.0);
        assert_eq!(m.bottom, 5.0);
        assert_eq!(m.left, 5.0);
    }

    #[test]
    fn border_top_does_not_overwrite_other_sides() {
        let Element::Column(el) = Column::new()
            .border(Color::rgb8(1, 2, 3), 5.0)
            .border_top(Color::rgb8(1, 2, 3), 2.0)
            .into_element()
        else {
            panic!()
        };
        let b = el.paint.border_width;
        assert_eq!(b.top, 2.0);
        assert_eq!(b.right, 5.0);
        assert_eq!(b.bottom, 5.0);
        assert_eq!(b.left, 5.0);
    }

    #[test]
    fn border_color_top_does_not_overwrite_other_sides() {
        let Element::Column(el) = Column::new()
            .border(Color::rgb8(1, 2, 3), 5.0)
            .border_color_top(Color::rgb8(9, 8, 7))
            .into_element()
        else {
            panic!()
        };
        let colors = el.paint.resolve().border_color;
        assert_eq!(colors.top, Some(Color::rgb8(9, 8, 7)));
        assert_eq!(colors.right, Some(Color::rgb8(1, 2, 3)));
        assert_eq!(colors.bottom, Some(Color::rgb8(1, 2, 3)));
        assert_eq!(colors.left, Some(Color::rgb8(1, 2, 3)));
    }

    #[test]
    fn linear_gradient_replaces_solid_background() {
        let Element::View(el) = View::new()
            .background(Color::rgb8(1, 2, 3))
            .linear_gradient(LinearGradient::new(
                (0.0, 0.0),
                (1.0, 1.0),
                Color::rgb8(10, 20, 30),
                Color::rgb8(40, 50, 60),
            ))
            .into_element()
        else {
            panic!()
        };
        assert!(el.paint.background.is_none());
        assert_eq!(
            el.paint.background_gradient,
            Some(LinearGradient::new(
                (0.0, 0.0),
                (1.0, 1.0),
                Color::rgb8(10, 20, 30),
                Color::rgb8(40, 50, 60),
            ))
        );
    }

    #[test]
    fn box_builder_sets_box_shadow() {
        let shadow = BoxShadow::new(Color::rgb8(0, 0, 0).with_alpha(0.25), 0.0, 6.0, 12.0);
        let Element::View(el) = View::new().box_shadow(shadow).into_element() else {
            panic!()
        };
        assert_eq!(el.paint.box_shadow, Some(shadow));
    }

    #[test]
    fn radius_top_sets_both_top_corners() {
        let Element::Column(el) = Column::new().radius(4.0).radius_top(12.0).into_element() else {
            panic!()
        };
        let r = el.paint.radius;
        assert_eq!(r.top_left, 12.0);
        assert_eq!(r.top_right, 12.0);
        assert_eq!(r.bottom_right, 4.0);
        assert_eq!(r.bottom_left, 4.0);
    }

    #[test]
    fn row_with_children() {
        let Element::Row(el) = Row::new()
            .child(Text::new("a"))
            .child(Text::new("b"))
            .into_element()
        else {
            panic!()
        };
        assert_eq!(el.children.len(), 2);
    }

    #[test]
    fn children_macro_accepts_mixed_builders() {
        use super::Button;
        use crate::{children, Column, Row, Text};

        let Element::Column(col) = Column::new()
            .children(children![
                Text::new("title"),
                Button::new("ok"),
                Row::new().child(Text::new("nested")),
            ])
            .into_element()
        else {
            panic!("expected Column");
        };
        assert_eq!(col.children.len(), 3);
    }

    #[test]
    fn box_builder_sets_key() {
        let Element::View(el) = View::new().key(9).into_element() else {
            panic!()
        };
        assert_eq!(el.key, Some(Key(9)));
    }

    #[test]
    fn box_builder_sets_overflow() {
        let Element::View(el) = View::new().overflow(Overflow::Hidden).into_element() else {
            panic!()
        };
        assert_eq!(el.style.overflow, Overflow::Hidden);
    }

    #[test]
    fn box_builder_sets_z_index() {
        let Element::View(el) = View::new().z_index(3).into_element() else {
            panic!()
        };
        assert_eq!(el.style.z_index, 3);
    }

    #[test]
    fn box_builder_clamps_opacity_to_valid_range() {
        let Element::View(el) = View::new().opacity(1.5).into_element() else {
            panic!()
        };
        assert_eq!(el.style.opacity, 1.0);

        let Element::View(el) = View::new().opacity(-0.5).into_element() else {
            panic!()
        };
        assert_eq!(el.style.opacity, 0.0);

        let Element::View(el) = View::new().opacity(f32::NAN).into_element() else {
            panic!()
        };
        assert_eq!(el.style.opacity, 1.0);
    }

    #[test]
    fn box_builder_sets_absolute_insets() {
        let Element::View(el) = View::new().top(12.0).left(8.0).into_element() else {
            panic!()
        };
        let inset = el.style.inset.as_ref().expect("expected inset");
        assert_eq!(inset.top, 12.0);
        assert_eq!(inset.left, 8.0);
    }

    #[test]
    fn box_builder_sets_focusable_cursor_and_handlers() {
        let Element::View(el) = View::new()
            .focusable()
            .cursor(crate::element::events::Cursor::Pointer)
            .on_click(|| {})
            .on_hover_enter(|| {})
            .on_hover_leave(|| {})
            .on_key_down(|_| {})
            .on_key_up(|_| {})
            .into_element()
        else {
            panic!()
        };
        assert!(el.style.focusable);
        assert_eq!(el.style.cursor, crate::element::events::Cursor::Pointer);
        assert!(el.handlers.on_click.is_some());
        assert!(el.handlers.on_hover_enter.is_some());
        assert!(el.handlers.on_hover_leave.is_some());
        assert!(el.handlers.on_key_down.is_some());
        assert!(el.handlers.on_key_up.is_some());
    }

    #[test]
    fn text_static_content() {
        let Element::Text(el) = Text::new("hello").into_element() else {
            panic!()
        };
        assert_eq!(el.content.resolve(), "hello");
    }

    #[test]
    fn text_dynamic_content() {
        let value = Rc::new(Cell::new(7u32));
        let v = value.clone();
        let Element::Text(el) = Text::new(move || v.get().to_string()).into_element() else {
            panic!()
        };
        assert_eq!(el.content.resolve(), "7");
        value.set(42);
        assert_eq!(el.content.resolve(), "42");
    }

    #[test]
    fn text_defaults_follow_active_theme_typography() {
        let previous = current_theme();
        let mut custom = Theme::default_dark();
        custom.typography.font_size_md = 19.0;
        custom.typography.font_family = "serif".to_string();
        custom.typography.line_height = 1.9;
        custom.typography.letter_spacing = 0.3;
        set_active_theme(custom.clone());

        let Element::Text(el) = Text::new("hello").into_element() else {
            panic!()
        };

        assert_eq!(el.style.font_size, custom.typography.font_size_md);
        assert_eq!(el.style.font_family, custom.typography.font_family);
        assert_eq!(el.style.line_height, custom.typography.line_height);
        assert_eq!(el.style.letter_spacing, custom.typography.letter_spacing);

        set_active_theme(previous);
    }

    #[test]
    fn text_builder_sets_font_family_and_line_height() {
        let Element::Text(el) = Text::new("hello")
            .font_family("Inter, system-ui")
            .line_height(1.2)
            .into_element()
        else {
            panic!()
        };

        assert_eq!(el.style.font_family, "Inter, system-ui");
        assert_eq!(el.style.line_height, 1.2);
    }

    #[test]
    fn button_on_click_fires() {
        let fired = Rc::new(Cell::new(false));
        let f = fired.clone();
        let Element::Button(el) = Button::new("OK")
            .on_click(move || f.set(true))
            .into_element()
        else {
            panic!()
        };
        el.on_click.unwrap()();
        assert!(fired.get());
    }

    #[test]
    fn button_builder_sets_padding() {
        let Element::Button(el) = Button::new("OK").padding(12.0).into_element() else {
            panic!()
        };
        assert_eq!(el.style.padding, Some(Edges::all(12.0)));
    }

    #[test]
    fn component_builder_tracks_function_identity_and_key() {
        fn first(_cx: &crate::runtime::cx::Cx) -> Element {
            Text::new("first").into_element()
        }

        fn second(_cx: &crate::runtime::cx::Cx) -> Element {
            Text::new("second").into_element()
        }

        let Element::Component(first_with_key) = Component::new(first).key(7).into_element() else {
            panic!("expected component element");
        };
        let Element::Component(first_again) = Component::new(first).into_element() else {
            panic!("expected component element");
        };
        let Element::Component(second_component) = Component::new(second).into_element() else {
            panic!("expected component element");
        };

        assert_eq!(first_with_key.key(), Some(&Key(7)));
        assert_eq!(first_with_key.identity(), first_again.identity());
        assert_ne!(first_with_key.identity(), second_component.identity());
    }
}
