use super::*;

#[test]
fn adapter_conformance_fixture_runner_records_viewport_and_selection_ordering() {
    let (graph, node_id, _b, _out_port, _in_port, edge_id) = make_graph();
    let scenario = ConformanceScenario::new("viewport and selection ordering", graph)
        .with_actions([
            ConformanceAction::set_viewport(CanvasPoint { x: 10.0, y: 20.0 }, 1.25),
            ConformanceAction::set_selection(vec![node_id], vec![edge_id], Vec::new()),
        ])
        .with_expected_trace([
            ConformanceTraceEvent::viewport(CanvasPoint { x: 10.0, y: 20.0 }, 1.25),
            ConformanceTraceEvent::selection(vec![node_id], vec![edge_id], Vec::new()),
        ]);

    assert_conformance_trace(&scenario);
}

#[test]
fn adapter_conformance_fixture_runner_asserts_rendering_query() {
    let (mut graph, node_id, outside, _out_port, _in_port, edge_id) = make_graph();
    graph
        .update_node(&node_id, |node| {
            node.size = Some(CanvasSize {
                width: 40.0,
                height: 40.0,
            })
        })
        .expect("node exists");
    graph
        .update_node(&outside, |node| {
            node.pos = CanvasPoint { x: 140.0, y: 0.0 };
            node.size = Some(CanvasSize {
                width: 40.0,
                height: 40.0,
            });
        })
        .expect("node exists");
    let partial = NodeId::new();
    let mut partial_node = graph.nodes().get(&node_id).expect("node exists").clone();
    partial_node.pos = CanvasPoint { x: 95.0, y: 0.0 };
    partial_node.clear_ports();
    fixture_insert_node(&mut graph, partial, partial_node);
    let mut view_state = crate::io::NodeGraphViewState {
        draw_order: vec![outside, node_id, partial],
        edge_draw_order: vec![edge_id],
        ..crate::io::NodeGraphViewState::default()
    };
    view_state.set_selection(vec![node_id], vec![edge_id], Vec::new());
    let viewport_size = CanvasSize {
        width: 100.0,
        height: 100.0,
    };
    let expected = crate::runtime::store::NodeGraphStore::new(
        graph.clone(),
        view_state.clone(),
        crate::io::NodeGraphEditorConfig::default(),
    )
    .rendering_query(viewport_size);
    assert_eq!(expected.node_order, vec![outside, partial, node_id]);
    assert_eq!(expected.visible_node_render_order, vec![partial, node_id]);
    assert_eq!(expected.visible_edge_ids, vec![edge_id]);
    assert_eq!(expected.visible_edge_render_order, vec![edge_id]);

    let scenario = ConformanceScenario::new("rendering query", graph)
        .with_view_state(view_state)
        .with_rendering_query_contract(ConformanceRenderingQueryContract::new(
            viewport_size,
            expected,
        ));

    assert_conformance_trace(&scenario);
}

#[test]
fn adapter_conformance_fixture_runner_records_auto_pan_frame() {
    let (graph, _node_id, _b, _out_port, _in_port, _edge_id) = make_graph();
    let mut editor_config = crate::io::NodeGraphEditorConfig::default();
    editor_config.interaction.auto_pan.speed = 100.0;
    editor_config.interaction.auto_pan.margin = 20.0;
    let pan = CanvasPoint { x: -50.0, y: 0.0 };

    let scenario = ConformanceScenario::new("auto-pan frame", graph)
        .with_editor_config(editor_config)
        .with_actions([ConformanceAction::apply_auto_pan(AutoPanRequest::new(
            AutoPanActivation::Always,
            CanvasPoint { x: 190.0, y: 50.0 },
            CanvasSize {
                width: 200.0,
                height: 100.0,
            },
            1.0,
        ))])
        .with_expected_trace([ConformanceTraceEvent::viewport(pan, 1.0)]);

    assert_conformance_trace(&scenario);
}

#[test]
fn adapter_conformance_fixture_runner_records_selection_auto_pan_frame() {
    let (graph, _node_id, _b, _out_port, _in_port, _edge_id) = make_graph();
    let mut editor_config = crate::io::NodeGraphEditorConfig::default();
    editor_config.interaction.auto_pan.on_node_drag = false;
    editor_config.interaction.auto_pan.on_connect = false;
    editor_config.interaction.auto_pan.on_node_focus = false;
    editor_config.interaction.auto_pan.speed = 100.0;
    editor_config.interaction.auto_pan.margin = 20.0;
    let pan = CanvasPoint { x: -50.0, y: 0.0 };

    let scenario = ConformanceScenario::new("selection auto-pan frame", graph)
        .with_editor_config(editor_config)
        .with_actions([ConformanceAction::apply_selection_auto_pan(
            SelectionAutoPanRequest::new(
                CanvasPoint { x: 190.0, y: 50.0 },
                CanvasSize {
                    width: 200.0,
                    height: 100.0,
                },
                1.0,
            ),
        )])
        .with_expected_trace([ConformanceTraceEvent::viewport(pan, 1.0)]);

    assert_conformance_trace(&scenario);
}

#[test]
fn adapter_conformance_fixture_runner_applies_viewport_scroll_gesture_policy() {
    let (graph, _node_id, _b, _out_port, _in_port, _edge_id) = make_graph();
    let mut editor_config = crate::io::NodeGraphEditorConfig::default();
    editor_config.interaction.pan_on_scroll = true;
    editor_config.interaction.pan_on_scroll_speed = 2.0;
    editor_config.interaction.pan_on_scroll_mode = NodeGraphPanOnScrollMode::Horizontal;

    let scenario = ConformanceScenario::new("viewport scroll gesture policy", graph)
        .with_editor_config(editor_config)
        .with_actions([ConformanceAction::apply_viewport_scroll_gesture(
            ViewportGestureContext::idle(),
            ViewportScrollInput::new(
                CanvasPoint { x: 12.0, y: -8.0 },
                CanvasPoint { x: 80.0, y: 20.0 },
                false,
                2.0,
                0.25,
                4.0,
            ),
        )])
        .with_expected_trace([ConformanceTraceEvent::viewport(
            CanvasPoint { x: -24.0, y: 0.0 },
            1.0,
        )]);

    assert_conformance_trace(&scenario);
}

#[test]
fn adapter_conformance_fixture_runner_applies_translate_extent_viewport_constraints() {
    let (graph, _node_id, _b, _out_port, _in_port, _edge_id) = make_graph();
    let mut editor_config = crate::io::NodeGraphEditorConfig::default();
    editor_config.interaction.translate_extent = Some(CanvasRect {
        origin: CanvasPoint { x: 0.0, y: 0.0 },
        size: CanvasSize {
            width: 100.0,
            height: 100.0,
        },
    });
    let viewport_size = CanvasSize {
        width: 50.0,
        height: 50.0,
    };
    let clamped_pan = CanvasPoint { x: 0.0, y: -50.0 };
    let centered_pan = CanvasPoint { x: 50.0, y: 50.0 };

    let scenario = ConformanceScenario::new("viewport translate extent constraints", graph)
        .with_editor_config(editor_config)
        .with_xyflow_callbacks()
        .with_actions([
            ConformanceAction::apply_viewport_pan_constrained(
                ViewportPanRequest::new(CanvasPoint {
                    x: 400.0,
                    y: -300.0,
                }),
                viewport_size,
            ),
            ConformanceAction::apply_viewport_zoom_constrained(
                ViewportZoomRequest::new(CanvasPoint::default(), 0.25, 0.25, 4.0),
                viewport_size,
            ),
        ])
        .with_expected_trace([
            ConformanceTraceEvent::viewport(clamped_pan, 1.0),
            ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewChange {
                changes: vec![ConformanceViewChange::Viewport {
                    pan: clamped_pan,
                    zoom: 1.0,
                }],
            }),
            ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewportChange {
                pan: clamped_pan,
                zoom: 1.0,
            }),
            ConformanceTraceEvent::viewport(centered_pan, 0.25),
            ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewChange {
                changes: vec![ConformanceViewChange::Viewport {
                    pan: centered_pan,
                    zoom: 0.25,
                }],
            }),
            ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewportChange {
                pan: centered_pan,
                zoom: 0.25,
            }),
        ]);

    assert_conformance_trace(&scenario);
}

#[test]
fn adapter_conformance_fixture_runner_checks_viewport_gesture_rejections() {
    let (graph, _node_id, _b, _out_port, _in_port, _edge_id) = make_graph();
    let mut editor_config = crate::io::NodeGraphEditorConfig::default();
    editor_config.interaction.pan_on_drag = NodeGraphPanOnDragButtons {
        left: false,
        middle: false,
        right: true,
    };

    let scenario = ConformanceScenario::new("viewport gesture rejection policy", graph)
        .with_editor_config(editor_config)
        .with_actions([
            ConformanceAction::apply_viewport_drag_pan_gesture(
                ViewportGestureContext::idle(),
                ViewportDragPanInput::new(
                    ViewportPointerButton::Right,
                    CanvasPoint { x: 10.0, y: 4.0 },
                ),
            ),
            ConformanceAction::expect_viewport_drag_pan_gesture_rejected(
                ViewportGestureContext {
                    connection_in_progress: true,
                    ..ViewportGestureContext::idle()
                },
                ViewportDragPanInput::new(
                    ViewportPointerButton::Right,
                    CanvasPoint { x: 10.0, y: 4.0 },
                ),
                ViewportGestureRejection::ConnectionInProgress,
            ),
            ConformanceAction::expect_viewport_scroll_gesture_rejected(
                ViewportGestureContext {
                    user_selection_active: true,
                    ..ViewportGestureContext::idle()
                },
                ViewportScrollInput::new(
                    CanvasPoint { x: 0.0, y: 32.0 },
                    CanvasPoint { x: 20.0, y: 20.0 },
                    false,
                    2.0,
                    0.25,
                    4.0,
                ),
                ViewportGestureRejection::UserSelectionActive,
            ),
        ])
        .with_expected_trace([ConformanceTraceEvent::viewport(
            CanvasPoint { x: 10.0, y: 4.0 },
            1.0,
        )]);

    assert_conformance_trace(&scenario);
}

#[test]
fn adapter_conformance_fixture_runner_asserts_viewport_animation_plans() {
    let (graph, _node_id, _b, _out_port, _in_port, _edge_id) = make_graph();
    let from = ViewportTransform::new(CanvasPoint { x: 0.0, y: 0.0 }, 1.0).unwrap();
    let to = ViewportTransform::new(CanvasPoint { x: 100.0, y: -50.0 }, 3.0).unwrap();
    let expected_frame = ViewportAnimationFrame {
        elapsed_seconds: 0.5,
        progress: 0.25,
        eased_progress: 0.0625,
        transform: ViewportTransform::new(CanvasPoint { x: 6.25, y: -3.125 }, 1.125).unwrap(),
        done: false,
    };

    let double_click_current =
        ViewportTransform::new(CanvasPoint { x: 10.0, y: 20.0 }, 2.0).unwrap();
    let anchor = CanvasPoint { x: 120.0, y: 60.0 };
    let double_click_target =
        ViewportTransform::new(CanvasPoint { x: -10.0, y: 10.0 }, 3.0).unwrap();
    let expected_plan = ViewportAnimationPlan {
        from: double_click_current,
        to: double_click_target,
        duration_seconds: 0.2,
        easing: crate::runtime::viewport::ViewportAnimationEasing::CubicInOut,
    };

    let scenario = ConformanceScenario::new("viewport animation planning", graph)
        .with_actions([
            ConformanceAction::assert_viewport_animation_frame(
                ViewportAnimationRequest::new(from, to, ViewportAnimationOptions::new(2.0)),
                0.5,
                expected_frame,
            ),
            ConformanceAction::assert_viewport_double_click_zoom(
                ViewportDoubleClickZoomInput::new(
                    double_click_current,
                    anchor,
                    2.0,
                    0.5,
                    3.0,
                    ViewportAnimationOptions::new(0.2),
                ),
                expected_plan,
            ),
        ])
        .with_expected_trace([]);

    assert_conformance_trace(&scenario);
}

#[test]
fn adapter_conformance_fixture_runner_applies_viewport_animation_frames() {
    let (graph, _node_id, _b, _out_port, _in_port, _edge_id) = make_graph();
    let from = ViewportTransform::new(CanvasPoint { x: 0.0, y: 0.0 }, 1.0).unwrap();
    let to = ViewportTransform::new(CanvasPoint { x: 80.0, y: -40.0 }, 2.0).unwrap();
    let midpoint_pan = CanvasPoint { x: 40.0, y: -20.0 };
    let endpoint_pan = CanvasPoint { x: 80.0, y: -40.0 };

    let scenario = ConformanceScenario::new("viewport animation frame apply", graph)
        .with_xyflow_callbacks()
        .with_actions([ConformanceAction::apply_viewport_animation_frames(
            ViewportAnimationRequest::new(from, to, ViewportAnimationOptions::new(1.0)),
            [0.5, 1.0],
        )])
        .with_expected_trace([
            ConformanceTraceEvent::viewport(midpoint_pan, 1.5),
            ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewChange {
                changes: vec![ConformanceViewChange::Viewport {
                    pan: midpoint_pan,
                    zoom: 1.5,
                }],
            }),
            ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewportChange {
                pan: midpoint_pan,
                zoom: 1.5,
            }),
            ConformanceTraceEvent::viewport(endpoint_pan, 2.0),
            ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewChange {
                changes: vec![ConformanceViewChange::Viewport {
                    pan: endpoint_pan,
                    zoom: 2.0,
                }],
            }),
            ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewportChange {
                pan: endpoint_pan,
                zoom: 2.0,
            }),
        ]);

    assert_conformance_trace(&scenario);
}

#[test]
fn adapter_conformance_fixture_runner_applies_viewport_pan_inertia_frames() {
    let (graph, _node_id, _b, _out_port, _in_port, _edge_id) = make_graph();
    let tuning = NodeGraphPanInertiaTuning {
        enabled: true,
        decay_per_s: 2.0,
        min_speed: 100.0,
        max_speed: 1000.0,
    };
    let request = ViewportPanInertiaRequest::new(
        ViewportTransform::new(CanvasPoint::default(), 2.0).unwrap(),
        CanvasPoint { x: 1000.0, y: 0.0 },
        tuning.clone(),
    );
    let plan = plan_viewport_pan_inertia(request.clone()).expect("inertia plan");
    let mid = plan.frame_at(0.5).expect("mid inertia frame");
    let terminal = plan.terminal_frame().expect("terminal inertia frame");

    let scenario = ConformanceScenario::new("viewport pan inertia frame apply", graph)
        .with_xyflow_callbacks()
        .with_actions([
            ConformanceAction::apply_viewport_pan_inertia_frames(
                request,
                [0.5, plan.duration_seconds],
            ),
            ConformanceAction::expect_viewport_pan_inertia_rejected(
                ViewportPanInertiaRequest::new(
                    ViewportTransform::new(CanvasPoint::default(), 1.0).unwrap(),
                    CanvasPoint { x: 50.0, y: 0.0 },
                    tuning,
                ),
            ),
        ])
        .with_expected_trace([
            ConformanceTraceEvent::viewport(mid.transform.pan, mid.transform.zoom),
            ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewChange {
                changes: vec![ConformanceViewChange::Viewport {
                    pan: mid.transform.pan,
                    zoom: mid.transform.zoom,
                }],
            }),
            ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewportChange {
                pan: mid.transform.pan,
                zoom: mid.transform.zoom,
            }),
            ConformanceTraceEvent::viewport(terminal.transform.pan, terminal.transform.zoom),
            ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewChange {
                changes: vec![ConformanceViewChange::Viewport {
                    pan: terminal.transform.pan,
                    zoom: terminal.transform.zoom,
                }],
            }),
            ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewportChange {
                pan: terminal.transform.pan,
                zoom: terminal.transform.zoom,
            }),
        ]);

    assert_conformance_trace(&scenario);
}
