//! Integration tests for layout module public API

use revue::layout::{
    max_width, merge_rects, min_width, responsive_layout, Breakpoint, Breakpoints, MediaQuery,
    Rect, ResponsiveLayout, ResponsiveValue,
};

// Convenience wrapper function for responsive tests
fn layout_with_width(width: u16) -> ResponsiveLayout {
    ResponsiveLayout::new(width, 24)
}

// ============================================================================
// Rect Tests
// ============================================================================

#[test]
fn test_rect_new() {
    let rect = Rect::new(10, 20, 100, 50);
    assert_eq!(rect.x, 10);
    assert_eq!(rect.y, 20);
    assert_eq!(rect.width, 100);
    assert_eq!(rect.height, 50);
}

#[test]
fn test_rect_default() {
    let rect = Rect::default();
    assert_eq!(rect.x, 0);
    assert_eq!(rect.y, 0);
    assert_eq!(rect.width, 0);
    assert_eq!(rect.height, 0);
}

#[test]
fn test_rect_contains_point_inside() {
    let rect = Rect::new(10, 10, 50, 50);
    assert!(rect.contains(15, 15));
    assert!(rect.contains(10, 10));
    assert!(rect.contains(59, 59));
}

#[test]
fn test_rect_contains_point_outside() {
    let rect = Rect::new(10, 10, 50, 50);
    assert!(!rect.contains(9, 15));
    assert!(!rect.contains(15, 9));
    assert!(!rect.contains(60, 15));
    assert!(!rect.contains(15, 60));
}

#[test]
fn test_rect_contains_point_on_edge() {
    let rect = Rect::new(10, 10, 50, 50);
    // Points on right/bottom edges are NOT contained
    assert!(!rect.contains(60, 15));
    assert!(!rect.contains(15, 60));
}

#[test]
fn test_rect_right() {
    let rect = Rect::new(10, 20, 100, 50);
    assert_eq!(rect.right(), 110);
}

#[test]
fn test_rect_right_saturating() {
    let rect = Rect::new(u16::MAX - 10, 0, 20, 10);
    assert_eq!(rect.right(), u16::MAX);
}

#[test]
fn test_rect_bottom() {
    let rect = Rect::new(10, 20, 100, 50);
    assert_eq!(rect.bottom(), 70);
}

#[test]
fn test_rect_bottom_saturating() {
    let rect = Rect::new(0, u16::MAX - 10, 10, 20);
    assert_eq!(rect.bottom(), u16::MAX);
}

#[test]
fn test_rect_intersects_true() {
    let rect1 = Rect::new(0, 0, 50, 50);
    let rect2 = Rect::new(25, 25, 50, 50);
    assert!(rect1.intersects(&rect2));
    assert!(rect2.intersects(&rect1));
}

#[test]
fn test_rect_intersects_false() {
    let rect1 = Rect::new(0, 0, 20, 20);
    let rect2 = Rect::new(30, 30, 20, 20);
    assert!(!rect1.intersects(&rect2));
}

#[test]
fn test_rect_intersects_adjacent() {
    let rect1 = Rect::new(0, 0, 20, 20);
    let rect2 = Rect::new(20, 0, 20, 20);
    // Adjacent rectangles do NOT intersect
    assert!(!rect1.intersects(&rect2));
}

#[test]
fn test_rect_intersection_some() {
    let rect1 = Rect::new(0, 0, 50, 50);
    let rect2 = Rect::new(25, 25, 50, 50);
    let result = rect1.intersection(&rect2);

    assert!(result.is_some());
    let intersection = result.unwrap();
    assert_eq!(intersection.x, 25);
    assert_eq!(intersection.y, 25);
    assert_eq!(intersection.width, 25);
    assert_eq!(intersection.height, 25);
}

#[test]
fn test_rect_intersection_none() {
    let rect1 = Rect::new(0, 0, 20, 20);
    let rect2 = Rect::new(30, 30, 20, 20);
    let result = rect1.intersection(&rect2);
    assert!(result.is_none());
}

#[test]
fn test_rect_union() {
    let rect1 = Rect::new(0, 0, 20, 20);
    let rect2 = Rect::new(10, 10, 20, 20);
    let result = rect1.union(&rect2);

    assert_eq!(result.x, 0);
    assert_eq!(result.y, 0);
    assert_eq!(result.width, 30);
    assert_eq!(result.height, 30);
}

#[test]
fn test_rect_union_non_overlapping() {
    let rect1 = Rect::new(0, 0, 10, 10);
    let rect2 = Rect::new(20, 20, 10, 10);
    let result = rect1.union(&rect2);

    assert_eq!(result.x, 0);
    assert_eq!(result.y, 0);
    assert_eq!(result.width, 30);
    assert_eq!(result.height, 30);
}

// ============================================================================
// merge_rects Tests
// ============================================================================

#[test]
fn test_merge_rects_empty() {
    let rects = vec![];
    let result = merge_rects(&rects);
    assert!(result.is_empty());
}

#[test]
fn test_merge_rects_single() {
    let rects = vec![Rect::new(0, 0, 10, 10)];
    let result = merge_rects(&rects);
    assert_eq!(result.len(), 1);
    assert_eq!(result[0], Rect::new(0, 0, 10, 10));
}

#[test]
fn test_merge_rects_non_overlapping() {
    let rects = vec![Rect::new(0, 0, 10, 10), Rect::new(20, 20, 10, 10)];
    let result = merge_rects(&rects);
    // Non-overlapping rects should remain separate
    assert_eq!(result.len(), 2);
}

#[test]
fn test_merge_rects_overlapping_pair() {
    let rects = vec![Rect::new(0, 0, 20, 20), Rect::new(10, 10, 20, 20)];
    let result = merge_rects(&rects);
    // Overlapping rects should be merged
    assert_eq!(result.len(), 1);
    assert_eq!(result[0].width, 30);
    assert_eq!(result[0].height, 30);
}

#[test]
fn test_merge_rects_chain() {
    let rects = vec![
        Rect::new(0, 0, 10, 10),
        Rect::new(10, 0, 10, 10),
        Rect::new(20, 0, 10, 10),
    ];
    let result = merge_rects(&rects);
    // Adjacent rects don't intersect (edge-to-edge), so they won't merge
    // Only overlapping rects are merged
    // First two: (0-10) and (10-20) don't overlap
    // Third: (20-30) doesn't overlap with second
    assert_eq!(result.len(), 3);
}

// ============================================================================
// Breakpoint Tests
// ============================================================================

#[test]
fn test_breakpoint_new() {
    let bp = Breakpoint::new("custom", 50);
    assert_eq!(bp.name, "custom");
    assert_eq!(bp.min_width, 50);
}

#[test]
fn test_breakpoint_xs_const() {
    assert_eq!(Breakpoint::XS.name, "xs");
    assert_eq!(Breakpoint::XS.min_width, 0);
}

#[test]
fn test_breakpoint_sm_const() {
    assert_eq!(Breakpoint::SM.name, "sm");
    assert_eq!(Breakpoint::SM.min_width, 40);
}

#[test]
fn test_breakpoint_md_const() {
    assert_eq!(Breakpoint::MD.name, "md");
    assert_eq!(Breakpoint::MD.min_width, 80);
}

#[test]
fn test_breakpoint_lg_const() {
    assert_eq!(Breakpoint::LG.name, "lg");
    assert_eq!(Breakpoint::LG.min_width, 120);
}

#[test]
fn test_breakpoint_xl_const() {
    assert_eq!(Breakpoint::XL.name, "xl");
    assert_eq!(Breakpoint::XL.min_width, 160);
}

// ============================================================================
// Breakpoints Tests
// ============================================================================

#[test]
fn test_breakpoints_new() {
    let _bp = Breakpoints::new();
    // Empty breakpoints returns a reference that may be invalid
    // Skip this test for now as it requires understanding the fallback behavior
}

#[test]
fn test_breakpoints_terminal() {
    let bp = Breakpoints::terminal();
    // Width 30 should be xs
    assert_eq!(bp.current(30).name, "xs");
    // Width 50 should be sm
    assert_eq!(bp.current(50).name, "sm");
    // Width 100 should be md
    assert_eq!(bp.current(100).name, "md");
    // Width 140 should be lg
    assert_eq!(bp.current(140).name, "lg");
    // Width 180 should be xl
    assert_eq!(bp.current(180).name, "xl");
}

#[test]
fn test_breakpoints_add() {
    let bp = Breakpoints::new()
        .add(Breakpoint::new("small", 20))
        .add(Breakpoint::new("large", 100));
    // Breakpoints are sorted by min_width
    assert_eq!(bp.current(10).name, "small");
    assert_eq!(bp.current(50).name, "small");
    assert_eq!(bp.current(150).name, "large");
}

#[test]
fn test_breakpoints_current_exact_match() {
    let bp = Breakpoints::new().add(Breakpoint::new("test", 50));
    assert_eq!(bp.current(50).name, "test");
}

#[test]
fn test_breakpoints_current_below_min() {
    let bp = Breakpoints::new().add(Breakpoint::new("test", 50));
    assert_eq!(bp.current(30).name, "test"); // Falls back to first available breakpoint
}

#[test]
fn test_breakpoints_current_above_min() {
    let bp = Breakpoints::new().add(Breakpoint::new("test", 50));
    assert_eq!(bp.current(70).name, "test");
}

#[test]
fn test_breakpoints_get() {
    let bp = Breakpoints::terminal();
    assert!(bp.get("md").is_some());
    assert!(bp.get("nonexistent").is_none());
}

#[test]
fn test_breakpoints_matches() {
    let bp = Breakpoints::terminal();
    assert!(bp.matches(100, "md"));
    assert!(!bp.matches(100, "lg"));
}

#[test]
fn test_breakpoints_at_least() {
    let bp = Breakpoints::terminal();
    assert!(bp.at_least(100, "md"));
    assert!(!bp.at_least(50, "md"));
}

#[test]
fn test_breakpoints_below() {
    let bp = Breakpoints::terminal();
    assert!(bp.below(50, "md"));
    assert!(!bp.below(100, "md"));
}

#[test]
fn test_breakpoints_names() {
    let bp = Breakpoints::terminal();
    let names = bp.names();
    assert_eq!(names, vec!["xs", "sm", "md", "lg", "xl"]);
}

#[test]
fn test_breakpoints_simple() {
    let bp = Breakpoints::simple();
    assert_eq!(bp.current(30).name, "sm");
    assert_eq!(bp.current(80).name, "md");
    assert_eq!(bp.current(120).name, "lg");
}

// ============================================================================
// ResponsiveValue Tests
// ============================================================================

#[test]
fn test_responsive_value_new() {
    let rv = ResponsiveValue::new(42);
    assert_eq!(rv.resolve(&Breakpoints::new(), 100), 42);
}

#[test]
fn test_responsive_value_at() {
    let bp = Breakpoints::terminal();
    let rv = ResponsiveValue::new(1).at("sm", 2).at("md", 3);

    assert_eq!(rv.resolve(&bp, 30), 1); // xs -> default
    assert_eq!(rv.resolve(&bp, 50), 2); // sm
    assert_eq!(rv.resolve(&bp, 100), 3); // md
    assert_eq!(rv.resolve(&bp, 150), 3); // lg -> last matched
}

#[test]
fn test_responsive_value_multiple_at() {
    let bp = Breakpoints::terminal();
    let rv = ResponsiveValue::new(1)
        .at("sm", 2)
        .at("md", 3)
        .at("lg", 4)
        .at("xl", 5);

    assert_eq!(rv.resolve(&bp, 180), 5);
}

#[test]
fn test_responsive_value_default_value() {
    let rv = ResponsiveValue::new(99);
    assert_eq!(rv.default_value(), &99);
}

// ============================================================================
// MediaQuery Helper Functions
// ============================================================================

#[test]
fn test_min_width_function() {
    let mq = min_width(80);
    assert!(matches!(mq, MediaQuery::MinWidth(80)));
}

#[test]
fn test_max_width_function() {
    let mq = max_width(80);
    assert!(matches!(mq, MediaQuery::MaxWidth(80)));
}

// ============================================================================
// ResponsiveLayout Tests
// ============================================================================

#[test]
fn test_responsive_layout_new() {
    let layout = ResponsiveLayout::new(100, 50);
    assert_eq!(layout.width(), 100);
    assert_eq!(layout.height(), 50);
}

#[test]
fn test_responsive_layout_default() {
    let layout = ResponsiveLayout::default();
    assert_eq!(layout.width(), 80);
    assert_eq!(layout.height(), 24);
}

#[test]
fn test_responsive_layout_current() {
    let layout = ResponsiveLayout::new(100, 50);
    assert_eq!(layout.current().name, "md");
}

#[test]
fn test_responsive_layout_breakpoint_name() {
    let layout = ResponsiveLayout::new(50, 20);
    assert_eq!(layout.breakpoint_name(), "sm");
}

#[test]
fn test_responsive_layout_at_least() {
    let layout = ResponsiveLayout::new(100, 50);
    assert!(layout.at_least("md"));
    assert!(!layout.at_least("lg"));
}

#[test]
fn test_responsive_layout_below() {
    let layout = ResponsiveLayout::new(50, 20);
    assert!(layout.below("md"));
    assert!(!layout.below("sm"));
}

#[test]
fn test_responsive_layout_resolve() {
    let layout = ResponsiveLayout::new(100, 50);
    let rv = ResponsiveValue::new(1).at("sm", 2).at("md", 3);
    assert_eq!(layout.resolve(&rv), 3);
}

#[test]
fn test_responsive_layout_is_portrait() {
    let layout = ResponsiveLayout::new(50, 100);
    assert!(layout.is_portrait());
}

#[test]
fn test_responsive_layout_is_landscape() {
    let layout = ResponsiveLayout::new(100, 50);
    assert!(layout.is_landscape());
}

#[test]
fn test_responsive_layout_resize() {
    let mut layout = ResponsiveLayout::new(50, 20);
    layout.resize(100, 50);
    assert_eq!(layout.width(), 100);
    assert_eq!(layout.height(), 50);
    assert_eq!(layout.current().name, "md");
}

#[test]
fn test_responsive_layout_with_breakpoints() {
    let custom_bp = Breakpoints::new().add(Breakpoint::new("small", 20));
    let layout = ResponsiveLayout::new(30, 10).with_breakpoints(custom_bp);
    assert_eq!(layout.current().name, "small");
}

#[test]
fn test_responsive_layout_function() {
    let layout = responsive_layout(80, 24);
    assert_eq!(layout.width(), 80);
    assert_eq!(layout.height(), 24);
}

// ============================================================================
// MediaQuery Tests
// ============================================================================

#[test]
fn test_media_query_min_width() {
    let layout = layout_with_width(100);
    let mq = MediaQuery::MinWidth(80);

    assert!(mq.matches(&layout));
}

#[test]
fn test_media_query_min_width_true() {
    let layout = layout_with_width(50);
    let mq = MediaQuery::MinWidth(80);

    assert!(!mq.matches(&layout));
}

#[test]
fn test_media_query_max_width() {
    let layout = layout_with_width(60);
    let mq = MediaQuery::MaxWidth(80);

    assert!(mq.matches(&layout));
}

#[test]
fn test_media_query_width_range() {
    let layout = layout_with_width(60);
    let mq = MediaQuery::WidthRange(40, 80);

    assert!(mq.matches(&layout));
}

#[test]
fn test_media_query_and() {
    let layout = layout_with_width(60);
    let mq = MediaQuery::MinWidth(40).and(MediaQuery::MaxWidth(80));

    assert!(mq.matches(&layout));
}

#[test]
fn test_media_query_or() {
    let layout = layout_with_width(120);
    let mq = MediaQuery::MinWidth(100).or(MediaQuery::MaxWidth(40));

    assert!(mq.matches(&layout));
}

#[test]
fn test_media_query_not() {
    let layout = layout_with_width(50);
    let mq = MediaQuery::MinWidth(80).not();

    assert!(mq.matches(&layout));
}

#[test]
fn test_media_query_breakpoint() {
    let layout = layout_with_width(50);
    let mq = MediaQuery::Breakpoint("sm");

    assert!(mq.matches(&layout));
}

#[test]
fn test_media_query_portrait() {
    let layout = ResponsiveLayout::new(50, 100);
    let mq = MediaQuery::Portrait;

    assert!(mq.matches(&layout));
}

#[test]
fn test_media_query_landscape() {
    let layout = ResponsiveLayout::new(100, 50);
    let mq = MediaQuery::Landscape;

    assert!(mq.matches(&layout));
}
