use dreport_core::models::{ContainerElement, PositionMode, SizeConstraint, SizeValue};
use taffy::prelude::*;

/// 1mm = 72/25.4 pt (kesin değer)
const MM_TO_PT_F64: f64 = 72.0 / 25.4;

pub fn mm_to_pt(mm: f64) -> f32 {
    (mm * MM_TO_PT_F64) as f32
}

pub fn pt_to_mm(pt: f32) -> f64 {
    (pt as f64) / MM_TO_PT_F64
}

/// SizeValue → taffy Dimension (width veya height için)
fn size_value_to_dimension(sv: &SizeValue) -> Dimension {
    match sv {
        SizeValue::Fixed { value } => Dimension::length(mm_to_pt(*value)),
        SizeValue::Auto => Dimension::auto(),
        // Fr için dimension Auto, flex_grow ayrıca set edilir
        SizeValue::Fr { .. } => Dimension::auto(),
    }
}

/// SizeValue → taffy LengthPercentage (min/max constraint'ler için)
fn mm_to_length(mm: f64) -> Dimension {
    Dimension::length(mm_to_pt(mm))
}

/// Fr değerini döndür (yoksa 0)
fn fr_value(sv: &SizeValue) -> f32 {
    match sv {
        SizeValue::Fr { value } => *value as f32,
        _ => 0.0,
    }
}

/// SizeConstraint'ten taffy Style'a flex_grow ayarını da dahil ederek dönüştür.
/// `main_axis` parametresi parent container'ın direction'ına göre
/// hangi eksenin flex_grow kullanacağını belirler.
pub fn apply_size_to_style(
    style: &mut Style,
    size: &SizeConstraint,
    parent_direction: Option<&str>,
) {
    style.size = Size {
        width: size_value_to_dimension(&size.width),
        height: size_value_to_dimension(&size.height),
    };

    // Min/max constraint'ler
    if let Some(min_w) = size.min_width {
        style.min_size.width = mm_to_length(min_w);
    }
    if let Some(min_h) = size.min_height {
        style.min_size.height = mm_to_length(min_h);
    }
    if let Some(max_w) = size.max_width {
        style.max_size.width = mm_to_length(max_w);
    }
    if let Some(max_h) = size.max_height {
        style.max_size.height = mm_to_length(max_h);
    }

    // Fr → flex_grow (main axis'e göre)
    let main_fr = match parent_direction {
        Some("row") => fr_value(&size.width),
        _ => fr_value(&size.height),
    };

    // Cross axis fr: row'da height fr, column'da width fr
    let cross_fr = match parent_direction {
        Some("row") => fr_value(&size.height),
        _ => fr_value(&size.width),
    };

    // Eğer main axis fr ise, flex_grow ayarla ve flex_basis 0 yap
    if main_fr > 0.0 {
        style.flex_grow = main_fr;
        style.flex_shrink = 1.0;
        style.flex_basis = Dimension::length(0.0);

        // min-width: 0 (row) veya min-height: 0 (column) ayarla —
        // taffy'de min_size default Auto = içerik boyutunun altına küçülemez.
        // Fr elemanların içerik taşırması engellemek için min_size 0 olmalı.
        match parent_direction {
            Some("row") => {
                if size.min_width.is_none() {
                    style.min_size.width = Dimension::length(0.0);
                }
            }
            _ => {
                if size.min_height.is_none() {
                    style.min_size.height = Dimension::length(0.0);
                }
            }
        }
    }

    // Cross axis fr ise, align_self stretch yeterli
    // (taffy'de cross axis flex_grow doğrudan yok, stretch ile çözülür)
    if cross_fr > 0.0 {
        style.align_self = Some(AlignSelf::Stretch);
    }
}

/// ContainerElement → taffy Style
pub fn container_to_style(el: &ContainerElement, parent_direction: Option<&str>) -> Style {
    let mut style = Style {
        display: Display::Flex,
        flex_direction: match el.direction.as_str() {
            "row" => FlexDirection::Row,
            _ => FlexDirection::Column,
        },
        gap: Size {
            width: LengthPercentage::length(mm_to_pt(el.gap)),
            height: LengthPercentage::length(mm_to_pt(el.gap)),
        },
        padding: Rect {
            top: LengthPercentage::length(mm_to_pt(el.padding.top)),
            right: LengthPercentage::length(mm_to_pt(el.padding.right)),
            bottom: LengthPercentage::length(mm_to_pt(el.padding.bottom)),
            left: LengthPercentage::length(mm_to_pt(el.padding.left)),
        },
        align_items: Some(match el.align.as_str() {
            "center" => AlignItems::Center,
            "end" => AlignItems::FlexEnd,
            "stretch" => AlignItems::Stretch,
            _ => AlignItems::FlexStart,
        }),
        justify_content: Some(match el.justify.as_str() {
            "center" => JustifyContent::Center,
            "end" => JustifyContent::FlexEnd,
            "space-between" => JustifyContent::SpaceBetween,
            _ => JustifyContent::FlexStart,
        }),
        ..Default::default()
    };

    // Pozisyon moduna göre
    match &el.base.position {
        PositionMode::Absolute { x, y } => {
            style.position = Position::Absolute;
            style.inset = Rect {
                top: LengthPercentageAuto::length(mm_to_pt(*y)),
                left: LengthPercentageAuto::length(mm_to_pt(*x)),
                right: auto(),
                bottom: auto(),
            };
        }
        PositionMode::Flow => {}
    }

    // Boyut
    apply_size_to_style(&mut style, &el.base.size, parent_direction);

    // Container border
    if let Some(bw) = el.style.border_width {
        let bpt = mm_to_pt(bw);
        style.border = Rect {
            top: LengthPercentage::length(bpt),
            right: LengthPercentage::length(bpt),
            bottom: LengthPercentage::length(bpt),
            left: LengthPercentage::length(bpt),
        };
    }

    style
}

/// Leaf element (text, line, image vs.) için taffy Style
pub fn leaf_style(
    size: &SizeConstraint,
    position: &PositionMode,
    parent_direction: Option<&str>,
) -> Style {
    let mut style = Style::default();

    match position {
        PositionMode::Absolute { x, y } => {
            style.position = Position::Absolute;
            style.inset = Rect {
                top: LengthPercentageAuto::length(mm_to_pt(*y)),
                left: LengthPercentageAuto::length(mm_to_pt(*x)),
                right: auto(),
                bottom: auto(),
            };
        }
        PositionMode::Flow => {}
    }

    apply_size_to_style(&mut style, size, parent_direction);

    style
}

#[cfg(test)]
mod tests {
    use super::*;
    use dreport_core::models::{ContainerStyle, ElementBase, Padding};

    #[test]
    fn test_mm_to_pt_conversion() {
        let pt = mm_to_pt(210.0);
        // A4 width = 210mm ≈ 595.28pt
        assert!((pt - 595.28).abs() < 0.1);
    }

    #[test]
    fn test_mm_to_pt_one_inch() {
        // 1 inch = 25.4mm = 72pt
        let pt = mm_to_pt(25.4);
        assert!(
            (pt - 72.0).abs() < 0.01,
            "25.4mm should be ~72pt, got {}",
            pt
        );
    }

    #[test]
    fn test_pt_to_mm_conversion() {
        // 72pt = 25.4mm (1 inch)
        let mm = pt_to_mm(72.0);
        assert!(
            (mm - 25.4).abs() < 0.01,
            "72pt should be ~25.4mm, got {}",
            mm
        );
    }

    #[test]
    fn test_roundtrip_mm_pt_mm() {
        // mm → pt → mm should preserve value within tolerance
        let original = 100.0_f64;
        let pt = mm_to_pt(original);
        let back = pt_to_mm(pt);
        assert!(
            (back - original).abs() < 0.01,
            "Roundtrip failed: {} → {}pt → {}",
            original,
            pt,
            back
        );
    }

    #[test]
    fn test_mm_to_pt_zero() {
        assert_eq!(mm_to_pt(0.0), 0.0);
    }

    #[test]
    fn test_pt_to_mm_zero() {
        assert!((pt_to_mm(0.0) - 0.0).abs() < f64::EPSILON);
    }

    #[test]
    fn test_fixed_size() {
        let sv = SizeValue::Fixed { value: 50.0 };
        assert_eq!(
            size_value_to_dimension(&sv),
            Dimension::length(mm_to_pt(50.0))
        );
    }

    #[test]
    fn test_auto_size() {
        let sv = SizeValue::Auto;
        assert_eq!(size_value_to_dimension(&sv), Dimension::auto());
    }

    #[test]
    fn test_fr_maps_to_auto_dimension() {
        let sv = SizeValue::Fr { value: 2.0 };
        assert_eq!(size_value_to_dimension(&sv), Dimension::auto());
    }

    #[test]
    fn test_fr_value_extraction() {
        assert_eq!(fr_value(&SizeValue::Fr { value: 3.0 }), 3.0);
        assert_eq!(fr_value(&SizeValue::Auto), 0.0);
        assert_eq!(fr_value(&SizeValue::Fixed { value: 10.0 }), 0.0);
    }

    #[test]
    fn test_apply_size_fr_sets_flex_grow() {
        let size = SizeConstraint {
            width: SizeValue::Fr { value: 2.0 },
            height: SizeValue::Auto,
            ..Default::default()
        };
        let mut style = Style::default();
        apply_size_to_style(&mut style, &size, Some("row"));
        assert_eq!(style.flex_grow, 2.0);
        assert_eq!(style.flex_basis, Dimension::length(0.0));
    }

    #[test]
    fn test_apply_size_fixed_no_flex_grow() {
        let size = SizeConstraint {
            width: SizeValue::Fixed { value: 50.0 },
            height: SizeValue::Fixed { value: 30.0 },
            ..Default::default()
        };
        let mut style = Style::default();
        apply_size_to_style(&mut style, &size, Some("row"));
        assert_eq!(style.flex_grow, 0.0);
    }

    #[test]
    fn test_apply_size_min_max_constraints() {
        let size = SizeConstraint {
            width: SizeValue::Auto,
            height: SizeValue::Auto,
            min_width: Some(20.0),
            max_width: Some(100.0),
            min_height: Some(10.0),
            max_height: Some(50.0),
        };
        let mut style = Style::default();
        apply_size_to_style(&mut style, &size, None);
        assert_eq!(style.min_size.width, Dimension::length(mm_to_pt(20.0)));
        assert_eq!(style.max_size.width, Dimension::length(mm_to_pt(100.0)));
        assert_eq!(style.min_size.height, Dimension::length(mm_to_pt(10.0)));
        assert_eq!(style.max_size.height, Dimension::length(mm_to_pt(50.0)));
    }

    #[test]
    fn test_container_to_style_direction() {
        let el = ContainerElement {
            base: ElementBase::flow("test".to_string(), SizeConstraint::default()),
            direction: "row".to_string(),
            gap: 5.0,
            padding: Padding {
                top: 10.0,
                right: 10.0,
                bottom: 10.0,
                left: 10.0,
            },
            align: "center".to_string(),
            justify: "space-between".to_string(),
            style: ContainerStyle::default(),
            children: vec![],
            break_inside: "auto".to_string(),
        };
        let style = container_to_style(&el, None);
        assert_eq!(style.flex_direction, FlexDirection::Row);
        assert_eq!(style.align_items, Some(AlignItems::Center));
        assert_eq!(style.justify_content, Some(JustifyContent::SpaceBetween));
    }

    #[test]
    fn test_container_to_style_absolute() {
        let el = ContainerElement {
            base: ElementBase {
                id: "test".to_string(),
                condition: None,
                position: PositionMode::Absolute { x: 20.0, y: 30.0 },
                size: SizeConstraint::default(),
            },
            direction: "column".to_string(),
            gap: 0.0,
            padding: Padding::default(),
            align: "stretch".to_string(),
            justify: "start".to_string(),
            style: ContainerStyle::default(),
            children: vec![],
            break_inside: "auto".to_string(),
        };
        let style = container_to_style(&el, None);
        assert_eq!(style.position, Position::Absolute);
    }

    #[test]
    fn test_leaf_style_flow() {
        let size = SizeConstraint {
            width: SizeValue::Fixed { value: 60.0 },
            height: SizeValue::Auto,
            ..Default::default()
        };
        let style = leaf_style(&size, &PositionMode::Flow, Some("column"));
        assert_eq!(style.position, Position::Relative);
        assert_eq!(style.size.width, Dimension::length(mm_to_pt(60.0)));
    }

    #[test]
    fn test_leaf_style_absolute() {
        let size = SizeConstraint {
            width: SizeValue::Fixed { value: 40.0 },
            height: SizeValue::Fixed { value: 20.0 },
            ..Default::default()
        };
        let style = leaf_style(&size, &PositionMode::Absolute { x: 10.0, y: 15.0 }, None);
        assert_eq!(style.position, Position::Absolute);
    }
}
