use std::hash::{Hash, Hasher};

use crate::scene::{Color, Point};
use crate::text::{TextSpan, TextStyle, TextWrap};
use crate::{ImageFit, ImageResource};

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ElementId(u64);

impl ElementId {
    pub const fn new(raw: u64) -> Self {
        Self(raw)
    }

    pub const fn raw(self) -> u64 {
        self.0
    }
}

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

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum AlignItems {
    #[default]
    Stretch,
    Start,
    Center,
    End,
}

/// Controls how children are positioned and how leftover space is distributed
/// along the main axis (horizontal for `FlexDirection::Row`, vertical for
/// `FlexDirection::Column`).
///
/// When a container has flex children (`flex_grow > 0`) all remaining space is
/// consumed by them, so `justify_content` has no visible effect on the main
/// axis distribution in that case. The container's `gap` is always honored as
/// a minimum between adjacent children — `SpaceBetween`, `SpaceAround`, and
/// `SpaceEvenly` distribute leftover space *in addition to* `gap`.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum JustifyContent {
    /// Pack children at the start of the main axis. This is the default and
    /// matches the behavior before `justify_content` existed.
    #[default]
    Start,
    /// Pack children at the end of the main axis.
    End,
    /// Pack children as a group, centered along the main axis.
    Center,
    /// Distribute children with the first child flush with the start, the last
    /// child flush with the end, and equal extra space between adjacent pairs.
    SpaceBetween,
    /// Distribute children with equal extra space around each child, so the
    /// space at the ends is half of the space between adjacent children.
    SpaceAround,
    /// Distribute children so the space between adjacent children and between
    /// the children and both edges of the container is identical.
    SpaceEvenly,
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum CursorIcon {
    Default,
    Pointer,
    Text,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Edges {
    pub top: f32,
    pub right: f32,
    pub bottom: f32,
    pub left: f32,
}

impl Edges {
    pub const ZERO: Self = Self {
        top: 0.0,
        right: 0.0,
        bottom: 0.0,
        left: 0.0,
    };

    pub const fn all(value: f32) -> Self {
        Self {
            top: value,
            right: value,
            bottom: value,
            left: value,
        }
    }

    pub const fn symmetric(horizontal: f32, vertical: f32) -> Self {
        Self {
            top: vertical,
            right: horizontal,
            bottom: vertical,
            left: horizontal,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Border {
    pub width: f32,
    pub color: Color,
}

impl Border {
    pub const fn new(width: f32, color: Color) -> Self {
        Self { width, color }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CornerRadius {
    pub top_left: f32,
    pub top_right: f32,
    pub bottom_right: f32,
    pub bottom_left: f32,
}

impl CornerRadius {
    pub const ZERO: Self = Self {
        top_left: 0.0,
        top_right: 0.0,
        bottom_right: 0.0,
        bottom_left: 0.0,
    };

    pub const fn all(radius: f32) -> Self {
        Self {
            top_left: radius,
            top_right: radius,
            bottom_right: radius,
            bottom_left: radius,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BoxShadowKind {
    Outer,
    Inner,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BoxShadow {
    pub offset: Point,
    pub blur: f32,
    pub spread: f32,
    pub color: Color,
    pub kind: BoxShadowKind,
}

impl BoxShadow {
    pub const fn new(
        offset: Point,
        blur: f32,
        spread: f32,
        color: Color,
        kind: BoxShadowKind,
    ) -> Self {
        Self {
            offset,
            blur,
            spread,
            color,
            kind,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScrollbarStyle {
    pub gutter_width: f32,
    pub track_color: Color,
    pub thumb_color: Color,
    pub corner_radius: f32,
    pub min_thumb_size: f32,
}

impl ScrollbarStyle {
    pub const fn new(gutter_width: f32, track_color: Color, thumb_color: Color) -> Self {
        Self {
            gutter_width,
            track_color,
            thumb_color,
            corner_radius: 999.0,
            min_thumb_size: 28.0,
        }
    }

    pub const fn with_corner_radius(mut self, corner_radius: f32) -> Self {
        self.corner_radius = corner_radius;
        self
    }

    pub const fn with_min_thumb_size(mut self, min_thumb_size: f32) -> Self {
        self.min_thumb_size = min_thumb_size;
        self
    }
}

impl Default for ScrollbarStyle {
    fn default() -> Self {
        Self::new(
            14.0,
            Color::rgba(0xFF, 0xFF, 0xFF, 0x12),
            Color::rgba(0xD8, 0xDE, 0xEA, 0x88),
        )
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct Style {
    pub direction: FlexDirection,
    pub align_items: AlignItems,
    /// Per-element override of the parent container's `align_items`. `None` means inherit.
    pub align_self: Option<AlignItems>,
    pub justify_content: JustifyContent,
    pub width: Option<f32>,
    pub height: Option<f32>,
    pub min_width: Option<f32>,
    pub min_height: Option<f32>,
    pub flex_grow: f32,
    pub gap: f32,
    pub padding: Edges,
    pub background: Option<Color>,
    pub border: Option<Border>,
    pub corner_radius: CornerRadius,
    pub box_shadows: Vec<BoxShadow>,
    pub pointer_events: bool,
    pub focusable: bool,
    pub cursor: Option<CursorIcon>,
}

impl Default for Style {
    fn default() -> Self {
        Self {
            direction: FlexDirection::Column,
            align_items: AlignItems::Stretch,
            align_self: None,
            justify_content: JustifyContent::Start,
            width: None,
            height: None,
            min_width: None,
            min_height: None,
            flex_grow: 0.0,
            gap: 0.0,
            padding: Edges::ZERO,
            background: None,
            border: None,
            corner_radius: CornerRadius::ZERO,
            box_shadows: Vec::new(),
            pointer_events: true,
            focusable: false,
            cursor: None,
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
enum ElementContent {
    Container,
    ScrollBox(ScrollBoxNode),
    Image(ImageNode),
    Text(TextNode),
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct TextNode {
    pub spans: Vec<TextSpan>,
    pub style: TextStyle,
    content_hash: u64,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct ImageNode {
    pub resource: ImageResource,
    pub fit: ImageFit,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct ScrollBoxNode {
    pub offset_y: f32,
    pub scrollbar: ScrollbarStyle,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Element {
    pub id: Option<ElementId>,
    pub style: Style,
    pub children: Vec<Element>,
    content: ElementContent,
}

impl Element {
    pub fn new() -> Self {
        Self {
            id: None,
            style: Style::default(),
            children: Vec::new(),
            content: ElementContent::Container,
        }
    }

    pub fn text(text: impl Into<String>, style: TextStyle) -> Self {
        Self::spans([TextSpan::new(text)], style)
    }

    pub fn spans(spans: impl IntoIterator<Item = TextSpan>, style: TextStyle) -> Self {
        let spans = spans.into_iter().collect::<Vec<_>>();
        Self {
            id: None,
            style: Style::default(),
            children: Vec::new(),
            content: ElementContent::Text(TextNode {
                content_hash: text_node_content_hash(&spans, &style),
                spans,
                style,
            }),
        }
    }

    pub fn paragraph(text: impl Into<String>, style: TextStyle) -> Self {
        Self::text(text, style.with_wrap(TextWrap::Word))
    }

    pub fn image(resource: ImageResource) -> Self {
        Self {
            id: None,
            style: Style::default(),
            children: Vec::new(),
            content: ElementContent::Image(ImageNode {
                resource,
                fit: ImageFit::Contain,
            }),
        }
    }

    pub fn scroll_box(offset_y: f32) -> Self {
        Self {
            id: None,
            style: Style {
                focusable: true,
                ..Style::default()
            },
            children: Vec::new(),
            content: ElementContent::ScrollBox(ScrollBoxNode {
                offset_y: offset_y.max(0.0),
                scrollbar: ScrollbarStyle::default(),
            }),
        }
    }

    pub fn rich_paragraph(spans: impl IntoIterator<Item = TextSpan>, style: TextStyle) -> Self {
        Self::spans(spans, style.with_wrap(TextWrap::Word))
    }

    pub fn row() -> Self {
        Self::new().direction(FlexDirection::Row)
    }

    pub fn column() -> Self {
        Self::new().direction(FlexDirection::Column)
    }

    pub fn direction(mut self, direction: FlexDirection) -> Self {
        self.style.direction = direction;
        self
    }

    pub fn width(mut self, width: f32) -> Self {
        self.style.width = Some(width);
        self
    }

    pub fn height(mut self, height: f32) -> Self {
        self.style.height = Some(height);
        self
    }

    pub fn min_width(mut self, min_width: f32) -> Self {
        self.style.min_width = Some(min_width.max(0.0));
        self
    }

    pub fn min_height(mut self, min_height: f32) -> Self {
        self.style.min_height = Some(min_height.max(0.0));
        self
    }

    pub fn min_size(self, min_width: f32, min_height: f32) -> Self {
        self.min_width(min_width).min_height(min_height)
    }

    pub fn flex(mut self, flex_grow: f32) -> Self {
        self.style.flex_grow = flex_grow.max(0.0);
        self
    }

    pub fn gap(mut self, gap: f32) -> Self {
        self.style.gap = gap.max(0.0);
        self
    }

    pub fn padding(mut self, padding: Edges) -> Self {
        self.style.padding = padding;
        self
    }

    pub fn background(mut self, color: Color) -> Self {
        self.style.background = Some(color);
        self
    }

    pub fn border(mut self, width: f32, color: Color) -> Self {
        self.style.border = Some(Border::new(width.max(0.0), color));
        self
    }

    pub fn corner_radius(mut self, radius: f32) -> Self {
        self.style.corner_radius = CornerRadius::all(radius.max(0.0));
        self
    }

    pub fn shadow(mut self, shadow: BoxShadow) -> Self {
        self.style.box_shadows.push(shadow);
        self
    }

    pub fn id(mut self, id: ElementId) -> Self {
        self.id = Some(id);
        self
    }

    pub fn pointer_events(mut self, pointer_events: bool) -> Self {
        self.style.pointer_events = pointer_events;
        self
    }

    pub fn focusable(mut self, focusable: bool) -> Self {
        self.style.focusable = focusable;
        self
    }

    pub fn align_items(mut self, align: AlignItems) -> Self {
        self.style.align_items = align;
        self
    }

    pub fn align_self(mut self, align: AlignItems) -> Self {
        self.style.align_self = Some(align);
        self
    }

    pub fn justify_content(mut self, justify: JustifyContent) -> Self {
        self.style.justify_content = justify;
        self
    }

    pub fn cursor(mut self, cursor: CursorIcon) -> Self {
        self.style.cursor = Some(cursor);
        self
    }

    pub fn image_fit(mut self, fit: ImageFit) -> Self {
        let ElementContent::Image(image) = &mut self.content else {
            panic!("only image elements can set image_fit");
        };
        image.fit = fit;
        self
    }

    pub fn scroll_offset(mut self, offset_y: f32) -> Self {
        let ElementContent::ScrollBox(scroll_box) = &mut self.content else {
            panic!("only scroll box elements can set scroll_offset");
        };
        scroll_box.offset_y = offset_y.max(0.0);
        self
    }

    pub fn scrollbar_style(mut self, scrollbar: ScrollbarStyle) -> Self {
        let ElementContent::ScrollBox(scroll_box) = &mut self.content else {
            panic!("only scroll box elements can set scrollbar_style");
        };
        scroll_box.scrollbar = scrollbar;
        self
    }

    pub fn child(mut self, child: Element) -> Self {
        self.assert_container();
        self.children.push(child);
        self
    }

    pub fn children(mut self, children: impl IntoIterator<Item = Element>) -> Self {
        self.assert_container();
        self.children.extend(children);
        self
    }

    pub(crate) fn text_node(&self) -> Option<&TextNode> {
        match &self.content {
            ElementContent::Text(text) => Some(text),
            ElementContent::Container | ElementContent::ScrollBox(_) | ElementContent::Image(_) => {
                None
            }
        }
    }

    pub(crate) fn image_node(&self) -> Option<&ImageNode> {
        match &self.content {
            ElementContent::Image(image) => Some(image),
            ElementContent::Container | ElementContent::ScrollBox(_) | ElementContent::Text(_) => {
                None
            }
        }
    }

    pub(crate) fn scroll_box_node(&self) -> Option<&ScrollBoxNode> {
        match &self.content {
            ElementContent::ScrollBox(scroll_box) => Some(scroll_box),
            ElementContent::Container | ElementContent::Image(_) | ElementContent::Text(_) => None,
        }
    }

    fn assert_container(&self) {
        assert!(
            matches!(
                self.content,
                ElementContent::Container | ElementContent::ScrollBox(_)
            ),
            "non-container elements cannot contain children"
        );
    }
}

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

// ---------------------------------------------------------------------------
// Hash implementations for f32-containing types.
// f32 fields use `.to_bits()` so NaN != NaN is avoided in hashing context
// (layout values are always finite so NaN is not expected).

impl Hash for FlexDirection {
    fn hash<H: Hasher>(&self, state: &mut H) {
        (*self as u8).hash(state);
    }
}

impl Hash for Edges {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.top.to_bits().hash(state);
        self.right.to_bits().hash(state);
        self.bottom.to_bits().hash(state);
        self.left.to_bits().hash(state);
    }
}

impl Hash for Border {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.width.to_bits().hash(state);
        self.color.hash(state);
    }
}

impl Hash for CornerRadius {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.top_left.to_bits().hash(state);
        self.top_right.to_bits().hash(state);
        self.bottom_right.to_bits().hash(state);
        self.bottom_left.to_bits().hash(state);
    }
}

impl Hash for BoxShadowKind {
    fn hash<H: Hasher>(&self, state: &mut H) {
        (*self as u8).hash(state);
    }
}

impl Hash for BoxShadow {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.offset.hash(state);
        self.blur.to_bits().hash(state);
        self.spread.to_bits().hash(state);
        self.color.hash(state);
        self.kind.hash(state);
    }
}

impl Hash for ScrollbarStyle {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.gutter_width.to_bits().hash(state);
        self.track_color.hash(state);
        self.thumb_color.hash(state);
        self.corner_radius.to_bits().hash(state);
        self.min_thumb_size.to_bits().hash(state);
    }
}

impl Hash for AlignItems {
    fn hash<H: Hasher>(&self, state: &mut H) {
        (*self as u8).hash(state);
    }
}

impl Hash for JustifyContent {
    fn hash<H: Hasher>(&self, state: &mut H) {
        (*self as u8).hash(state);
    }
}

impl Hash for Style {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.direction.hash(state);
        self.align_items.hash(state);
        self.align_self.hash(state);
        self.justify_content.hash(state);
        self.width.map(f32::to_bits).hash(state);
        self.height.map(f32::to_bits).hash(state);
        self.min_width.map(f32::to_bits).hash(state);
        self.min_height.map(f32::to_bits).hash(state);
        self.flex_grow.to_bits().hash(state);
        self.gap.to_bits().hash(state);
        self.padding.hash(state);
        self.background.hash(state);
        self.border.hash(state);
        self.corner_radius.hash(state);
        self.box_shadows.hash(state);
        self.pointer_events.hash(state);
        self.focusable.hash(state);
        self.cursor.hash(state);
    }
}

impl Hash for TextNode {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.content_hash.hash(state);
    }
}

impl Hash for ImageNode {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.resource.hash(state);
        self.fit.hash(state);
    }
}

fn text_node_content_hash(spans: &[TextSpan], style: &TextStyle) -> u64 {
    use std::hash::DefaultHasher;

    let mut hasher = DefaultHasher::new();
    spans.hash(&mut hasher);
    style.hash(&mut hasher);
    hasher.finish()
}

impl Hash for ScrollBoxNode {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.offset_y.to_bits().hash(state);
        self.scrollbar.hash(state);
    }
}

impl Hash for ElementContent {
    fn hash<H: Hasher>(&self, state: &mut H) {
        match self {
            Self::Container => 0u8.hash(state),
            Self::ScrollBox(s) => {
                1u8.hash(state);
                s.hash(state);
            }
            Self::Image(i) => {
                2u8.hash(state);
                i.hash(state);
            }
            Self::Text(t) => {
                3u8.hash(state);
                t.hash(state);
            }
        }
    }
}

impl Hash for Element {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.id.hash(state);
        self.style.hash(state);
        self.children.hash(state);
        self.content.hash(state);
    }
}

impl Element {
    /// A hash of this element's entire subtree.  Two subtrees with identical structure and
    /// data will produce the same hash; any change produces a different hash.
    pub fn subtree_hash(&self) -> u64 {
        use std::hash::DefaultHasher;
        let mut h = DefaultHasher::new();
        self.hash(&mut h);
        h.finish()
    }
}
