use crate::{
    css_cascade::ComputedStyle,
    css_values::property::{CssValue, PropertyId},
    layout::resolve::{ResolveContext, resolve_length, resolve_length_percentage},
};

pub fn computed_to_taffy(style: &ComputedStyle, ctx: &ResolveContext) -> taffy::Style {
    let mut ts = taffy::Style::default();

    if let Some(CssValue::Display(d)) = style.get(&PropertyId::Display) {
        use crate::css_values::types::display::Display;
        ts.display = match d {
            Display::None => taffy::Display::None,
            Display::Flex | Display::InlineFlex => taffy::Display::Flex,
            Display::Grid | Display::InlineGrid => taffy::Display::Grid,
            _ => taffy::Display::Block,
        };
    }

    if let Some(CssValue::Position(p)) = style.get(&PropertyId::Position) {
        use crate::css_values::types::display::Position;
        ts.position = match p {
            Position::Relative => taffy::Position::Relative,
            Position::Absolute | Position::Fixed => taffy::Position::Absolute,
            _ => taffy::Position::Relative,
        };
    }

    ts.size.width = css_to_dimension(style, &PropertyId::Width, ctx);
    ts.size.height = css_to_dimension(style, &PropertyId::Height, ctx);
    ts.min_size.width = css_to_dimension(style, &PropertyId::MinWidth, ctx);
    ts.min_size.height = css_to_dimension(style, &PropertyId::MinHeight, ctx);
    ts.max_size.width = css_to_dimension(style, &PropertyId::MaxWidth, ctx);
    ts.max_size.height = css_to_dimension(style, &PropertyId::MaxHeight, ctx);

    ts.margin.top = css_to_lpa(style, &PropertyId::MarginTop, ctx);
    ts.margin.right = css_to_lpa(style, &PropertyId::MarginRight, ctx);
    ts.margin.bottom = css_to_lpa(style, &PropertyId::MarginBottom, ctx);
    ts.margin.left = css_to_lpa(style, &PropertyId::MarginLeft, ctx);

    ts.padding.top = css_to_lp(style, &PropertyId::PaddingTop, ctx);
    ts.padding.right = css_to_lp(style, &PropertyId::PaddingRight, ctx);
    ts.padding.bottom = css_to_lp(style, &PropertyId::PaddingBottom, ctx);
    ts.padding.left = css_to_lp(style, &PropertyId::PaddingLeft, ctx);

    ts.border.top = css_to_border(style, &PropertyId::BorderTopWidth, ctx);
    ts.border.right = css_to_border(style, &PropertyId::BorderRightWidth, ctx);
    ts.border.bottom = css_to_border(style, &PropertyId::BorderBottomWidth, ctx);
    ts.border.left = css_to_border(style, &PropertyId::BorderLeftWidth, ctx);

    if let Some(CssValue::FlexDirection(fd)) = style.get(&PropertyId::FlexDirection) {
        use crate::css_values::types::display::FlexDirection;
        ts.flex_direction = match fd {
            FlexDirection::Row => taffy::FlexDirection::Row,
            FlexDirection::RowReverse => taffy::FlexDirection::RowReverse,
            FlexDirection::Column => taffy::FlexDirection::Column,
            FlexDirection::ColumnReverse => taffy::FlexDirection::ColumnReverse,
        };
    }
    if let Some(CssValue::FlexWrap(fw)) = style.get(&PropertyId::FlexWrap) {
        use crate::css_values::types::display::FlexWrap;
        ts.flex_wrap = match fw {
            FlexWrap::Nowrap => taffy::FlexWrap::NoWrap,
            FlexWrap::Wrap => taffy::FlexWrap::Wrap,
            FlexWrap::WrapReverse => taffy::FlexWrap::WrapReverse,
        };
    }
    if let Some(CssValue::Number(v)) = style.get(&PropertyId::FlexGrow) {
        ts.flex_grow = *v as f32;
    }
    if let Some(CssValue::Number(v)) = style.get(&PropertyId::FlexShrink) {
        ts.flex_shrink = *v as f32;
    }

    if let Some(CssValue::LengthPercentage(lp)) = style.get(&PropertyId::RowGap) {
        ts.gap.height =
            taffy::LengthPercentage::length(resolve_length_percentage(lp, ctx, ctx.viewport_h));
    }
    if let Some(CssValue::LengthPercentage(lp)) = style.get(&PropertyId::ColumnGap) {
        ts.gap.width =
            taffy::LengthPercentage::length(resolve_length_percentage(lp, ctx, ctx.viewport_w));
    }

    if let Some(CssValue::BoxSizing(bs)) = style.get(&PropertyId::BoxSizing) {
        use crate::css_values::types::display::BoxSizing;
        ts.box_sizing = match bs {
            BoxSizing::ContentBox => taffy::BoxSizing::ContentBox,
            BoxSizing::BorderBox => taffy::BoxSizing::BorderBox,
        };
    }

    ts
}

fn css_to_dimension(
    style: &ComputedStyle,
    prop: &PropertyId,
    ctx: &ResolveContext,
) -> taffy::Dimension {
    match style.get(prop) {
        Some(CssValue::LengthPercentageAuto(lpa)) => {
            use crate::css_values::types::length::LengthPercentageAuto as CssLPA;
            match lpa {
                CssLPA::Length(l) => taffy::Dimension::length(resolve_length(l, ctx)),
                CssLPA::Percentage(p) => taffy::Dimension::percent(*p as f32 / 100.0),
                CssLPA::Auto => taffy::Dimension::auto(),
            }
        }
        Some(CssValue::LengthPercentage(lp)) => {
            use crate::css_values::types::length::LengthPercentage as CssLP;
            match lp {
                CssLP::Length(l) => taffy::Dimension::length(resolve_length(l, ctx)),
                CssLP::Percentage(p) => taffy::Dimension::percent(*p as f32 / 100.0),
            }
        }
        Some(CssValue::Length(l)) => taffy::Dimension::length(resolve_length(l, ctx)),
        _ => taffy::Dimension::auto(),
    }
}

fn css_to_lpa(
    style: &ComputedStyle,
    prop: &PropertyId,
    ctx: &ResolveContext,
) -> taffy::LengthPercentageAuto {
    use crate::css_values::types::length::LengthPercentageAuto as CssLPA;
    match style.get(prop) {
        Some(CssValue::LengthPercentageAuto(lpa)) => match lpa {
            CssLPA::Length(l) => taffy::LengthPercentageAuto::length(resolve_length(l, ctx)),
            CssLPA::Percentage(p) => taffy::LengthPercentageAuto::percent(*p as f32 / 100.0),
            CssLPA::Auto => taffy::LengthPercentageAuto::auto(),
        },
        _ => taffy::LengthPercentageAuto::length(0.0),
    }
}

fn css_to_lp(
    style: &ComputedStyle,
    prop: &PropertyId,
    ctx: &ResolveContext,
) -> taffy::LengthPercentage {
    use crate::css_values::types::length::LengthPercentage as CssLP;
    match style.get(prop) {
        Some(CssValue::LengthPercentage(lp)) => match lp {
            CssLP::Length(l) => taffy::LengthPercentage::length(resolve_length(l, ctx)),
            CssLP::Percentage(p) => taffy::LengthPercentage::percent(*p as f32 / 100.0),
        },
        _ => taffy::LengthPercentage::length(0.0),
    }
}

fn css_to_border(
    style: &ComputedStyle,
    prop: &PropertyId,
    ctx: &ResolveContext,
) -> taffy::LengthPercentage {
    match style.get(prop) {
        Some(CssValue::Length(l)) => taffy::LengthPercentage::length(resolve_length(l, ctx)),
        _ => taffy::LengthPercentage::length(0.0),
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use super::*;
    use crate::css_values::types::{
        display::Display,
        length::{Length, LengthPercentageAuto},
    };

    #[test]
    fn default_style_maps() {
        let style = ComputedStyle::resolve(&HashMap::new(), None);
        let ctx = ResolveContext::default();
        let ts = computed_to_taffy(&style, &ctx);
        assert!(matches!(ts.display, taffy::Display::Block));
    }

    #[test]
    fn flex_display_maps() {
        let mut cascaded = HashMap::new();
        cascaded.insert(PropertyId::Display, CssValue::Display(Display::Flex));
        let style = ComputedStyle::resolve(&cascaded, None);
        let ctx = ResolveContext::default();
        let ts = computed_to_taffy(&style, &ctx);
        assert!(matches!(ts.display, taffy::Display::Flex));
    }

    #[test]
    fn width_px_maps() {
        let mut cascaded = HashMap::new();
        cascaded.insert(
            PropertyId::Width,
            CssValue::LengthPercentageAuto(LengthPercentageAuto::Length(Length::Px(200.0))),
        );
        let style = ComputedStyle::resolve(&cascaded, None);
        let ctx = ResolveContext::default();
        let ts = computed_to_taffy(&style, &ctx);
        assert_eq!(ts.size.width, taffy::Dimension::length(200.0));
    }

    #[test]
    fn margin_auto_maps() {
        let mut cascaded = HashMap::new();
        cascaded.insert(
            PropertyId::MarginLeft,
            CssValue::LengthPercentageAuto(LengthPercentageAuto::Auto),
        );
        let style = ComputedStyle::resolve(&cascaded, None);
        let ctx = ResolveContext::default();
        let ts = computed_to_taffy(&style, &ctx);
        assert_eq!(ts.margin.left, taffy::LengthPercentageAuto::auto());
    }
}
