use bevy::prelude::{Component, Entity, Query, Reflect, ReflectComponent};

use crate::styles::{KStyle, StyleProp};

#[derive(Component, Debug, Clone, Copy)]
pub struct DirtyNode;

/// A widget node used for building the layout tree
#[derive(Debug, Reflect, Clone, PartialEq, Component)]
#[reflect(Component)]
pub struct Node {
    /// The list of children directly under this node
    pub children: Vec<WrappedIndex>,
    /// The ID of this node's widget
    pub id: WrappedIndex,
    /// The fully resolved styles for this node
    pub resolved_styles: KStyle,
    /// The raw styles for this node, before style resolution
    pub raw_styles: Option<KStyle>,
    /// The z-index of this node, used for controlling layering
    pub z: f32,
    pub old_z: f32,
    pub opacity: f32,
}

impl Default for Node {
    fn default() -> Self {
        Self {
            children: Default::default(),
            id: WrappedIndex(Entity::from_raw(0)),
            resolved_styles: Default::default(),
            raw_styles: Default::default(),
            z: Default::default(),
            old_z: Default::default(),
            opacity: 1.0,
        }
    }
}

/// A struct used for building a [`Node`]
pub struct NodeBuilder {
    node: Node,
}

impl NodeBuilder {
    /// Defines a basic node without children, styles, etc.
    pub fn empty() -> Self {
        Self {
            node: Node {
                children: Vec::new(),
                id: WrappedIndex(Entity::from_raw(0)),
                resolved_styles: KStyle::default(),
                raw_styles: None,
                z: 0.0,
                old_z: 0.0,
                opacity: 1.0,
            },
        }
    }

    /// Defines a node with the given id and styles
    pub fn new(id: WrappedIndex, styles: KStyle) -> Self {
        Self {
            node: Node {
                children: Vec::new(),
                id,
                resolved_styles: styles,
                raw_styles: None,
                z: 0.0,
                old_z: 0.0,
                opacity: 1.0,
            },
        }
    }

    /// Sets the ID of the node being built
    pub fn with_id(mut self, id: WrappedIndex) -> Self {
        self.node.id = id;
        self
    }

    /// Sets the children of the node being built
    pub fn with_children(mut self, children: Vec<WrappedIndex>) -> Self {
        self.node.children.extend(children);
        self
    }

    /// Sets the resolved and raw styles, respectively, of the node being built
    pub fn with_styles(mut self, resolved_styles: KStyle, raw_styles: Option<KStyle>) -> Self {
        self.node.resolved_styles = resolved_styles;
        self.node.raw_styles = raw_styles;
        self
    }

    pub fn with_opacity(mut self, opacity: f32) -> Self {
        self.node.opacity = opacity;
        self
    }

    /// Completes and builds the actual [`Node`]
    pub fn build(self) -> Node {
        self.node
    }
}

#[derive(Debug, Reflect, Clone, Copy, Hash, PartialEq, Eq)]
pub struct WrappedIndex(pub Entity);

impl<'a> morphorm::Node<'a> for WrappedIndex {
    type Data = Query<'a, 'a, &'static Node>;

    fn layout_type(&self, store: &'_ Self::Data) -> Option<morphorm::LayoutType> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.layout_type {
                StyleProp::Default => Some(morphorm::LayoutType::default()),
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::LayoutType::default()),
            };
        }
        Some(morphorm::LayoutType::default())
    }

    fn position_type(&self, store: &'_ Self::Data) -> Option<morphorm::PositionType> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.position_type {
                StyleProp::Default => Some(morphorm::PositionType::default()),
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::PositionType::default()),
            };
        }
        Some(morphorm::PositionType::default())
    }

    fn width(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.width {
                StyleProp::Default => Some(morphorm::Units::Stretch(1.0)),
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Stretch(1.0)),
            };
        }
        Some(morphorm::Units::Stretch(1.0))
    }

    fn height(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.height {
                StyleProp::Default => Some(morphorm::Units::Stretch(1.0)),
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Stretch(1.0)),
            };
        }
        Some(morphorm::Units::Stretch(1.0))
    }

    fn min_width(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.min_width {
                StyleProp::Default => Some(morphorm::Units::Pixels(0.0)),
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn min_height(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.min_height {
                StyleProp::Default => Some(morphorm::Units::Pixels(0.0)),
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn max_width(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.max_width {
                StyleProp::Default => Some(morphorm::Units::Auto),
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn max_height(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.max_height {
                StyleProp::Default => Some(morphorm::Units::Auto),
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn left(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.left {
                StyleProp::Default => match node.resolved_styles.offset {
                    StyleProp::Default => Some(morphorm::Units::Auto),
                    StyleProp::Value(prop) => Some(prop.left.into()),
                    _ => Some(morphorm::Units::Auto),
                },
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn right(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.right {
                StyleProp::Default => match node.resolved_styles.offset {
                    StyleProp::Default => Some(morphorm::Units::Auto),
                    StyleProp::Value(prop) => Some(prop.right.into()),
                    _ => Some(morphorm::Units::Auto),
                },
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn top(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.top {
                StyleProp::Default => match node.resolved_styles.offset {
                    StyleProp::Default => Some(morphorm::Units::Auto),
                    StyleProp::Value(prop) => Some(prop.top.into()),
                    _ => Some(morphorm::Units::Auto),
                },
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn bottom(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.bottom {
                StyleProp::Default => match node.resolved_styles.offset {
                    StyleProp::Default => Some(morphorm::Units::Auto),
                    StyleProp::Value(prop) => Some(prop.bottom.into()),
                    _ => Some(morphorm::Units::Auto),
                },
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn min_left(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> {
        Some(morphorm::Units::Auto)
    }

    fn max_left(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> {
        Some(morphorm::Units::Auto)
    }

    fn min_right(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> {
        Some(morphorm::Units::Auto)
    }

    fn max_right(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> {
        Some(morphorm::Units::Auto)
    }

    fn min_top(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> {
        Some(morphorm::Units::Auto)
    }

    fn max_top(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> {
        Some(morphorm::Units::Auto)
    }

    fn min_bottom(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> {
        Some(morphorm::Units::Auto)
    }

    fn max_bottom(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> {
        Some(morphorm::Units::Auto)
    }

    fn child_left(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.padding_left {
                StyleProp::Default => match node.resolved_styles.padding {
                    StyleProp::Default => Some(morphorm::Units::Auto),
                    StyleProp::Value(prop) => Some(prop.left.into()),
                    _ => Some(morphorm::Units::Auto),
                },
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn child_right(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.padding_right {
                StyleProp::Default => match node.resolved_styles.padding {
                    StyleProp::Default => Some(morphorm::Units::Auto),
                    StyleProp::Value(prop) => Some(prop.right.into()),
                    _ => Some(morphorm::Units::Auto),
                },
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn child_top(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.padding_top {
                StyleProp::Default => match node.resolved_styles.padding {
                    StyleProp::Default => Some(morphorm::Units::Auto),
                    StyleProp::Value(prop) => Some(prop.top.into()),
                    _ => Some(morphorm::Units::Auto),
                },
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn child_bottom(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.padding_bottom {
                StyleProp::Default => match node.resolved_styles.padding {
                    StyleProp::Default => Some(morphorm::Units::Auto),
                    StyleProp::Value(prop) => Some(prop.bottom.into()),
                    _ => Some(morphorm::Units::Auto),
                },
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn row_between(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.row_between {
                StyleProp::Default => Some(morphorm::Units::Auto),
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn col_between(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.col_between {
                StyleProp::Default => Some(morphorm::Units::Auto),
                StyleProp::Value(prop) => Some(prop.into()),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn grid_rows(&self, store: &'_ Self::Data) -> Option<Vec<morphorm::Units>> {
        if let Ok(node) = store.get(self.0) {
            return match &node.resolved_styles.grid_rows {
                StyleProp::Default => Some(vec![]),
                StyleProp::Value(prop) => Some(prop.iter().map(|&x| x.into()).collect()),
                _ => Some(vec![]),
            };
        }
        Some(vec![])
    }

    fn grid_cols(&self, store: &'_ Self::Data) -> Option<Vec<morphorm::Units>> {
        if let Ok(node) = store.get(self.0) {
            return match &node.resolved_styles.grid_cols {
                StyleProp::Default => Some(vec![]),
                StyleProp::Value(prop) => Some(prop.iter().map(|&x| x.into()).collect()),
                _ => Some(vec![]),
            };
        }
        Some(vec![])
    }

    fn row_index(&self, store: &'_ Self::Data) -> Option<usize> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.row_index {
                StyleProp::Default => Some(0),
                StyleProp::Value(prop) => Some(prop),
                _ => Some(0),
            };
        }
        Some(0)
    }

    fn col_index(&self, store: &'_ Self::Data) -> Option<usize> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.col_index {
                StyleProp::Default => Some(0),
                StyleProp::Value(prop) => Some(prop),
                _ => Some(0),
            };
        }
        Some(0)
    }

    fn row_span(&self, store: &'_ Self::Data) -> Option<usize> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.row_span {
                StyleProp::Default => Some(1),
                StyleProp::Value(prop) => Some(prop),
                _ => Some(1),
            };
        }
        Some(1)
    }

    fn col_span(&self, store: &'_ Self::Data) -> Option<usize> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.col_span {
                StyleProp::Default => Some(1),
                StyleProp::Value(prop) => Some(prop),
                _ => Some(1),
            };
        }
        Some(1)
    }

    fn border_left(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.border {
                StyleProp::Default => Some(morphorm::Units::Auto),
                StyleProp::Value(prop) => Some(morphorm::Units::Pixels(prop.left)),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn border_right(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.border {
                StyleProp::Default => Some(morphorm::Units::Auto),
                StyleProp::Value(prop) => Some(morphorm::Units::Pixels(prop.right)),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn border_top(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.border {
                StyleProp::Default => Some(morphorm::Units::Auto),
                StyleProp::Value(prop) => Some(morphorm::Units::Pixels(prop.top)),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }

    fn border_bottom(&self, store: &'_ Self::Data) -> Option<morphorm::Units> {
        if let Ok(node) = store.get(self.0) {
            return match node.resolved_styles.border {
                StyleProp::Default => Some(morphorm::Units::Auto),
                StyleProp::Value(prop) => Some(morphorm::Units::Pixels(prop.bottom)),
                _ => Some(morphorm::Units::Auto),
            };
        }
        Some(morphorm::Units::Auto)
    }
}