use crate::prelude::*;

fn build_custom_graph_1() -> graphview::Graph<String> {
    let mut nodes = Vec::new();
    let mut edges = Vec::new();
    nodes.push(
        graphview::NodeBuilder::new("School".to_string())
            .border(LineType::Double)
            .size(Size::new(20, 1))
            .build(),
    );
    nodes.push(
        graphview::NodeBuilder::new("Math".to_string())
            .border(LineType::Single)
            .size(Size::new(10, 1))
            .text_alignment(TextAlignment::Left)
            .build(),
    );
    nodes.push(
        graphview::NodeBuilder::new("English".to_string())
            .border(LineType::Ascii)
            .size(Size::new(15, 1))
            .text_alignment(TextAlignment::Right)
            .build(),
    );
    nodes.push(graphview::NodeBuilder::new("Science".to_string()).size(Size::new(12, 1)).build());
    nodes.push(
        graphview::NodeBuilder::new("Glonal\nLiterature".to_string())
            .size(Size::new(12, 2))
            .build(),
    );
    edges.push(graphview::EdgeBuilder::new(0, 1).build());
    edges.push(graphview::EdgeBuilder::new(0, 2).build());
    edges.push(graphview::EdgeBuilder::new(1, 3).build());
    edges.push(graphview::EdgeBuilder::new(2, 4).build());
    graphview::Graph::new(nodes, edges)
}

fn build_custom_graph_2() -> graphview::Graph<&'static str> {
    let nodes = &[
        "N1", "N2", "N3", "N4", "N5", "N6", "N7", "N8", "N9", "N10", "N11", "N12", "N13", "N14", "N15", "N16", "N17", "N18", "N19", "N20",
    ];

    let edges: &[(u32, u32)] = &[
        (0, 1),
        (0, 2),
        (0, 3),
        (1, 4),
        (1, 5),
        (1, 6),
        (2, 6),
        (2, 7),
        (2, 8),
        (3, 8),
        (3, 9),
        (3, 10),
        (4, 10),
        (4, 11),
        (4, 12),
        (5, 12),
        (5, 13),
        (5, 14),
        (6, 14),
        (6, 15),
        (6, 16),
        (7, 16),
        (7, 17),
        (7, 18),
        (8, 18),
        (8, 19),
        (8, 0),
        (9, 1),
        (9, 5),
        (9, 13),
        (10, 2),
        (10, 6),
        (10, 14),
        (11, 3),
        (11, 7),
        (11, 15),
        (12, 4),
        (12, 8),
        (12, 16),
        (13, 5),
        (13, 9),
        (13, 17),
        (14, 6),
        (14, 10),
        (14, 18),
        (15, 7),
        (15, 11),
        (15, 19),
        (16, 8),
        (16, 12),
        (16, 0),
        (17, 9),
        (17, 13),
        (17, 1),
        (18, 10),
        (18, 14),
        (18, 2),
        (19, 11),
        (19, 15),
        (19, 3),
    ];

    graphview::Graph::with_slices(nodes, edges, false)
}

fn build_custom_graph_3() -> graphview::Graph<String> {
    let mut nodes = Vec::new();
    let mut edges = Vec::new();
    nodes.push(
        graphview::NodeBuilder::new("School".to_string())
            .border(LineType::Double)
            .size(Size::new(20, 1))
            .text_attribute(charattr!("white,red"))
            .build(),
    );
    nodes.push(
        graphview::NodeBuilder::new("Math".to_string())
            .border(LineType::Single)
            .size(Size::new(10, 1))
            .text_alignment(TextAlignment::Left)
            .text_attribute(charattr!("white,darkgreen"))
            .build(),
    );
    nodes.push(
        graphview::NodeBuilder::new("English".to_string())
            .border(LineType::Ascii)
            .size(Size::new(15, 1))
            .text_alignment(TextAlignment::Right)
            .text_attribute(charattr!("black,pink"))
            .build(),
    );
    nodes.push(graphview::NodeBuilder::new("Science".to_string()).size(Size::new(12, 1)).build());
    nodes.push(
        graphview::NodeBuilder::new("Literature\n101".to_string())
            .size(Size::new(12, 2))
            .text_attribute(charattr!("black,aqua"))
            .build(),
    );
    edges.push(
        graphview::EdgeBuilder::new(0, 1)
            .attribute(charattr!("r,y"))
            .line_type(LineType::Ascii)
            .build(),
    );
    edges.push(
        graphview::EdgeBuilder::new(0, 2)
            .attribute(charattr!("a,black"))
            .line_type(LineType::Braille)
            .build(),
    );
    edges.push(
        graphview::EdgeBuilder::new(1, 3)
            .attribute(charattr!("green,black"))
            .line_type(LineType::Double)
            .build(),
    );
    edges.push(
        graphview::EdgeBuilder::new(2, 4)
            .attribute(charattr!("black,silver"))
            .line_type(LineType::Border)
            .build(),
    );
    graphview::Graph::new(nodes, edges)
}

fn build_custom_graph_4() -> graphview::Graph<String> {
    let mut nodes = Vec::new();
    let mut edges = Vec::new();
    nodes.push(graphview::NodeBuilder::new("A".to_string()).position(Point::new(1, 1)).build());
    nodes.push(graphview::NodeBuilder::new("B".to_string()).position(Point::new(10, 1)).build());
    nodes.push(graphview::NodeBuilder::new("C".to_string()).position(Point::new(20, 1)).build());
    nodes.push(graphview::NodeBuilder::new("D".to_string()).position(Point::new(20, 5)).build());
    nodes.push(graphview::NodeBuilder::new("E".to_string()).position(Point::new(10, 5)).build());
    edges.push(graphview::EdgeBuilder::new(0, 1).build());
    edges.push(graphview::EdgeBuilder::new(1, 2).build());
    edges.push(graphview::EdgeBuilder::new(2, 3).build());
    edges.push(graphview::EdgeBuilder::new(3, 4).build());
    edges.push(graphview::EdgeBuilder::new(4, 0).build());
    graphview::Graph::new(nodes, edges)
}

fn build_custom_graph_5() -> graphview::Graph<&'static str> {
    let nodes = &[
        "N1", "N2", "N3", "N4", "N5", "N6", "N7", "N8", "N9", "N10", "N11", "N12", "N13", "N14", "N15", "N16", "N17", "N18", "N19", "N20",
    ];

    let edges: &[(u32, u32)] = &[
        (0, 1),
        (1, 2),
        (2, 3),
        (3, 4),
        (5, 6),
        (6, 7),
        (7, 8),
        (8, 9),
        (10, 11),
        (11, 12),
        (10, 12),
        (13, 14),
        (14, 15),
        (13, 16),
        (15, 17),
        (15, 18),
        (16, 19),
    ];

    graphview::Graph::with_slices(nodes, edges, false)
}

#[test]
fn check_sinple_display() {
    let script = "
        Paint.Enable(false)
        Paint('Initial state')   
        CheckHash(0xC976C47E78769FFC) 
    ";
    let mut a = App::debug(60, 10, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = GraphView::new(Layout::fill(), graphview::Flags::ScrollBars | graphview::Flags::SearchBar);
    let g = graphview::Graph::with_slices(&["A", "B", "C", "D"], &[(0, 1), (0, 2), (1, 3)], true);
    gv.arrange_nodes(graphview::ArrangeMethod::GridPacked);
    gv.set_edge_routing(graphview::EdgeRouting::Orthogonal);
    gv.set_edge_line_type(LineType::SingleThick);
    gv.enable_arrow_heads(false);
    gv.enable_edge_highlighting(true, true);
    gv.set_background(char!(".,gray,black"));
    gv.set_components_toolbar_margins(2, 1);
    gv.set_graph(g);
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_sinple_display_proc_macro() {
    let script = "
        Paint.Enable(false)
        Paint('Initial state')   
        CheckHash(0xC976C47E78769FFC) 
    ";
    let mut a = App::debug(60, 10, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: SingleThick, routing: Orthogonal, hie: true, hoe: true, arrows: false, arrange: GridPacked, back:{.,gray,black}, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    let g = graphview::Graph::with_slices(&["A", "B", "C", "D"], &[(0, 1), (0, 2), (1, 3)], true);
    gv.set_graph(g);
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_events() {
    #[Window(events = GraphViewEvents<u32>, internal: true)]
    struct MyWin {}
    impl MyWin {
        fn new(g: graphview::Graph<u32>) -> Self {
            let mut w = Self { base: window!("Test,d:f") };
            let mut gv = graphview!("line-type: SingleThick, routing: Orthogonal, hie: true, hoe: true, arrows: false, arrange: GridPacked, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
            gv.set_graph(g);
            w.add(gv);
            w
        }
    }
    impl GraphViewEvents<u32> for MyWin {
        fn on_current_node_changed(&mut self, handle: Handle<GraphView<u32>>) -> EventProcessStatus {
            let nid = self.control(handle).unwrap().graph().current_node_id().unwrap();
            let f = format!("Node IDX: {nid}");
            self.set_title(&f);
            EventProcessStatus::Ignored
        }

        fn on_node_action(&mut self, _: Handle<GraphView<u32>>, item_index: usize) -> EventProcessStatus {
            let f = format!("Action IDX: {item_index}");
            self.set_title(&f);
            EventProcessStatus::Ignored
        }
    }

    let script = "
        Paint.Enable(false)
        Paint('1. Initial state')   
        CheckHash(0x369776D345DF3EB4) 
        Key.Pressed('Right')
        Paint('2. New node - title is: New IDX: 1')   
        CheckHash(0xCFF990B23499F06B) 
        Key.Pressed('Down')
        Paint('3. New node - title is: New IDX: 3')   
        CheckHash(0xA9F5CB1A947005) 
        Key.Pressed('Enter')
        Paint('4. Action - title is: Action IDX: 3')   
        CheckHash(0xDAB162CDCCC002FF) 
        Key.Pressed('Left')
        Paint('5. New node - title is: New IDX: 2')   
        CheckHash(0xFA69C3E174F6553C)         
        Key.Pressed('Up')
        Paint('6. New node - title is: New IDX: 0')   
        CheckHash(0xDD4D4629B0731CA6)         
    ";
    let mut a = App::debug(60, 10, script).build().unwrap();
    let g = graphview::Graph::with_slices(&[0, 1, 2, 3], &[(0, 1), (0, 2), (1, 3)], true);
    a.add_window(MyWin::new(g));
    a.run();
}

#[test]
fn check_events_with_mouse() {
    #[Window(events = GraphViewEvents<u32>, internal: true)]
    struct MyWin {}
    impl MyWin {
        fn new(g: graphview::Graph<u32>) -> Self {
            let mut w = Self { base: window!("Test,d:f") };
            let mut gv = graphview!("line-type: SingleThick, routing: Orthogonal, hie: true, hoe: true, arrows: false, arrange: GridPacked, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
            gv.set_graph(g);
            w.add(gv);
            w
        }
    }
    impl GraphViewEvents<u32> for MyWin {
        fn on_current_node_changed(&mut self, handle: Handle<GraphView<u32>>) -> EventProcessStatus {
            let nid = self.control(handle).unwrap().graph().current_node_id().unwrap();
            let f = format!("Node IDX: {nid}");
            self.set_title(&f);
            EventProcessStatus::Ignored
        }

        fn on_node_action(&mut self, _: Handle<GraphView<u32>>, item_index: usize) -> EventProcessStatus {
            let f = format!("Action IDX: {item_index}");
            self.set_title(&f);
            EventProcessStatus::Ignored
        }
    }

    let script = "
        Paint.Enable(false)
        Paint('1. Initial state')   
        CheckHash(0x369776D345DF3EB4) 
        Mouse.Click(9,2,left)
        Paint('2. New node - title is: New IDX: 1')   
        CheckHash(0xCFF990B23499F06B) 
        Mouse.Click(9,4,left)
        Paint('3. New node - title is: New IDX: 3')   
        CheckHash(0xA9F5CB1A947005) 
        Mouse.DoubleClick(9,4,left)
        Paint('4. Action - title is: Action IDX: 3')   
        CheckHash(0xDAB162CDCCC002FF) 
        Mouse.Click(4,4,left)
        Paint('5. New node - title is: New IDX: 2')   
        CheckHash(0xFA69C3E174F6553C)         
        Mouse.Click(4,2,left)
        Paint('6. New node - title is: New IDX: 0')   
        CheckHash(0xDD4D4629B0731CA6)         
    ";
    let mut a = App::debug(60, 10, script).build().unwrap();
    let g = graphview::Graph::with_slices(&[0, 1, 2, 3], &[(0, 1), (0, 2), (1, 3)], true);
    a.add_window(MyWin::new(g));
    a.run();
}

#[test]
fn check_graph_with_margins_for_node_single() {
    let script = "
        Paint.Enable(false)
        Paint('Initial state')   
        CheckHash(0x7CDDF286305BF42D) 
    ";
    let mut a = App::debug(60, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: SingleThick, routing: Orthogonal, hie: true, hoe: true, arrows: false, arrange: GridPacked, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    let g = graphview::Graph::with_slices_and_border(
        &["Node-A", "Node-B", "Node-C", "Node-D"],
        &[(0, 1), (0, 2), (1, 3)],
        LineType::Single,
        true,
    );
    gv.set_graph(g);
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_graph_with_margins_for_node_double() {
    let script = "
        Paint.Enable(false)
        Paint('Initial state')   
        CheckHash(0x91DEDEB78C848365) 
    ";
    let mut a = App::debug(60, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: SingleThick, routing: Orthogonal, hie: true, hoe: true, arrows: false, arrange: GridPacked, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    let g = graphview::Graph::with_slices_and_border(
        &["Node-A", "Node-B", "Node-C", "Node-D"],
        &[(0, 1), (0, 2), (1, 3)],
        LineType::Double,
        true,
    );
    gv.set_graph(g);
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_hover_node() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state')   
        CheckHash(0xA52D4BE5FE3169FD) 
        Mouse.Move(15,4)
        Paint('2. Hovered over Node-B')   
        CheckHash(0xD82A932648700D17) 
        Mouse.Move(15,7)
        Paint('3. Hovered over Node-D')   
        CheckHash(0x2C607075309AE9F3)      
        Mouse.Move(40,7)
        Paint('4. hovered outside any node')   
        CheckHash(0xA52D4BE5FE3169FD)             
    ";
    let mut a = App::debug(60, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: SingleThick, routing: Orthogonal, hie: true, hoe: true, arrows: false, arrange: GridPacked, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    let g = graphview::Graph::with_slices_and_border(
        &["Node-A", "Node-B", "Node-C", "Node-D"],
        &[(0, 1), (0, 2), (1, 3)],
        LineType::SingleRound,
        true,
    );
    gv.set_graph(g);
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_tree_view() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state')   
        CheckHash(0xBE6D432823B0BC2B)           
    ";
    let mut a = App::debug(60, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: SingleThick, routing: Orthogonal, hie: false, hoe: false, arrows: true, arrange: Hierarchical, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    let g = graphview::Graph::with_slices(
        &["1", "1-1", "1-2", "1-1-1", "1-1-2", "1-2-1", "1-2-2"],
        &[(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)],
        true,
    );
    gv.set_graph(g);
    w.add(gv);
    a.add_window(w);
    a.run();
}
#[test]
fn check_tree_view_packed() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state')   
        CheckHash(0x29694D45CF699BF)           
    ";
    let mut a = App::debug(60, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: SingleThick, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: HierarchicalPacked, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    let g = graphview::Graph::with_slices(
        &["1", "1-1", "1-2", "1-1-1", "1-1-2", "1-2-1", "1-2-2"],
        &[(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)],
        true,
    );
    gv.set_graph(g);
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_custom_graph() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state')   
        CheckHash(0x3A54E82DF4408030)           
    ";
    let mut a = App::debug(60, 20, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: SingleThick, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: Hierarchical, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    gv.set_graph(build_custom_graph_1());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_arrange_hierarchical() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state')   
        CheckHash(0xC6B8B2D87813BCED)           
    ";
    let mut a = App::debug(130, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: Hierarchical, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    gv.set_graph(build_custom_graph_2());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_arrange_grid() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state')   
        CheckHash(0x1F4C38ABDB6BB845)           
    ";
    let mut a = App::debug(130, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!(
        "line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: GridPacked, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1"
    );
    gv.set_graph(build_custom_graph_2());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_arrange_circular() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state')   
        CheckHash(0x710407C8369BB527)           
    ";
    let mut a = App::debug(60, 30, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: Circular, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    gv.set_graph(build_custom_graph_2());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_arrange_force_directed() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state')   
        CheckHash(0xCF8E04F6F85CD000)           
    ";
    let mut a = App::debug(120, 30, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: ForceDirected, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    gv.set_graph(build_custom_graph_2());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_mouse_wheel() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state')   
        CheckHash(0x2A306A3D0F8190EA)
        Mouse.Wheel(20,10,right,3)
        Paint('2. Scroll right 3 times')   
        CheckHash(0x7E3EDB940E168E0F)
        Mouse.Wheel(20,10,down,10)
        Paint('3. Scroll down 10 times')   
        CheckHash(0x60AB14D9C5B89607)
        Mouse.Wheel(20,10,left,2)
        Paint('4. Scroll left 2 times')   
        CheckHash(0xF35DC5C49FA3C5D4)
        Mouse.Wheel(20,10,up,4)
        Paint('5. Scroll left 4 times')   
        CheckHash(0x96CF99C818070CCC)
    ";
    let mut a = App::debug(40, 20, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: ForceDirected, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    gv.set_graph(build_custom_graph_2());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_scroll_view_via_keyboard() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state')   
        CheckHash(0x2A306A3D0F8190EA)
        Key.Pressed(Alt+Right,3)
        Paint('2. Scroll right 3 times')   
        CheckHash(0x7E3EDB940E168E0F)
        Key.Pressed(Alt+Down,10)
        Paint('3. Scroll down 10 times')   
        CheckHash(0x60AB14D9C5B89607)
        Key.Pressed(Alt+Left,2)
        Paint('4. Scroll left 2 times')   
        CheckHash(0xF35DC5C49FA3C5D4)
        Key.Pressed(Alt+Up,4)
        Paint('5. Scroll left 4 times')   
        CheckHash(0x96CF99C818070CCC)
        Key.Pressed(PageDown)
        Paint('6. Scroll Down One Page')   
        CheckHash(0x9CD59E33F0D1DCA6)
        Key.Pressed(PageUp)
        Paint('7. Scroll Up One Page')   
        CheckHash(0x4617C19A7708D5B3)
        Key.Pressed(Home)
        Paint('8. Goto top-left of the graph')   
        CheckHash(0x2A306A3D0F8190EA)
        Key.Pressed(End)
        Paint('9. Goto bottom-right of graph')   
        CheckHash(0xFC5CED13088F95C3)
    ";
    let mut a = App::debug(40, 20, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: ForceDirected, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    gv.set_graph(build_custom_graph_2());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_search_bar() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state')   
        CheckHash(0x710407C8369BB527)   
        Key.TypeText('N1')        
        Paint('2. Nodes that conrain N1 are selected')   
        CheckHash(0xF95D8B2BE34ED00B)   
        Key.Pressed(Enter)
        Paint('3. Move to Next node found (-> N10)')   
        CheckHash(0xF4A63DCADBB69072)   
        Key.Pressed(Ctrl+Enter,2)
        Paint('4. Move backwards twice to Previous node found (-> N19)')   
        CheckHash(0x64E4BBBE905D4056)   
        Key.TypeText('7')        
        Paint('5. Now only N17 is selected')   
        CheckHash(0xCD55F86BFF9BC314)   
        Key.Pressed(Escape)       
        Paint('6. Clear search text (N17 is still current node'))   
        CheckHash(0x836840446A2F8D16)   
        Key.Pressed(Escape)       
        Paint('7. Exit window (Escape is no longer processed)')   
        CheckHash(0x9EE74A86D600A6F5)   
    ";
    let mut a = App::debug(60, 30, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: Circular, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    gv.set_graph(build_custom_graph_2());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_moving_nodes_with_keyboard() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (on node <1>)')   
        CheckHash(0x11284079999C4D55)
        Key.Pressed(Right)           
        Paint('2. Move to node: <1-2-1>')   
        CheckHash(0x94ECE0C4BEEF149)
        Key.Pressed(Right)           
        Paint('3. Move to node: <1-2>')   
        CheckHash(0x315E4E42E45F35AD)
        Key.Pressed(Right)              
        Paint('4. Move to node: <1-2-2>')   
        CheckHash(0x46209BF2096F9E75)
        Key.Pressed(Left, 2)           
        Paint('5. Move to node: <1-2-2>')   
        CheckHash(0x94ECE0C4BEEF149)
        Key.Pressed(Up)           
        Paint('6. Move to node: <1-2>')   
        CheckHash(0x315E4E42E45F35AD)
        Key.Pressed(Ctrl+Right,10)           
        Paint('7. Move node: <1-2> to a right most position')   
        CheckHash(0x1BDE5E242E760D11)
        Key.Pressed(Ctrl+Up,3)           
        Paint('8. Move node: <1-2> to a top position (by 3)')   
        CheckHash(0x14E7FC3ACDE047D1)        
        Key.Pressed(Ctrl+Left,12)           
        Paint('9. Move node: <1-2> to a left most poition')   
        CheckHash(0x6FFBC2338F6C3597)        
        Key.Pressed(Down)           
        Paint('10. Move to node: <1-2-1>')   
        CheckHash(0xFABC5CEE2E3A23A3)
        Key.Pressed(Ctrl+Down,5)           
        Paint('11. Move node: <1-2-1> down, also so scroll moves to ensure <1-2-1> is visible')   
        CheckHash(0x60E8B63004E31F9C)        
        Key.Pressed(Ctrl+Left,40)           
        Paint('12. Move node: <1-2-1> left, also so scroll moves to ensure <1-2-1> is visible')   
        CheckHash(0x1B960839B2B9F81E)        
        Key.Pressed(Ctrl+Up,10)           
        Paint('13. Move node: <1-2-1> up, also so scroll moves to ensure <1-2-1> is visible')   
        CheckHash(0xDDD275848378D703)        
        Key.Pressed(Ctrl+Right,60)           
        Paint('14. Move node: <1-2-1> right, also so scroll moves to ensure <1-2-1> is visible')   
        CheckHash(0x97B0C6BD53B493DB)        
    ";
    let mut a = App::debug(60, 10, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: SingleThick, routing: Orthogonal, hie: false, hoe: false, arrows: true, arrange: Hierarchical, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    let g = graphview::Graph::with_slices(
        &["1", "1-1", "1-2", "1-1-1", "1-1-2", "1-2-1", "1-2-2"],
        &[(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)],
        true,
    );
    gv.set_graph(g);
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_scroll_from_scrollbars() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state')   
        CheckHash(0x522B607E2901558)
        Mouse.Click(37,19,left)
        Paint('2. Scroll left by 1')   
        CheckHash(0x631EB708E571E526)
        Mouse.Click(37,19,left)
        Mouse.Click(37,19,left)
        Paint('3. Scroll left by 2')   
        CheckHash(0x3A949F7C6F289B17)
        Mouse.Click(39,18,left)
        Paint('4. Scroll down by 1')   
        CheckHash(0x8073C14693BB8C9E)        
        Mouse.Click(36,19,left)
        Paint('5. Move scroll to right most')   
        CheckHash(0x68BC29B5BA766271)        
        Mouse.Click(39,17,left)
        Paint('6. Move scroll to Bottom most')   
        CheckHash(0x9AACA4A28AB010C)        
        Mouse.Click(39,2,left)
        Paint('7. Move scroll up by 1')   
        CheckHash(0x253AD9719554AD11)        
        Mouse.Click(39,3,left)
        Paint('8. Move scroll to top most')   
        CheckHash(0xD3A23E8D8D66211C)        
        Mouse.Click(18,19,left)
        Paint('9. Move scroll left by 1')   
        CheckHash(0x2A8F52BF81AD8F86)        
        Mouse.Click(19,19,left)
        Paint('10. Move scroll to left most')   
        CheckHash(0x469A23C3E150C371)        

    ";
    let mut a = App::debug(40, 20, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: Circular, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    gv.set_graph(build_custom_graph_2());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_navigate_between_nodes() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (current is N1)')   
        CheckHash(0x522B607E2901558)
        Key.Pressed(Ctrl+Tab)
        Paint('2. Focus on N2 (with ensure visible)')   
        CheckHash(0x9AB273BF25427B77)   
        Key.Pressed(Ctrl+Tab,2)
        Paint('3. Focus on N4 (with ensure visible)')   
        CheckHash(0xE6FBB28B074C74B6)   
        Key.Pressed(Ctrl+Shift+Tab)
        Paint('4. Focus on N3 (with ensure visible)')   
        CheckHash(0x6888EA4B81E93586)   
    ";
    let mut a = App::debug(40, 20, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: Circular, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    gv.set_graph(build_custom_graph_2());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_graph_disabled() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (current is N1)')   
        CheckHash(0xD7A096B508B654AB) 
    ";
    let mut a = App::debug(40, 20, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: Circular, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1, enable: false");
    gv.set_graph(build_custom_graph_2());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_graph_without_focus() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (focus on button)')   
        CheckHash(0x9F6629F0F641D509) 
        Key.Pressed(Tab)
        Paint('2. Now with focus')   
        CheckHash(0xC4ACEFC3627EC4E9) 
    ";
    let mut a = App::debug(40, 20, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: Circular, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1,back:{*,white,black}");
    gv.set_graph(build_custom_graph_2());
    w.add(gv);
    w.add(button!("Press,a:c,w:10"));
    a.add_window(w);
    a.run();
}

#[test]
fn check_graph_with_u32() {
    let script = "
        Paint.Enable(false)
        Paint('Initial state')   
        CheckHash(0xCF5921784B552D98) 
        Mouse.Move(23,6)
        Paint('Hovered over node')
        CheckHash(0x4F280346E0D0B000) 
    ";
    let mut a = App::debug(60, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: SingleThick, routing: Orthogonal, hie: true, hoe: true, arrows: false, arrange: GridPacked, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    let g = graphview::Graph::with_slices(
        &[1u32, 100, 1000, 255, 65535, 0xFFFFFFFF, 99],
        &[(0, 1), (0, 2), (1, 2), (2, 3), (0, 4), (1, 5), (2, 6), (2, 5), (3, 4)],
        false,
    );
    gv.set_graph(g);
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_graph_with_i32() {
    let script = "
        Paint.Enable(false)
        Paint('Initial state')   
        CheckHash(0x240751B72EF9B9E0) 
        Mouse.Move(5,6)
        Paint('Hovered over node (-1234)')
        CheckHash(0xCD750515028A6FF8) 
    ";
    let mut a = App::debug(60, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!(
        "line-type: Single, routing: Direct, hie: true, hoe: true, arrows: false, arrange: GridPacked, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1"
    );
    let g = graphview::Graph::with_slices(
        &[1, 100, 1000, 255, -1234, -1, 99],
        &[(0, 1), (0, 2), (1, 2), (2, 3), (0, 4), (1, 5), (2, 6), (2, 5), (3, 4)],
        false,
    );
    gv.set_graph(g);
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_scroll_with_mouse_drag() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (current is N1)')   
        CheckHash(0x522B607E2901558) 
        Mouse.Drag(30,9,17,9)
        Paint('2. Drag horizontally')   
        CheckHash(0x1026F782FAFDAB79) 
        Mouse.Drag(17,9,12,9)
        Paint('3. Drag vertically')   
        CheckHash(0x58F9C35AFE5A0EE6) 
        Mouse.Drag(17,9,-100,-100)
        Paint('4. Drag until bottom-right')   
        CheckHash(0x4A55F2C886D16CA2) 
        Mouse.Drag(17,9,100,100)
        Paint('5. Drag until top-left')   
        CheckHash(0x522B607E2901558)         
    ";
    let mut a = App::debug(40, 20, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: Circular, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    gv.set_graph(build_custom_graph_2());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_mouse_drag_nodes() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (current is N1)')   
        CheckHash(0x522B607E2901558) 
        Mouse.Drag(9,7,30,17)
        Paint('2. Drag N11')   
        CheckHash(0xFF03D585A1AE8C20)        
        Mouse.Drag(30,17,-3,17)
        Paint('3. Drag N11 - and extend on Left')   
        CheckHash(0xC0A6E5F151220CAF)        
        Mouse.Drag(5,17,5,-2)
        Paint('4. Drag N11 - and extend on top')   
        CheckHash(0x672EFC99CCBB85AC)        
        Mouse.Drag(5,2,45,2)
        Paint('5. Drag N11 - and extend on riht')   
        CheckHash(0xD4437D12488EB421)        
    ";
    let mut a = App::debug(40, 20, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: Circular, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    gv.set_graph(build_custom_graph_2());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_api() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (current is N1)')   
        CheckHash(0x522B607E2901558) 
    ";
    let mut a = App::debug(40, 20, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: Circular, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1,back:{x}");
    gv.set_graph(build_custom_graph_2());
    assert_eq!(gv.graph().current_node_id(), Some(0));
    assert_eq!(*gv.graph().current_node().unwrap().value(), "N1");
    assert_eq!(*gv.graph().node(1).unwrap().value(), "N2");
    assert_eq!(gv.graph().nodes_count(), 20);
    gv.clear_background();
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_click_double_click() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (current is N1)')   
        CheckHash(0x522B607E2901558) 
        Mouse.Click(9,7,left)
        Paint('2. Clink on N11 (new node selected)')   
        CheckHash(0x2F11FF95B7641249) 
        Mouse.DoubleClick(9,7,left)
        Paint('3. DoubleClick on N11 (nothing changes)')   
        CheckHash(0x2F11FF95B7641249)         
    ";
    let mut a = App::debug(40, 20, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: Circular, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1,back:{x}");
    gv.set_graph(build_custom_graph_2());
    gv.clear_background();
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_mouse_event_enter_leave() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (button has the focus)')   
        CheckHash(0xED41A043919CE36B)    
        Key.Pressed(Tab)   
        Paint('2. Graphview has the focus')   
        CheckHash(0x90BD62B2DB6108A4)    
        Key.Pressed(Tab)   
        Paint('3. Button has the focus')   
        CheckHash(0xED41A043919CE36B)  
        Mouse.Move(14,4)  
        Paint('4. Mouse.Hover over N19')   
        CheckHash(0x866707DCCD848BAC)  
        Mouse.Move(35,1)  
        Paint('5. Mouse.Hover over Button')   
        CheckHash(0xED41A043919CE36B)  
    ";
    let mut a = App::debug(40, 10, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: Circular, x:0,y:0,w:30,h:100%, flags:[ScrollBars,SearchBar],lsm:2,tsm:1,back:{x}");
    gv.set_graph(build_custom_graph_2());
    gv.clear_background();
    w.add(gv);
    w.add(vline!("x:30,y:0,h:100%"));
    w.add(button!("Test,x:32,y:0,w:8"));
    a.add_window(w);
    a.run();
}

#[test]
fn check_custom_nodes() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (button has the focus)')   
        CheckHash(0x75EDC95A42AF8EB2)    
    ";
    let mut a = App::debug(120, 30, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!(
        "line-type: Single, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: Circular, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1"
    );
    gv.set_graph(build_custom_graph_3());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_small_graph_grid() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (button has the focus)')   
        CheckHash(0x3F3EA09383A225E1)    
    ";
    let mut a = App::debug(40, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!(
        "line-type: Single, routing: Orthogonal, hie: true, hoe: false, arrows: true, arrange: grid, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1"
    );
    let g = graphview::Graph::with_slices(&["A", "B"], &[(0, 1)], true);
    gv.set_graph(g);
    w.add(gv);
    a.add_window(w);
    a.run();
}
#[test]
fn check_small_graph_circular() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (button has the focus)')   
        CheckHash(0xC3ACDE8A75F03BB2)    
    ";
    let mut a = App::debug(40, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: true, hoe: false, arrows: true, arrange: circular, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    let g = graphview::Graph::with_slices(&["A", "B"], &[(0, 1)], true);
    gv.set_graph(g);
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_small_graph_hierarchical() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (button has the focus)')   
        CheckHash(0x3F3EA09383A225E1)    
    ";
    let mut a = App::debug(40, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!("line-type: Single, routing: Orthogonal, hie: true, hoe: false, arrows: true, arrange: Hierarchical, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1");
    let g = graphview::Graph::with_slices(&["A", "B"], &[(0, 1)], true);
    gv.set_graph(g);
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_normal_graph_grid() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (button has the focus)')   
        CheckHash(0xA4BD71B231C98680)    
    ";
    let mut a = App::debug(40, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!(
        "line-type: Single, routing: Orthogonal, hie: true, hoe: false, arrows: false, arrange: Grid, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1"
    );
    let g = graphview::Graph::with_slices_and_border(&["A", "Node B", "C", "D12"], &[(0, 1), (0, 2), (1, 2), (2, 3)], LineType::Single, false);
    gv.set_graph(g);
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_empty_graph_grid() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (button has the focus)')   
        CheckHash(0x6F340CB050701481)   
        Key.TypeText('abc')
        Key.Pressed(Enter)
        Key.Pressed(Ctrl+Enter)
        Key.Pressed(Escape)
        Key.Pressed(Up)
        Key.Pressed(Down) 
        Key.Pressed(Ctrl+Tab)
        Key.Pressed(Ctrl+Shift+Tab)
        Paint('2. same state')   
        CheckHash(0x6F340CB050701481)   
        Mouse.Drag(10,10,20,10)
        Paint('3. same state')   
        CheckHash(0x6F340CB050701481)   
    ";
    let mut a = App::debug(40, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let gv: graphview::graphview::GraphView<u8> = graphview!(
        "line-type: Single, routing: Orthogonal, hie: true, hoe: false, arrows: false, arrange: Grid, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1"
    );
    assert!(gv.graph().current_node_id().is_none());
    assert!(gv.graph().current_node().is_none());
    assert!(gv.graph().node(1).is_none());
    assert_eq!(gv.graph().nodes_count(), 0);

    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_graph_with_cusom_node_positions() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (button has the focus)')   
        CheckHash(0xB58F2D09300FF889)    
    ";
    let mut a = App::debug(40, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!(
        "line-type: Single, routing: Orthogonal, hie: true, hoe: false, arrows: false, arrange: None, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1"
    );
    gv.set_graph(build_custom_graph_4());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_multi_graph_hierarchical() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (button has the focus)')
        CheckHash(0xE3FB6F2B982BED13)
    ";
    let mut a = App::debug(130, 30, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!(
        "line-type: Single, routing: Orthogonal, hie: true, hoe: false, arrows: false, arrange: Hierarchical, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1"
    );
    gv.set_graph(build_custom_graph_5());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_multi_graph_circular() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (button has the focus)')   
        CheckHash(0x9CEFA8197DC8117)    
    ";
    let mut a = App::debug(130, 30, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!(
        "line-type: Single, routing: Orthogonal, hie: true, hoe: false, arrows: false, arrange: circular, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1"
    );
    gv.set_graph(build_custom_graph_5());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_multi_graph_force_directed() {
    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (button has the focus)')   
        CheckHash(0x6B3617673D0B6383)    
    ";
    let mut a = App::debug(130, 30, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!(
        "line-type: Single, routing: Orthogonal, hie: true, hoe: false, arrows: false, arrange: forcedirected, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1"
    );
    gv.set_graph(build_custom_graph_5());
    w.add(gv);
    a.add_window(w);
    a.run();
}

#[test]
fn check_doc_example() {
    type NodeValue = &'static str;

    #[Window(events = GraphViewEvents<NodeValue>, internal: true)]
    struct MyWin {
        graph_view: Handle<GraphView<NodeValue>>,
    }
    impl MyWin {
        fn new() -> Self {
            let mut win = MyWin {
                base: Window::new("Graph Demo", layout!("d:f"), window::Flags::None),
                graph_view: Handle::None,
            };

            // Create a simple graph
            let nodes = &["Root", "Child 1", "Child 2", "Grandchild"];
            let edges = &[(0, 1), (0, 2), (1, 3)];
            let graph = graphview::Graph::with_slices(nodes, edges, true);

            // Create and configure the GraphView
            let mut gv = graphview!("d:f,arrange:Grid,routing:Orthogonal,arrows:true,flags:[ScrollBars,SearchBar],hie:true");
            gv.set_graph(graph);

            win.graph_view = win.add(gv);
            win
        }
    }
    impl GraphViewEvents<NodeValue> for MyWin {
        fn on_current_node_changed(&mut self, handle: Handle<GraphView<&'static str>>) -> EventProcessStatus {
            if let Some(gv) = self.control(handle) {
                if let Some(node) = gv.graph().current_node() {
                    let title = format!("Graph Demo - Selected: {}", node.value());
                    self.set_title(&title);
                }
            }
            EventProcessStatus::Processed
        }

        fn on_node_action(&mut self, handle: Handle<GraphView<&'static str>>, node_index: usize) -> EventProcessStatus {
            if let Some(gv) = self.control(handle) {
                if let Some(node) = gv.graph().node(node_index) {
                    let title = format!("Graph Demo - Action on: {}", node.value());
                    self.set_title(&title);
                }
            }
            EventProcessStatus::Processed
        }
    }

    let script = "
        Paint.Enable(false)
        Paint('1. Initial state (button has the focus)')   
        CheckHash(0xCC478EC25AC80686)  
        Mouse.Click(24,2,left)  
        Paint('2. Click on Child 1')   
        CheckHash(0x196ECBEC764AED6A)  
    ";
    let mut a = App::debug(60, 15, script).build().unwrap();
    a.add_window(MyWin::new());
    a.run();
}

fn modify_graph_test_graphview() -> GraphView<&'static str> {
    let mut gv = graphview!(
        "line-type: SingleThick, routing: Orthogonal, hie: true, hoe: true, arrows: false, arrange: GridPacked, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1"
    );
    let g = graphview::Graph::with_slices(&["Node"], &[], true);
    gv.set_graph(g);
    gv
}

/// Renders after [`GraphView::modify_graph`] calls [`EditableNode::set_text_attribute`] on node 0.
/// Replace `CheckHash(0x0)` with the hash reported when the assertion fails.
#[test]
fn modify_graph_set_node_text_color_check_hash() {
    let script = "
        Paint.Enable(false)
        Paint('modify_graph: set_text_attribute (red foreground)')
        CheckHash(0x5814A3858DC8E69)
    ";
    let mut a = App::debug(60, 10, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = modify_graph_test_graphview();
    gv.modify_graph(|g| {
        if let Some(mut node) = g.node(0) {
            node.set_text_attribute(CharAttribute::with_color(Color::Red, Color::Black));
        }
    });
    w.add(gv);
    a.add_window(w);
    a.run();
}

/// Renders after [`GraphView::modify_graph`] calls [`EditableNode::set_border`] on node 0.
#[test]
fn modify_graph_set_node_border_check_hash() {
    let script = "
        Paint.Enable(false)
        Paint('modify_graph: set_border (double) - height will chage to support the border')
        CheckHash(0xF48C83C7DCE8EF51)
    ";
    let mut a = App::debug(60, 10, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = modify_graph_test_graphview();
    gv.modify_graph(|g| {
        if let Some(mut node) = g.node(0) {
            node.set_border(LineType::Double);
        }
    });
    w.add(gv);
    a.add_window(w);
    a.run();
}

/// Renders after [`GraphView::modify_graph`] calls [`EditableNode::set_value`] on node 0.
#[test]
fn modify_graph_set_node_content_check_hash() {
    let script = "
        Paint.Enable(false)
        Paint('modify_graph: set_value (label text)')
        CheckHash(0xD8FD6426B4F7736D)
    ";
    let mut a = App::debug(60, 10, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = modify_graph_test_graphview();
    gv.modify_graph(|g| {
        if let Some(mut node) = g.node(0) {
            node.set_value("1234");
        }
    });
    w.add(gv);
    a.add_window(w);
    a.run();
}

/// Single [`GraphView::modify_graph`] closure updating text color, border, and node value together.
#[test]
fn modify_graph_set_color_border_and_content_check_hash() {
    let script = "
        Paint.Enable(false)
        Paint('modify_graph: color + border + value in one closure')
        CheckHash(0xD307A82D0CDA1AFB)
    ";
    let mut a = App::debug(60, 10, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = modify_graph_test_graphview();
    gv.modify_graph(|g| {
        if let Some(mut node) = g.node(0) {
            node.set_value("All-in-one");
            node.set_border(LineType::Ascii);
            node.set_bounds(Rect::new(6,3,20,7));
        }
    });
    w.add(gv);
    a.add_window(w);
    a.run();
}

/// Builds an empty graph, then [`GraphView::modify_graph`] adds two nodes and a directed edge between them.
#[test]
fn modify_graph_add_two_nodes_and_edge_check_hash() {
    let script = "
        Paint.Enable(false)
        Paint('modify_graph: add_node x2 + add_edge(0 -> 1)')
        CheckHash(0x8F7C724263880506)
    ";
    let mut a = App::debug(60, 10, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!(
        "line-type: SingleThick, routing: Orthogonal, hie: true, hoe: true, arrows: false, arrange: GridPacked, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1"
    );
    let g: graphview::Graph<&'static str> = graphview::Graph::with_slices(&[], &[], true);
    gv.set_graph(g);
    gv.modify_graph(|g| {
        let a = graphview::NodeBuilder::new("A")
            .position(Point::new(2, 2))
            .size(Size::new(8, 1))
            .build();
        let b = graphview::NodeBuilder::new("B")
            .position(Point::new(22, 2))
            .size(Size::new(8, 1))
            .build();
        let id_a = g.add_node(a);
        let id_b = g.add_node(b);
        let _ = g.add_edge(
            graphview::EdgeBuilder::new(id_a as u32, id_b as u32)
                .directed(true)
                .build(),
        );
    });
    w.add(gv);
    a.add_window(w);
    a.run();
}

/// [`GraphView::modify_graph`] deletes a node that has both incoming and outgoing edges (node `B` in A→B→D, A→C).
#[test]
fn modify_graph_delete_node_with_edges_check_hash() {
    let script = "
        Paint.Enable(false) 
        Paint('modify_graph: delete_node(1) removes B (edges 0->1 and 1->3)')
        CheckHash(0xB2685CB981BCDA7)
    ";
    let mut a = App::debug(60, 15, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!(
        "line-type: SingleThick, routing: Orthogonal, hie: true, hoe: true, arrows: false, arrange: GridPacked, d:f, flags:[ScrollBars,SearchBar],lsm:2,tsm:1"
    );
    let g = graphview::Graph::with_slices(
        &["A", "B", "C", "D"],
        &[(0, 1), (0, 2), (1, 3)],
        true,
    );
    gv.set_graph(g);
    gv.modify_graph(|g| {
        g.delete_node(1);
    });
    w.add(gv);
    a.add_window(w);
    a.run();
}

/// Four nodes **A**–**D** in fixed positions (corners of a rectangle): arrow **Right** / **Down** walks A→B→C; **D** stays unselected.
fn graph_four_nodes_multiselect_layout() -> graphview::Graph<&'static str> {
    graphview::Graph::new(
        vec![
            graphview::NodeBuilder::new("A")
                .position(Point::new(4, 3))
                .size(Size::new(8, 1))
                .build(),
            graphview::NodeBuilder::new("B")
                .position(Point::new(30, 3))
                .size(Size::new(8, 1))
                .build(),
            graphview::NodeBuilder::new("C")
                .position(Point::new(30, 14))
                .size(Size::new(8, 1))
                .build(),
            graphview::NodeBuilder::new("D")
                .position(Point::new(4, 14))
                .size(Size::new(8, 1))
                .build(),
        ],
        vec![],
    )
}

/// **Step 1:** four nodes, `MultiSelect` + scrollbars (no search bar so **Space** reaches the graph).
/// **Step 2:** navigate A→B→C with arrows; **Space** on each to select three nodes (D never selected).
/// **Step 3:** **Ctrl+arrow** nudges the selected group (all four directions).
///
/// Replace each `CheckHash(0x0)` with the hash printed when that assertion fails (see other `graphview` tests).
#[test]
fn multiselect_space_three_nodes_then_ctrl_arrow_move_check_hash() {
    let script = "
        Paint.Enable(false)

        Paint('1. Initial state')
        CheckHash(0xB25906CDFF834E58)
        Key.Pressed(Space)
        Paint('2. Node A is selected')
        CheckHash(0xCE03FA1E85520AD9)
        Key.Pressed(Right)
        Paint('3. Arrow Right — current node is B, A remains selected')
        CheckHash(0xA4C9A203971FA6D9)
        Key.Pressed(Space)
        Paint('4. A and B are selected')
        CheckHash(0x6D779A01E496CB34)
        Key.Pressed(Down)
        Paint('5. Arrow Down — current node is C, A and B remain selected')
        CheckHash(0x36F086E2F7CDAEB4)
        Key.Pressed(Space)
        Paint('6. A, B, C are selected, D remains unselected')
        CheckHash(0x10703C77CBCDC451)

        Key.Pressed(Ctrl+Right,2)
        Paint('7: Ctrl+Right x2 — group moves to the right (A, B and C)')
        CheckHash(0xE932A9CC07EA8251)
        Key.Pressed(Ctrl+Down,2)
        Paint('8: Ctrl+Down x2 — group moves down (A, B and C)')
        CheckHash(0x4BD7935EF01B691)
        Key.Pressed(Ctrl+Left,2)
        Paint('9: Ctrl+Left x2 — group moves left (A, B and C)')
        CheckHash(0x866AB7194FB3DC51)
        Key.Pressed(Ctrl+Up,2)
        Paint('10: Ctrl+Up x2 — group moves up (A, B and C)')
        CheckHash(0x10703C77CBCDC451)
    ";
    let mut a = App::debug(72, 22, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!(
        "line-type: SingleThick, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: None, d:f, flags:[ScrollBars,MultiSelect],lsm:2,tsm:1"
    );
    gv.set_graph(graph_four_nodes_multiselect_layout());
    w.add(gv);
    a.add_window(w);
    a.run();
}

/// Same graph and **Ctrl+arrow** moves as [`multiselect_space_three_nodes_then_ctrl_arrow_move_check_hash`], but the three nodes are
/// included via **Ctrl+left click** (`Key.Modifier(Ctrl)` + `Mouse.Click(..., left)` + `Key.Modifier(None)`), matching `GraphView` multi-select mouse handling.
///
/// For `App::debug(72, 22)` and a full-desktop test window, terminal coordinates **(9,2)**, **(35,2)**, and **(35,13)** hit nodes **A**, **B**, and **C** (column **x = 9** matches [`check_events_with_mouse`] for hitting the first node).
/// Do not plain-click a node and then Ctrl+click the same pixel — the second action toggles selection off again.
#[test]
fn multiselect_ctrl_click_three_nodes_then_ctrl_arrow_move_check_hash() {
    let script = "
        Paint.Enable(false)

        Paint('1. Initial state')
        CheckHash(0xB25906CDFF834E58)
        Key.Modifier(Ctrl)
        Mouse.Click(9,2,left)
        Key.Modifier(None)
        Paint('2. Ctrl+click node A — A selected (same end state as Space on A)')
        CheckHash(0xCE03FA1E85520AD9)
        Key.Modifier(Ctrl)
        Mouse.Click(35,2,left)
        Key.Modifier(None)
        Paint('3. Ctrl+click node B — A and B selected')
        CheckHash(0x6D779A01E496CB34)
        Key.Modifier(Ctrl)
        Mouse.Click(35,13,left)
        Key.Modifier(None)
        Paint('4. Ctrl+click node C — A, B, C selected; D unselected')
        CheckHash(0x10703C77CBCDC451)

        Key.Pressed(Ctrl+Right,2)
        Paint('5. Ctrl+Right x2 — group nudge right')
        CheckHash(0xE932A9CC07EA8251)
        Key.Pressed(Ctrl+Down,2)
        Paint('6. Ctrl+Down x2 — group nudge down')
        CheckHash(0x4BD7935EF01B691)
        Key.Pressed(Ctrl+Left,2)
        Paint('7. Ctrl+Left x2 — group nudge left')
        CheckHash(0x866AB7194FB3DC51)
        Key.Pressed(Ctrl+Up,2)
        Paint('8. Ctrl+Up x2 — group nudge up')
        CheckHash(0x10703C77CBCDC451)
    ";
    let mut a = App::debug(72, 22, script).build().unwrap();
    let mut w = window!("Test,d:f");
    let mut gv = graphview!(
        "line-type: SingleThick, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: None, d:f, flags:[ScrollBars,MultiSelect],lsm:2,tsm:1"
    );
    gv.set_graph(graph_four_nodes_multiselect_layout());
    w.add(gv);
    a.add_window(w);
    a.run();
}

/// Starts from an **empty** graph; [`GraphView`] raises `on_request_new_node` for **Ctrl+click on empty canvas**
/// (`Key.Modifier(Ctrl)` + `Mouse.Click(..., left)`), matching `GraphView` mouse handling.
/// Then **right-button drag** from node to node (`Mouse.Hold` / `Mouse.Move` / `Mouse.Release`) raises `on_request_new_edge`.
///
/// The debug script uses **left** for placement because empty-area node creation is wired to the primary button with Ctrl held,
/// not the secondary mouse button (right opens edge-drag when pressed on a node).
#[test]
fn ctrl_click_empty_add_three_nodes_then_right_drag_edges_check_hash() {
    #[Window(events = GraphViewEvents<String>, internal: true)]
    struct InteractiveGraphWin {}

    impl InteractiveGraphWin {
        fn new() -> Self {
            let mut w = Self { base: window!("Test,d:f") };
            let mut gv: GraphView<String> = graphview!(
                "line-type: SingleThick, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: None, d:f, flags:[ScrollBars,MultiSelect],lsm:2,tsm:1"
            );
            gv.set_graph(graphview::Graph::<String>::new(vec![], vec![]));
            w.add(gv);
            w
        }
    }

    impl GraphViewEvents<String> for InteractiveGraphWin {
        fn on_request_new_node(&mut self, handle: Handle<GraphView<String>>, p: Point) -> EventProcessStatus {
            if let Some(gv) = self.control_mut(handle) {
                gv.modify_graph(|g| {
                    let n = graphview::NodeBuilder::new(format!("N{}", g.nodes_count() + 1))
                        .position(p)
                        .size(Size::new(8, 1))
                        .build();
                    let id = g.add_node(n);
                    g.set_current_node(id);
                });
            }
            EventProcessStatus::Processed
        }

        fn on_request_new_edge(&mut self, handle: Handle<GraphView<String>>, from: u32, to: u32) -> EventProcessStatus {
            if let Some(gv) = self.control_mut(handle) {
                gv.modify_graph(|g| {
                    let _ = g.add_edge(graphview::EdgeBuilder::new(from, to).directed(true).build());
                });
            }
            EventProcessStatus::Processed
        }
    }

    let script = "
        Paint.Enable(false)

        Paint('1. Empty graph')
        CheckHash(0xE3D0E179665B4428)
        Key.Modifier(Ctrl)
        Mouse.Click(12,6,left)
        Key.Modifier(None)
        Paint('2. First node (Ctrl+click empty)')
        CheckHash(0xFA31EC1C288F3CE1)
        Key.Modifier(Ctrl)
        Mouse.Click(40,6,left)
        Key.Modifier(None)
        Paint('3. Second node')
        CheckHash(0xE88EC73796576F1B)
        Key.Modifier(Ctrl)
        Mouse.Click(26,14,left)
        Key.Modifier(None)
        Paint('4. Third node')
        CheckHash(0xDF7079A5B7D1D42C)

        Mouse.Hold(12,6,right)
        Mouse.Move(40,6)
        Mouse.Release(40,6,right)
        Paint('5. Edge 0 -> 1')
        CheckHash(0x72AD618F506F168C)

        Mouse.Hold(40,6,right)
        Mouse.Move(26,14)
        Mouse.Release(26,14,right)
        Paint('6. Edge 1 -> 2')
        CheckHash(0x8A74B28D54C70BF6)
    ";

    let mut a = App::debug(72, 22, script).build().unwrap();
    a.add_window(InteractiveGraphWin::new());
    a.run();
}

/// Same flow as [`ctrl_click_empty_add_three_nodes_then_right_drag_edges_check_hash`], but [`NodeBuilder`] uses
/// **`LineType::Double`** borders, then **Ctrl+A** selects every visible node before the right-drag edge strokes.
///
/// Golden hashes are for `App::debug(72, 22)`; if the surface layout changes, re-record from the debug **found** hash.
#[test]
fn ctrl_click_empty_double_border_three_nodes_ctrl_a_right_drag_edges_check_hash() {
    #[Window(events = GraphViewEvents<String>, internal: true)]
    struct InteractiveDoubleBorderWin {}

    impl InteractiveDoubleBorderWin {
        fn new() -> Self {
            let mut w = Self { base: window!("Test,d:f") };
            let mut gv: GraphView<String> = graphview!(
                "line-type: SingleThick, routing: Orthogonal, hie: false, hoe: false, arrows: false, arrange: None, d:f, flags:[ScrollBars,MultiSelect],lsm:2,tsm:1"
            );
            gv.set_graph(graphview::Graph::<String>::new(vec![], vec![]));
            w.add(gv);
            w
        }
    }

    impl GraphViewEvents<String> for InteractiveDoubleBorderWin {
        fn on_request_new_node(&mut self, handle: Handle<GraphView<String>>, p: Point) -> EventProcessStatus {
            if let Some(gv) = self.control_mut(handle) {
                gv.modify_graph(|g| {
                    let n = graphview::NodeBuilder::new(format!("N{}", g.nodes_count() + 1))
                        .position(p)
                        .size(Size::new(8, 1))
                        .border(LineType::Double)
                        .text_alignment(TextAlignment::Left)
                        .build();
                    let id = g.add_node(n);
                    g.set_current_node(id);
                });
            }
            EventProcessStatus::Processed
        }

        fn on_request_new_edge(&mut self, handle: Handle<GraphView<String>>, from: u32, to: u32) -> EventProcessStatus {
            if let Some(gv) = self.control_mut(handle) {
                gv.modify_graph(|g| {
                    let _ = g.add_edge(graphview::EdgeBuilder::new(from, to).directed(true).build());
                });
            }
            EventProcessStatus::Processed
        }
    }

    let script = "
        Paint.Enable(false)

        Paint('1. Empty graph')
        CheckHash(0xE3D0E179665B4428)
        Key.Modifier(Ctrl)
        Mouse.Click(12,6,left)
        Key.Modifier(None)
        Paint('2. First node (double border, Ctrl+click empty)')
        CheckHash(0x28CC91FC3D24C4D1)
        Key.Modifier(Ctrl)
        Mouse.Click(40,6,left)
        Key.Modifier(None)
        Paint('3. Second node')
        CheckHash(0x3C19D3EE7DE179A7)
        Key.Modifier(Ctrl)
        Mouse.Click(26,14,left)
        Key.Modifier(None)
        Paint('4. Third node')
        CheckHash(0x339294EE49A06638)

        Key.Pressed(Ctrl+A)
        Paint('5. Ctrl+A — all visible selected')
        CheckHash(0x18408BC9E55C5721)

        Mouse.Hold(12,6,right)
        Mouse.Move(40,6)
        Mouse.Release(40,6,right)
        Paint('6. Edge 0 -> 1')
        CheckHash(0xADDA35FE288D4D91)

        Mouse.Hold(40,6,right)
        Mouse.Move(26,14)
        Mouse.Release(26,14,right)
        Paint('7. Edge 1 -> 2')
        CheckHash(0xF876C73CD277592B)
    ";

    let mut a = App::debug(72, 22, script).build().unwrap();
    a.add_window(InteractiveDoubleBorderWin::new());
    a.run();
}

/// [`EditableNode`] getters match the geometry and style from [`NodeBuilder`] (including `resize` padding rules).
#[test]
fn editable_node_getters_match_node_builder() {
    use super::graph::EditableGraph;

    let top_left = Point::new(11, 3);
    let inner = Size::new(5, 2);
    let node = graphview::NodeBuilder::new("alpha")
        .position(top_left)
        .size(inner)
        .text_alignment(TextAlignment::Right)
        .build();
    // No border: `Node::resize` widens by 2 for side padding (see `node.rs`).
    let expected_size = Size::new(inner.width + 2, inner.height);
    let expected_bounds = Rect::with_point_and_size(top_left, expected_size);

    let mut graph = graphview::Graph::new(vec![node], vec![]);
    let mut editor = EditableGraph::new(&mut graph);
    let n = editor.node(0).expect("node 0");
    assert_eq!(*n.value(), "alpha");
    assert_eq!(n.position(), top_left);
    assert_eq!(n.size(), expected_size);
    assert_eq!(n.bounds(), expected_bounds);
    assert_eq!(n.text_alignment(), TextAlignment::Right);
    assert_eq!(n.text_attribute(), None);
    assert_eq!(n.border(), None);
}

/// [`EditableNode`] getters for border and [`CharAttribute`]; `value_mut` is exercised and read back via `value`.
#[test]
fn editable_node_getters_with_border_and_text_attribute() {
    use super::graph::EditableGraph;

    let top_left = Point::new(2, 5);
    let inner = Size::new(6, 2);
    let attr = charattr!("yellow,blue");
    let node = graphview::NodeBuilder::new("beta")
        .position(top_left)
        .size(inner)
        .border(LineType::Single)
        .text_alignment(TextAlignment::Center)
        .text_attribute(attr)
        .build();
    // With border: `Node::resize` adds 2 to width and height.
    let expected_size = Size::new(inner.width + 2, inner.height + 2);
    let expected_bounds = Rect::with_point_and_size(top_left, expected_size);

    let mut graph = graphview::Graph::new(vec![node], vec![]);
    let mut editor = EditableGraph::new(&mut graph);
    let mut n = editor.node(0).expect("node 0");
    assert_eq!(*n.value(), "beta");
    assert_eq!(n.position(), top_left);
    assert_eq!(n.size(), expected_size);
    assert_eq!(n.bounds(), expected_bounds);
    assert_eq!(n.text_alignment(), TextAlignment::Center);
    assert_eq!(n.text_attribute(), Some(attr));
    assert_eq!(n.border(), Some(LineType::Single));

    *n.value_mut() = "gamma";
    assert_eq!(*n.value(), "gamma");
}

/// `EditableEdge::attribute` / `set_attribute` and `line_type` / `set_line_type`.
#[test]
fn editable_edge_get_set_attribute_and_line_type() {
    use super::graph::EditableGraph;

    let mut graph = graphview::Graph::with_slices(&["A", "B"], &[(0, 1)], true);
    let attr_before = charattr!("white,red");
    let attr_after = charattr!("yellow,blue");

    let mut editor = EditableGraph::new(&mut graph);
    {
        let mut e = editor.edge(0).expect("edge 0");
        assert_eq!(e.from_node_id(), 0);
        assert_eq!(e.to_node_id(), 1);
        assert!(e.directed());
        assert_eq!(e.attribute(), None);
        assert_eq!(e.line_type(), None);

        e.set_attribute(attr_before);
        e.set_line_type(LineType::Double);
    }
    {
        let e = editor.edge(0).expect("edge 0");
        assert_eq!(e.attribute(), Some(attr_before));
        assert_eq!(e.line_type(), Some(LineType::Double));
    }
    {
        let mut e = editor.edge(0).expect("edge 0");
        e.set_attribute(attr_after);
        e.set_line_type(LineType::Ascii);
    }
    {
        let e = editor.edge(0).expect("edge 0");
        assert_eq!(e.attribute(), Some(attr_after));
        assert_eq!(e.line_type(), Some(LineType::Ascii));
    }
}

/// `EditableGraph::add_edge` appends an edge; `delete_edge` removes by index (same API used by the graph editor example via `GraphView::modify_graph`).
#[test]
fn editable_graph_add_edge_and_delete_edge() {
    use super::graph::EditableGraph;

    let mut graph = graphview::Graph::with_slices(&["A", "B", "C"], &[(0, 1)], true);
    let mut editor = EditableGraph::new(&mut graph);
    assert_eq!(editor.edges_count(), 1);

    assert!(
        editor.add_edge(
            graphview::EdgeBuilder::new(1, 2).directed(false).build(),
        )
    );
    assert_eq!(editor.edges_count(), 2);
    assert_eq!(editor.edge(1).unwrap().from_node_id(), 1);
    assert_eq!(editor.edge(1).unwrap().to_node_id(), 2);

    editor.delete_edge(0);
    assert_eq!(editor.edges_count(), 1);
    assert_eq!(editor.edge(0).unwrap().from_node_id(), 1);
    assert_eq!(editor.edge(0).unwrap().to_node_id(), 2);

    assert!(!editor.add_edge(graphview::EdgeBuilder::new(0, 99).build()));
    assert_eq!(editor.edges_count(), 1);
}

/// Exercises `EditableNode` setters: `set_position`, `set_size`, `set_text_alignment`, `clear_text_attribute`, `set_selected`, `clear_border`, `is_selected`.
#[test]
fn editable_node_set_position_size_alignment_clear_attrs_border_and_selected() {
    use super::graph::EditableGraph;

    let node = graphview::NodeBuilder::new("n")
        .position(Point::new(1, 1))
        .size(Size::new(10, 3))
        .border(LineType::Single)
        .text_alignment(TextAlignment::Center)
        .text_attribute(charattr!("white,black"))
        .build();

    let mut graph = graphview::Graph::new(vec![node], vec![]);
    graph.nodes[0].selected = false;
    graph.rendering_options.multiselect_ui = true;

    let mut editor = EditableGraph::new(&mut graph);
    let mut n = editor.node(0).expect("node 0");

    let pos_after = Point::new(7, 9);
    n.set_position(pos_after);
    assert_eq!(n.position(), pos_after);

    let size_inner = Size::new(14, 4);
    n.set_size(size_inner);
    assert!(n.size().width >= size_inner.width);
    assert!(n.size().height >= size_inner.height);

    n.set_text_alignment(TextAlignment::Left);
    assert_eq!(n.text_alignment(), TextAlignment::Left);

    assert!(n.text_attribute().is_some());
    n.clear_text_attribute();
    assert_eq!(n.text_attribute(), None);

    assert!(n.border().is_some());
    n.clear_border();
    assert_eq!(n.border(), None);

    assert!(!n.is_selected());
    n.set_selected(true);
    assert!(n.is_selected());
    n.set_selected(false);
    assert!(!n.is_selected());

    drop(editor);
    assert!(!graph.nodes[0].selected);
}

/// [`EditableGraph::delete_node`] removes incident edges and reindexes remaining edge endpoints.
#[test]
fn editable_graph_delete_nodes_updates_edges_and_labels() {
    use super::graph::EditableGraph;

    // A ──► B ──► D
    // └──► C
    let mut graph = graphview::Graph::with_slices(
        &["A", "B", "C", "D"],
        &[(0, 1), (0, 2), (1, 3)],
        true,
    );

    let mut editor = EditableGraph::new(&mut graph);
    assert_eq!(editor.nodes_count(), 4);
    assert_eq!(editor.edges_count(), 3);

    editor.delete_node(1);
    assert_eq!(editor.nodes_count(), 3);
    assert_eq!(editor.edges_count(), 1);
    assert_eq!(*editor.node(0).unwrap().value(), "A");
    assert_eq!(*editor.node(1).unwrap().value(), "C");
    assert_eq!(*editor.node(2).unwrap().value(), "D");
    let e0 = editor.edge(0).expect("remaining edge");
    assert_eq!(e0.from_node_id(), 0);
    assert_eq!(e0.to_node_id(), 1);
    assert!(e0.directed());

    // Remove D (index 2 after first deletion); the A→C edge must stay intact.
    editor.delete_node(2);
    assert_eq!(editor.nodes_count(), 2);
    assert_eq!(editor.edges_count(), 1);
    assert_eq!(*editor.node(0).unwrap().value(), "A");
    assert_eq!(*editor.node(1).unwrap().value(), "C");
    let e0 = editor.edge(0).expect("remaining edge");
    assert_eq!(e0.from_node_id(), 0);
    assert_eq!(e0.to_node_id(), 1);
}

mod process_key_multiselect_tests {
    use super::*;

    #[test]
    fn space_not_handled_when_multiselect_ui_off() {
        let mut graph = graphview::Graph::<&'static str>::with_slices(&["A"], &[], true);
        graph.rendering_options.multiselect_ui = false;
        let base = ControlBase::new(layout!("d:f"), true);
        let key = Key::new(KeyCode::Space, KeyModifier::None);
        assert!(!graph.process_key_events(key, &base));
    }

    #[test]
    fn space_not_handled_when_multiselect_on_but_graph_empty() {
        let mut graph: graphview::Graph<&'static str> = graphview::Graph::with_slices(&[], &[], true);
        graph.rendering_options.multiselect_ui = true;
        let base = ControlBase::new(layout!("d:f"), true);
        let key = Key::new(KeyCode::Space, KeyModifier::None);
        assert!(!graph.process_key_events(key, &base));
    }

    #[test]
    fn ctrl_a_not_handled_when_multiselect_ui_off() {
        let mut graph = graphview::Graph::<&'static str>::with_slices(&["A", "B"], &[], true);
        graph.rendering_options.multiselect_ui = false;
        let base = ControlBase::new(layout!("d:f"), true);
        let key = Key::new(KeyCode::A, KeyModifier::Ctrl);
        assert!(!graph.process_key_events(key, &base));
    }

    #[test]
    fn ctrl_a_not_handled_when_multiselect_on_but_graph_empty() {
        let mut graph: graphview::Graph<&'static str> = graphview::Graph::with_slices(&[], &[], true);
        graph.rendering_options.multiselect_ui = true;
        let base = ControlBase::new(layout!("d:f"), true);
        let key = Key::new(KeyCode::A, KeyModifier::Ctrl);
        assert!(!graph.process_key_events(key, &base));
    }

    #[test]
    fn ctrl_a_selects_all_visible_when_not_all_visible_selected() {
        let mut graph = graphview::Graph::<&'static str>::with_slices(&["A", "B", "C"], &[], true);
        graph.rendering_options.multiselect_ui = true;
        for n in &mut graph.nodes {
            n.selected = false;
        }
        assert_eq!(graph.apply_ctrl_a_visible_selection_toggle(), Some(0));
        assert!(graph.nodes[0].selected);
        assert!(graph.nodes[1].selected);
        assert!(graph.nodes[2].selected);
    }

    #[test]
    fn ctrl_a_clears_visible_selection_when_all_visible_were_selected() {
        let mut graph = graphview::Graph::<&'static str>::with_slices(&["A", "B"], &[], true);
        graph.rendering_options.multiselect_ui = true;
        graph.nodes[0].selected = true;
        graph.nodes[1].selected = true;

        assert_eq!(graph.apply_ctrl_a_visible_selection_toggle(), Some(0));

        assert!(!graph.nodes[0].selected);
        assert!(!graph.nodes[1].selected);
    }

    #[test]
    fn ctrl_a_only_toggles_non_filtered_nodes() {
        let mut graph = graphview::Graph::<&'static str>::with_slices(&["A", "B", "C"], &[], true);
        graph.rendering_options.multiselect_ui = true;
        graph.nodes[1].filtered = true;
        graph.nodes[0].selected = false;
        graph.nodes[1].selected = false;
        graph.nodes[2].selected = false;

        assert_eq!(graph.apply_ctrl_a_visible_selection_toggle(), Some(0));

        assert!(graph.nodes[0].selected);
        assert!(!graph.nodes[1].selected);
        assert!(graph.nodes[2].selected);

        assert_eq!(graph.apply_ctrl_a_visible_selection_toggle(), Some(0));

        assert!(!graph.nodes[0].selected);
        assert!(!graph.nodes[1].selected);
        assert!(!graph.nodes[2].selected);
    }

    #[test]
    fn ctrl_a_toggle_returns_none_when_all_nodes_filtered() {
        let mut graph = graphview::Graph::<&'static str>::with_slices(&["A", "B"], &[], true);
        graph.rendering_options.multiselect_ui = true;
        graph.nodes[0].filtered = true;
        graph.nodes[1].filtered = true;

        assert_eq!(graph.apply_ctrl_a_visible_selection_toggle(), None);
    }

    #[test]
    fn ctrl_a_toggle_returns_none_when_multiselect_off_or_empty() {
        let mut graph = graphview::Graph::<&'static str>::with_slices(&["X"], &[], true);
        graph.rendering_options.multiselect_ui = false;
        assert_eq!(graph.apply_ctrl_a_visible_selection_toggle(), None);

        let mut empty: graphview::Graph<&'static str> = graphview::Graph::with_slices(&[], &[], true);
        empty.rendering_options.multiselect_ui = true;
        assert_eq!(empty.apply_ctrl_a_visible_selection_toggle(), None);
    }
}

mod editable_graph_delete_tests {
    use crate::ui::graphview::graph::EditableGraph;
    use super::*;

    #[test]
    fn delete_last_node_resets_current_index() {
        let mut graph = graphview::Graph::<&'static str>::with_slices(&["A"], &[], true);
        graph.current_node = 0;
        graph.rendering_options.multiselect_ui = true;
        graph.nodes[0].selected = true;

        let mut editor = EditableGraph::new(&mut graph);
        editor.delete_node(0);

        assert_eq!(editor.nodes_count(), 0);
        assert_eq!(editor.current_node(), 0);
    }

    #[test]
    fn delete_node_moves_primary_to_remaining_selected() {
        let mut graph = graphview::Graph::<&'static str>::with_slices(&["A", "B", "C"], &[], true);
        graph.rendering_options.multiselect_ui = true;
        graph.nodes[0].selected = true;
        graph.nodes[2].selected = true;
        graph.current_node = 2;

        let mut editor = EditableGraph::new(&mut graph);
        editor.delete_node(2);

        assert_eq!(editor.nodes_count(), 2);
        assert_eq!(editor.current_node(), 0);
        assert_eq!(*editor.node(0).unwrap().value(), "A");
        assert_eq!(*editor.node(1).unwrap().value(), "B");
        drop(editor);
        assert!(graph.nodes[0].selected);
        assert!(!graph.nodes[1].selected);
    }
}
