use crossterm::event::{
    Event, KeyCode as K, KeyEvent, KeyModifiers as M, MouseButton as MB, MouseEvent,
    MouseEventKind as MK,
};
use ratatui::{backend::TestBackend, layout::Rect, Terminal};
use std::{
    fs,
    path::PathBuf,
    sync::atomic::{AtomicUsize, Ordering},
};
use tb_frontend::forms::{self, ObjectClass as C};
use tb_ide::{
    app::{App, DialogKind, Field, Hit, Mode, WindowKind},
    commands::Command,
    designer::{self, DesignId},
    documents::{Destination, Project},
};
use tb_ui::{forms::PropertyValue as V, frm};
struct Temp(PathBuf);
impl Temp {
    fn new() -> Self {
        static N: AtomicUsize = AtomicUsize::new(0);
        let p = std::env::temp_dir().join(format!(
            "tb-designer-{}-{}",
            std::process::id(),
            N.fetch_add(1, Ordering::Relaxed)
        ));
        fs::create_dir_all(&p).unwrap();
        Self(p.canonicalize().unwrap())
    }
    fn app(&self) -> App {
        let mut a = App::new(&self.0, self.0.join("options"), (100, 30)).unwrap();
        a.options.syntax_checking = false;
        a.execute(Command::NewForm);
        assert_eq!(a.mode, Mode::Designer);
        a
    }
}
impl Drop for Temp {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.0);
    }
}
fn key(a: &mut App, k: K, m: M) {
    a.handle(Event::Key(KeyEvent::new(k, m)));
}
fn plain(a: &mut App, k: K) {
    key(a, k, M::NONE);
}
fn root(a: &mut App) -> DesignId {
    a.design_objects().unwrap()[0].0
}
fn node(a: &mut App, id: DesignId) -> frm::FormNode {
    a.design_objects()
        .unwrap()
        .into_iter()
        .find(|(i, _)| *i == id)
        .unwrap()
        .1
}
fn number(a: &mut App, id: DesignId, p: &str) -> i32 {
    match designer::value(&node(a, id), p).unwrap() {
        V::Integer(n) => n,
        _ => panic!(),
    }
}
fn draw(a: &mut App) -> String {
    let mut t = Terminal::new(TestBackend::new(100, 30)).unwrap();
    t.draw(|f| a.render(f)).unwrap();
    t.backend()
        .buffer()
        .content
        .iter()
        .map(|c| c.symbol())
        .collect()
}
fn mouse(a: &mut App, kind: MK, x: u16, y: u16, m: M) {
    a.handle(Event::Mouse(MouseEvent {
        kind,
        column: x,
        row: y,
        modifiers: m,
    }));
}
fn click(a: &mut App, x: u16, y: u16) {
    mouse(a, MK::Down(MB::Left), x, y, M::NONE);
    mouse(a, MK::Up(MB::Left), x, y, M::NONE);
}
fn property(a: &mut App, p: &str, v: &str) {
    a.design_property(p, v).unwrap();
}
#[test]
fn all_metadata_tools_persist_and_preview_matches_runtime_without_events() {
    let t = Temp::new();
    let mut a = t.app();
    let root = root(&mut a);
    property(&mut a, "WIDTH", "70");
    property(&mut a, "HEIGHT", "24");
    for (i, (_, class, style)) in designer::tools().iter().enumerate() {
        a.design_place(
            *class,
            *style,
            root,
            Rect::new(1 + (i % 8) as u16 * 8, 1 + (i / 8) as u16 * 6, 6, 3),
        )
        .unwrap();
    }
    let timer = a
        .design_objects()
        .unwrap()
        .into_iter()
        .find(|(_, n)| n.class == C::Timer)
        .unwrap()
        .0;
    a.design_select(timer, false).unwrap();
    property(&mut a, "INTERVAL", "1");
    let doc = a.design_document().unwrap();
    a.project.replace_text(doc,0..0,"SUB Form_Load\nPRINT \"NO PREVIEW EXECUTION\"\nEND SUB\nSUB Timer1_Timer\nPRINT \"NO TIMER\"\nEND SUB\n").unwrap();
    let form = a.design_form().unwrap().clone();
    let (mut model, screen) = designer::preview(&form, (100, 30)).unwrap();
    assert!(model.events.is_empty());
    assert!(model.next_deadline().is_none());
    model.timers(10_000);
    assert!(model.next_event().is_none());
    assert!(a.session.vm.is_none());
    let mut runtime = tb_ui::forms::FormsModel::new(form.catalog().objects, 100, 30);
    form.apply(&mut runtime).unwrap();
    runtime.show(0, false).unwrap();
    let mut expected = tb_runtime::screen::TextScreen::with_size(100, 30);
    runtime.render(&mut expected);
    for y in 1..=30 {
        for x in 1..=100 {
            assert_eq!(screen.cell(y, x), expected.cell(y, x));
        }
    }
    let path = t.0.join("designed.frm");
    a.project
        .save_file(doc, Some(&Destination::new(&path)))
        .unwrap();
    let reopened = Project::open(&path, vec![]).unwrap();
    let tb_vm::project_io::Content::Form(f) =
        reopened.document(reopened.members()[0]).unwrap().content()
    else {
        panic!()
    };
    assert_equivalent(&f.root, &form.root);
    assert_eq!(f.root.children.len(), designer::tools().len());
    tb_vm::project_io::load_program(&path).unwrap();
    let listed = a
        .menus()
        .into_iter()
        .find(|m| m.title == "Tools")
        .unwrap()
        .items;
    assert_eq!(listed.len(), designer::tools().len());
}
#[test]
fn container_multiselect_keyboard_drag_resize_and_undo() {
    let t = Temp::new();
    let mut a = t.app();
    let root = root(&mut a);
    let frame = a
        .design_place(C::Frame, 0, root, Rect::new(3, 2, 28, 10))
        .unwrap();
    let b1 = a
        .design_place(C::CommandButton, 0, frame, Rect::new(2, 2, 8, 2))
        .unwrap();
    let b2 = a
        .design_place(C::TextBox, 0, frame, Rect::new(12, 2, 8, 2))
        .unwrap();
    a.design_select(b1, false).unwrap();
    a.design_select(b2, true).unwrap();
    key(&mut a, K::Right, M::CONTROL);
    assert_eq!(number(&mut a, b1, "LEFT"), 7);
    assert_eq!(number(&mut a, b2, "LEFT"), 17);
    assert_eq!(number(&mut a, frame, "LEFT"), 3);
    a.execute(Command::Undo);
    assert_eq!(number(&mut a, b1, "LEFT"), 2);
    assert_eq!(number(&mut a, b2, "LEFT"), 12);
    a.design_select(b1, false).unwrap();
    key(&mut a, K::Right, M::SHIFT);
    assert_eq!(number(&mut a, b1, "WIDTH"), 9);
    a.execute(Command::Undo);
    draw(&mut a);
    let r = a
        .designer
        .objects
        .iter()
        .find(|(id, _)| *id == b1)
        .unwrap()
        .1;
    assert_eq!(r.x, a.designer.viewport.x + 3 + 2);
    assert_eq!(r.y, a.designer.viewport.y + 2 + 2);
    mouse(&mut a, MK::Down(MB::Left), r.x + 2, r.y, M::NONE);
    mouse(&mut a, MK::Drag(MB::Left), r.x + 3, r.y + 1, M::NONE);
    mouse(&mut a, MK::Up(MB::Left), r.x + 3, r.y + 1, M::NONE);
    assert_eq!(number(&mut a, b1, "LEFT"), 3);
    assert_eq!(number(&mut a, b1, "TOP"), 3);
    draw(&mut a);
    let r = a
        .designer
        .objects
        .iter()
        .find(|(id, _)| *id == b1)
        .unwrap()
        .1;
    mouse(
        &mut a,
        MK::Down(MB::Left),
        r.right() - 1,
        r.bottom() - 1,
        M::NONE,
    );
    mouse(&mut a, MK::Up(MB::Left), r.right(), r.bottom(), M::NONE);
    assert_eq!(number(&mut a, b1, "WIDTH"), 9);
    assert_eq!(number(&mut a, b1, "HEIGHT"), 3);
    let before = a.design_form().unwrap().clone();
    assert!(a.design_geometry(-100, 0, false).is_err());
    assert_eq!(*a.design_form().unwrap(), before);
    a.design_select(b1, false).unwrap();
    plain(&mut a, K::Tab);
    assert_eq!(
        a.designer.selections[&a.design_document().unwrap()],
        vec![b2]
    );
    plain(&mut a, K::BackTab);
    assert_eq!(
        a.designer.selections[&a.design_document().unwrap()],
        vec![b1]
    );
    draw(&mut a);
    let r = a
        .designer
        .objects
        .iter()
        .find(|(id, _)| *id == b2)
        .unwrap()
        .1;
    mouse(&mut a, MK::Down(MB::Left), r.x + 1, r.y, M::CONTROL);
    mouse(&mut a, MK::Up(MB::Left), r.x + 1, r.y, M::CONTROL);
    assert_eq!(
        a.designer.selections[&a.design_document().unwrap()].len(),
        2
    );
}
#[test]
fn property_bar_types_readonly_and_no_status_line() {
    let t = Temp::new();
    let mut a = t.app();
    let root = root(&mut a);
    let b = a
        .design_place(C::CommandButton, 0, root, Rect::new(2, 3, 8, 2))
        .unwrap();
    let before = a.design_form().unwrap().clone();
    for (p, v) in [
        ("HEIGHT", "0"),
        ("WIDTH", "x"),
        ("BACKCOLOR", "16"),
        ("ENABLED", "yes"),
        ("PARENT", "anything"),
    ] {
        let e = a.design_property(p, v).unwrap_err();
        assert!(format!("{e:#}").contains(p));
        assert_eq!(*a.design_form().unwrap(), before);
    }
    a.designer.property = forms::properties(C::CommandButton)
        .iter()
        .position(|p| p.name == "CAPTION")
        .unwrap();
    plain(&mut a, K::F(2));
    assert!(a.value_focus);
    key(&mut a, K::Char('a'), M::CONTROL);
    for c in "Hallo".chars() {
        plain(&mut a, K::Char(c));
    }
    plain(&mut a, K::Enter);
    assert_eq!(
        designer::value(&node(&mut a, b), "CAPTION"),
        Some(V::String("Hallo".into()))
    );
    let screen = draw(&mut a);
    assert!(screen.contains("2,3"));
    assert!(screen.contains("8×2"));
    assert!(!screen.contains("Shift+F1=Help"));
    plain(&mut a, K::F(10));
    assert!(!a.properties && a.menu.is_some());
    plain(&mut a, K::F(10));
    assert!(a.properties && a.menu.is_none());
    let spin = a
        .design_place(C::Spin, 0, root, Rect::new(15, 3, 1, 2))
        .unwrap();
    assert!(a.design_property("WIDTH", "4").is_err());
    property(&mut a, "STYLE", "1");
    assert_eq!(number(&mut a, spin, "WIDTH"), 2);
    assert_eq!(number(&mut a, spin, "HEIGHT"), 1);
}
#[test]
fn clipboard_children_arrays_and_delete_preserve_code() {
    let t = Temp::new();
    let mut a = t.app();
    let root = root(&mut a);
    let frame = a
        .design_place(C::Frame, 0, root, Rect::new(1, 1, 20, 10))
        .unwrap();
    let b = a
        .design_place(C::CommandButton, 0, frame, Rect::new(1, 1, 8, 2))
        .unwrap();
    property(&mut a, "INDEX", "0");
    a.design_event(b, "CLICK").unwrap();
    a.design_event(b, "CLICK").unwrap();
    key(&mut a, K::F(12), M::SHIFT);
    let code = a.design_form().unwrap().code.clone();
    a.design_select(frame, false).unwrap();
    a.execute(Command::Copy);
    a.design_select(root, false).unwrap();
    a.execute(Command::Paste);
    assert!(a.dialog.is_none(), "{:?}", a.dialog);
    let f = a.design_form().unwrap();
    assert_eq!(f.root.children.len(), 2);
    let copied = &f.root.children[1];
    assert_eq!(copied.children.len(), 1);
    assert_ne!(copied.children[0].name, f.root.children[0].children[0].name);
    assert_eq!(designer::node_key(&copied.children[0]).1, Some(0));
    a.execute(Command::Undo);
    assert_eq!(a.design_form().unwrap().root.children.len(), 1);
    a.design_select(frame, false).unwrap();
    a.execute(Command::Cut);
    assert_eq!(a.design_form().unwrap().code, code);
    assert!(a.message.contains("ungebunden"));
    plain(&mut a, K::Esc);
    a.execute(Command::Undo);
    assert_eq!(a.design_form().unwrap().root.children.len(), 1);
    assert_eq!(node(&mut a, b).class, C::CommandButton);
}
#[test]
fn bound_rename_across_documents_is_atomic_and_undo_restores_all() {
    let t = Temp::new();
    let mut a = t.app();
    let root = root(&mut a);
    let b = a
        .design_place(C::CommandButton, 0, root, Rect::new(2, 2, 8, 2))
        .unwrap();
    let name = node(&mut a, b).name;
    let form = a.design_form().unwrap().root.name.clone();
    let doc = a.design_document().unwrap();
    let code=format!("SUB {name}_Click\n{name}.Caption = \"{name} unchanged\"\n' {name}.Caption unchanged\nEND SUB\n");
    a.project.replace_text(doc, 0..0, &code).unwrap();
    let other = a.project.new_module("Other").unwrap();
    let external = format!("PRINT {form}!{name}.Caption\n");
    a.project.replace_text(other, 0..0, &external).unwrap();
    a.design_rename("GoButton").unwrap();
    assert_eq!(node(&mut a, b).name, "GoButton");
    assert!(a
        .project
        .document(doc)
        .unwrap()
        .code()
        .contains("SUB GoButton_CLICK"));
    assert!(a
        .project
        .document(doc)
        .unwrap()
        .code()
        .contains("GoButton.Caption"));
    assert!(a
        .project
        .document(doc)
        .unwrap()
        .code()
        .contains(&format!("\"{name} unchanged\"")));
    assert!(a
        .project
        .document(other)
        .unwrap()
        .code()
        .contains("!GoButton.Caption"));
    a.project.undo(other).unwrap();
    assert_eq!(a.project.document(other).unwrap().code(), external);
    assert_eq!(a.project.document(doc).unwrap().code(), code);
    assert_eq!(node(&mut a, b).name, name);
    let conflict = a
        .design_place(C::TextBox, 0, root, Rect::new(14, 2, 8, 2))
        .unwrap();
    let conflict_name = node(&mut a, conflict).name;
    a.design_select(b, false).unwrap();
    let before = a.design_form().unwrap().clone();
    assert!(a.design_rename(&conflict_name).is_err());
    assert_eq!(*a.design_form().unwrap(), before);
    a.project.replace_text(other, 0..0, "IF \n").unwrap();
    assert!(a.design_rename("Unsafe").is_err());
    assert_eq!(*a.design_form().unwrap(), before);
}
#[test]
fn event_array_signature_twice_and_shared_form_identity() {
    let t = Temp::new();
    let mut a = t.app();
    let root = root(&mut a);
    let b = a
        .design_place(C::TextBox, 0, root, Rect::new(2, 2, 8, 2))
        .unwrap();
    property(&mut a, "INDEX", "0");
    let doc = a.design_document().unwrap();
    let structure = a.design_form().unwrap().root.clone();
    a.design_event(b, "DRAGOVER").unwrap();
    assert_eq!(a.mode, Mode::Environment);
    let code = a.project.document(doc).unwrap().code().to_owned();
    assert!(code.contains(
        "Index AS INTEGER, SOURCE AS CONTROL, X AS SINGLE, Y AS SINGLE, STATE AS INTEGER"
    ));
    a.compile_current().unwrap();
    let view = if let WindowKind::Code(v) = a.active_window().unwrap().kind {
        v
    } else {
        panic!()
    };
    let cursor = a.project.view(view).unwrap().cursor;
    key(&mut a, K::F(12), M::SHIFT);
    assert_eq!(a.mode, Mode::Designer);
    assert_eq!(a.active_document(), Some(doc));
    assert_eq!(a.design_form().unwrap().root, structure);
    assert_eq!(a.designer.selections[&doc], vec![b]);
    a.design_event(b, "DRAGOVER").unwrap();
    assert_eq!(a.project.document(doc).unwrap().code(), code);
    assert_eq!(a.project.view(view).unwrap().cursor, cursor);
    key(&mut a, K::F(12), M::SHIFT);
    plain(&mut a, K::F(12));
    assert!(matches!(
        a.dialog.as_ref().unwrap().kind,
        DialogKind::DesignObject(_)
    ));
    plain(&mut a, K::Enter);
    assert!(matches!(
        a.dialog.as_ref().unwrap().kind,
        DialogKind::DesignEvent(_)
    ));
    plain(&mut a, K::Enter);
    assert_eq!(a.mode, Mode::Environment);
    assert!(a.dialog.is_none());
}
#[test]
fn menu_hierarchy_arrays_shortcuts_and_palette_use_events() {
    let t = Temp::new();
    let mut a = t.app();
    let root = root(&mut a);
    let title = a.design_place(C::Menu, 0, root, Rect::default()).unwrap();
    property(&mut a, "CAPTION", "&File");
    let sub = a.design_place(C::Menu, 0, title, Rect::default()).unwrap();
    property(&mut a, "CAPTION", "&Open");
    property(&mut a, "INDEX", "0");
    property(&mut a, "SHORTCUT", "F3");
    let second = a.design_place(C::Menu, 0, title, Rect::default()).unwrap();
    property(&mut a, "CAPTION", "-");
    property(&mut a, "SEPARATOR", "true");
    assert!(a.design_property("CHECKED", "true").is_err());
    a.design_select(sub, false).unwrap();
    a.design_menu_move("down").unwrap();
    a.design_menu_move("up").unwrap();
    assert!(a.design_property("SHORTCUT", "F11").is_err());
    a.execute(Command::MenuDesign);
    draw(&mut a);
    assert_eq!(a.active_window().unwrap().kind, WindowKind::MenuDesign);
    plain(&mut a, K::Enter);
    assert!(matches!(
        a.dialog.as_ref().unwrap().kind,
        DialogKind::DesignMenu(_)
    ));
    a.dialog.as_mut().unwrap().fields[2] = Field::text("Tag", "kept");
    plain(&mut a, K::Enter);
    assert!(a.dialog.is_none(), "{:?}", a.dialog);
    a.design_select(root, false).unwrap();
    a.execute(Command::Palette);
    plain(&mut a, K::Enter);
    a.dialog.as_mut().unwrap().fields[0] =
        Field::choice("ForeColor", (0..16).map(|n| n.to_string()).collect(), 4);
    a.dialog.as_mut().unwrap().fields[1] =
        Field::choice("BackColor", (0..16).map(|n| n.to_string()).collect(), 1);
    a.dialog.as_mut().unwrap().fields[2] = Field::toggle("Form", true);
    plain(&mut a, K::Enter);
    assert_eq!(number(&mut a, root, "FORECOLOR"), 4);
    assert_eq!(number(&mut a, root, "BACKCOLOR"), 1);
    let path = t.0.join("menu.frm");
    let doc = a.design_document().unwrap();
    a.project
        .save_file(doc, Some(&Destination::new(&path)))
        .unwrap();
    let reopened = frm::read_text("menu", &fs::read_to_string(&path).unwrap()).unwrap();
    assert_equivalent(&reopened.root, &a.design_form().unwrap().root);
    tb_vm::project_io::load_program(&path).unwrap();
    assert_eq!(node(&mut a, second).name, "Menu3");
}
#[test]
fn toolbox_double_click_tool_drag_grid_and_window_form_selection() {
    let t = Temp::new();
    let mut a = t.app();
    let root = root(&mut a);
    a.execute(Command::Toolbox);
    draw(&mut a);
    let r = a
        .hits
        .iter()
        .find_map(|(r, h)| matches!(h, Hit::DesignTool(2)).then_some(*r))
        .unwrap();
    click(&mut a, r.x + 1, r.y);
    click(&mut a, r.x + 1, r.y);
    assert_eq!(a.design_form().unwrap().root.children.len(), 1);
    a.execute(Command::Tool("TextBox"));
    draw(&mut a);
    let r = a
        .designer
        .objects
        .iter()
        .find(|(i, _)| *i == root)
        .unwrap()
        .1;
    mouse(&mut a, MK::Down(MB::Left), r.x + 15, r.y + 3, M::NONE);
    mouse(&mut a, MK::Drag(MB::Left), r.x + 22, r.y + 4, M::NONE);
    mouse(&mut a, MK::Up(MB::Left), r.x + 22, r.y + 4, M::NONE);
    assert!(a.dialog.is_none(), "{:?}", a.dialog);
    let textbox = a.design_form().unwrap().root.children.last().unwrap();
    assert_eq!(textbox.class, C::TextBox);
    assert_eq!(designer::value(textbox, "LEFT"), Some(V::Integer(15)));
    assert_eq!(designer::value(textbox, "WIDTH"), Some(V::Integer(8)));
    a.execute(Command::Grid);
    assert!(a.designer.grid);
    assert!(draw(&mut a).contains('·'));
    a.execute(Command::Grid);
    assert!(!a.designer.grid);
    let first = a.design_document().unwrap();
    a.execute(Command::NewForm);
    let second = a.design_document().unwrap();
    assert_ne!(first, second);
    let command=a.window_commands().into_iter().find(|(_,c)|matches!(c,Command::FocusWindow(id) if a.windows.iter().any(|w|w.id==*id&&matches!(w.kind,WindowKind::Code(v) if a.project.view(v).unwrap().document()==first)))).unwrap().1;
    a.execute(command);
    assert_eq!(a.design_document().unwrap(), first);
}
#[test]
fn binary_designer_import_requires_text_target_and_compiles() {
    let t = Temp::new();
    let bytes: Vec<u8> = include_str!("../../tb-ui/tests/data/new.frm.hex")
        .split_whitespace()
        .map(|s| u8::from_str_radix(s, 16).unwrap())
        .collect();
    let source = t.0.join("binary.frm");
    fs::write(&source, &bytes).unwrap();
    let mut a = App::new(&t.0, t.0.join("options"), (100, 30)).unwrap();
    a.load_initial_project(source.clone()).unwrap();
    a.execute(Command::Form);
    plain(&mut a, K::Enter);
    let root = root(&mut a);
    a.design_select(root, false).unwrap();
    property(&mut a, "CAPTION", "Imported and edited");
    let doc = a.design_document().unwrap();
    assert!(a.project.save_file(doc, None).is_err());
    let path = t.0.join("text.frm");
    a.project
        .save_file(doc, Some(&Destination::new(&path)))
        .unwrap();
    assert_eq!(fs::read(&source).unwrap(), bytes);
    tb_vm::project_io::load_program(&path).unwrap();
}

fn assert_equivalent(a: &frm::FormNode, b: &frm::FormNode) {
    assert_eq!(
        (a.class, &a.name, designer::node_key(a)),
        (b.class, &b.name, designer::node_key(b))
    );
    for p in forms::properties(a.class) {
        assert_eq!(
            designer::value(a, p.name),
            designer::value(b, p.name),
            "{}.{}",
            a.name,
            p.name
        );
    }
    assert_eq!(a.children.len(), b.children.len());
    for (a, b) in a.children.iter().zip(&b.children) {
        assert_equivalent(a, b);
    }
}

#[test]
fn rename_preserves_shadowed_names_and_handles_form_aliases_and_include_calls() {
    let t = Temp::new();
    let mut a = t.app();
    let r = root(&mut a);
    let b = a
        .design_place(C::CommandButton, 0, r, Rect::new(2, 2, 8, 2))
        .unwrap();
    let doc = a.design_document().unwrap();
    let name = node(&mut a, b).name;
    let form = node(&mut a, r).name;
    let code=format!("SUB {name}_Click\nDIM {name}_Click AS INTEGER\n{name}_Click = 7\nPRINT {name}_Click\nEND SUB\nSUB {form}_Load\n{name}.Caption=\"Hi\"\nEND SUB\n");
    a.project.replace_text(doc, 0..0, &code).unwrap();
    // In eine andere Datei gebundene Aufrufe müssen mit umbenannt werden.
    let other = a.project.new_module("Calls").unwrap();
    let call = format!("DECLARE SUB {name}_Click ()\nCALL {name}_Click\n");
    a.project.replace_text(other, 0..0, &call).unwrap();
    a.design_rename("Launch").unwrap();
    let changed = a.project.document(doc).unwrap().code();
    assert!(changed.contains(&format!("DIM {name}_Click AS INTEGER")));
    assert!(changed.contains(&format!("PRINT {name}_Click")));
    assert!(changed.contains("SUB Launch_CLICK"));
    assert!(a
        .project
        .document(other)
        .unwrap()
        .code()
        .contains("CALL Launch_CLICK"));
    a.design_select(r, false).unwrap();
    a.design_event(r, "LOAD").unwrap();
    assert!(!a
        .project
        .document(doc)
        .unwrap()
        .code()
        .contains("SUB Form_LOAD"));
    key(&mut a, K::F(12), M::SHIFT);
    a.design_select(r, false).unwrap();
    a.design_rename("MainForm").unwrap();
    assert_eq!(node(&mut a, r).name, "MainForm");
    assert!(a
        .project
        .document(doc)
        .unwrap()
        .code()
        .contains("SUB MainForm_LOAD"));
}
#[test]
fn existing_default_typed_event_and_all_metadata_signatures_compile() {
    let t = Temp::new();
    let mut a = t.app();
    let r = root(&mut a);
    let b = a
        .design_place(C::CommandButton, 0, r, Rect::new(2, 2, 8, 2))
        .unwrap();
    property(&mut a, "INDEX", "0");
    let name = node(&mut a, b).name;
    let doc = a.design_document().unwrap();
    let code = format!("DEFINT I,K,S\nSUB {name}_KeyDown (Index, KeyCode, Shift)\nEND SUB\n");
    a.project.replace_text(doc, 0..0, &code).unwrap();
    a.design_event(b, "KEYDOWN").unwrap();
    assert_eq!(a.project.document(doc).unwrap().code(), code);
    key(&mut a, K::F(12), M::SHIFT);
    for event in forms::events(C::CommandButton) {
        a.design_event(b, event).unwrap();
        a.compile_current().unwrap();
        key(&mut a, K::F(12), M::SHIFT);
    }
}
#[test]
fn all_eight_handles_resize_transactionally_and_preserve_identity_after_structure_edits() {
    let t = Temp::new();
    let mut a = t.app();
    let r = root(&mut a);
    let b = a
        .design_place(C::TextBox, 0, r, Rect::new(8, 5, 10, 5))
        .unwrap();
    for (horizontal, vertical) in [
        (-1, -1),
        (0, -1),
        (1, -1),
        (-1, 0),
        (1, 0),
        (-1, 1),
        (0, 1),
        (1, 1),
    ] {
        a.design_select(b, false).unwrap();
        draw(&mut a);
        let rect = a
            .designer
            .objects
            .iter()
            .find(|(id, _)| *id == b)
            .unwrap()
            .1;
        let x = match horizontal {
            -1 => rect.x,
            0 => rect.x + rect.width / 2,
            _ => rect.right() - 1,
        };
        let y = match vertical {
            -1 => rect.y,
            0 => rect.y + rect.height / 2,
            _ => rect.bottom() - 1,
        };
        let before = a.design_form().unwrap().clone();
        mouse(&mut a, MK::Down(MB::Left), x, y, M::NONE);
        mouse(&mut a, MK::Up(MB::Left), x + 1, y + 1, M::NONE);
        assert!(a.dialog.is_none(), "{:?}", a.dialog);
        assert_eq!(number(&mut a, b, "WIDTH"), 10 + horizontal);
        assert_eq!(number(&mut a, b, "HEIGHT"), 5 + vertical);
        assert_eq!(number(&mut a, b, "LEFT"), 8 + i32::from(horizontal < 0));
        assert_eq!(number(&mut a, b, "TOP"), 5 + i32::from(vertical < 0));
        a.execute(Command::Undo);
        assert_eq!(*a.design_form().unwrap(), before);
    }
    let old_id = b;
    a.design_select(b, false).unwrap();
    property(&mut a, "INDEX", "0");
    assert_eq!(node(&mut a, old_id).class, C::TextBox);
    a.execute(Command::Undo);
    assert_eq!(designer::node_key(&node(&mut a, old_id)).1, None);
    a.design_select(b, false).unwrap();
    a.execute(Command::Clear);
    plain(&mut a, K::Esc);
    let replacement = a
        .design_place(C::TextBox, 0, r, Rect::new(1, 1, 5, 2))
        .unwrap();
    assert_ne!(replacement, b);
    a.execute(Command::Undo);
    a.execute(Command::Undo);
    assert_eq!(node(&mut a, b).class, C::TextBox);
}
#[test]
fn menu_indent_outdent_rename_and_field_changes_have_one_undo() {
    let t = Temp::new();
    let mut a = t.app();
    let r = root(&mut a);
    let title = a.design_place(C::Menu, 0, r, Rect::default()).unwrap();
    let child = a.design_place(C::Menu, 0, title, Rect::default()).unwrap();
    let sibling = a.design_place(C::Menu, 0, title, Rect::default()).unwrap();
    a.design_menu_move("in").unwrap();
    assert_eq!(node(&mut a, child).children.len(), 1);
    a.design_menu_move("out").unwrap();
    assert_eq!(node(&mut a, title).children.len(), 2);
    let before = a.design_form().unwrap().clone();
    a.execute(Command::MenuDesign);
    plain(&mut a, K::Enter);
    let d = a.dialog.as_mut().unwrap();
    d.fields[0] = Field::text("CtlName", "SaveMenu");
    d.fields[1] = Field::text("Caption", "&Save");
    d.fields[2] = Field::text("Tag", "saved");
    d.fields[3] = Field::text("Index", "0");
    plain(&mut a, K::Enter);
    assert!(a.dialog.is_none(), "{:?}", a.dialog);
    assert_eq!(node(&mut a, sibling).name, "SaveMenu");
    a.execute(Command::Undo);
    assert_eq!(*a.design_form().unwrap(), before);
    assert_eq!(node(&mut a, sibling).name, "Menu3");
}

#[test]
fn property_menu_buttons_palette_drag_and_modal_mouse_are_connected() {
    let t = Temp::new();
    let mut a = t.app();
    let r = root(&mut a);
    let before = a.design_form().unwrap().code.clone();
    plain(&mut a, K::Char('x'));
    assert_eq!(a.design_form().unwrap().code, before);
    draw(&mut a);
    click(&mut a, 1, 0);
    assert!(matches!(
        a.dialog.as_ref().unwrap().kind,
        DialogKind::DesignProperty
    ));
    plain(&mut a, K::Enter);
    assert!(a.value_focus);
    plain(&mut a, K::Esc);
    a.execute(Command::Palette);
    draw(&mut a);
    let swatch = a
        .hits
        .iter()
        .find_map(|(r, h)| matches!(h, Hit::DesignPaint("BACKCOLOR", 5)).then_some(*r))
        .unwrap();
    let form = a
        .designer
        .objects
        .iter()
        .find(|(id, _)| *id == r)
        .unwrap()
        .1;
    mouse(&mut a, MK::Down(MB::Left), swatch.x, swatch.y, M::NONE);
    mouse(
        &mut a,
        MK::Drag(MB::Left),
        form.right() - 3,
        form.bottom() - 3,
        M::NONE,
    );
    mouse(
        &mut a,
        MK::Up(MB::Left),
        form.right() - 3,
        form.bottom() - 3,
        M::NONE,
    );
    assert_eq!(number(&mut a, r, "BACKCOLOR"), 5);
    a.execute(Command::MenuDesign);
    draw(&mut a);
    let insert = a
        .hits
        .iter()
        .find_map(|(r, h)| matches!(h, Hit::DesignAction("insert")).then_some(*r))
        .unwrap();
    click(&mut a, insert.x, insert.y);
    assert_eq!(a.design_form().unwrap().root.children.len(), 1);
    draw(&mut a);
    let edit = a
        .hits
        .iter()
        .find_map(|(r, h)| matches!(h, Hit::DesignAction("menu")).then_some(*r))
        .unwrap();
    click(&mut a, edit.x, edit.y);
    assert!(matches!(
        a.dialog.as_ref().unwrap().kind,
        DialogKind::DesignMenu(_)
    ));
    draw(&mut a);
    let before = a.design_form().unwrap().clone();
    click(&mut a, 1, 0);
    assert_eq!(*a.design_form().unwrap(), before);
    assert!(matches!(
        a.dialog.as_ref().unwrap().kind,
        DialogKind::DesignMenu(_)
    ));
}
#[test]
fn shared_include_ambiguity_aborts_rename_and_event_defaults_use_includes() {
    let t = Temp::new();
    fs::write(t.0.join("shared.bi"), "PRINT Button.Caption\n").unwrap();
    for (file, name) in [("a.frm", "AForm"), ("b.frm", "BForm")] {
        fs::write(t.0.join(file),format!("VERSION 1.00\nBegin Form {name}\n Width=40\n Height=15\n Begin CommandButton Button\n  Width=8\n End\nEnd\n'$INCLUDE: 'shared.bi'\n")).unwrap();
    }
    fs::write(t.0.join("p.mak"), "a.frm\nb.frm\n").unwrap();
    let mut a = App::new(&t.0, t.0.join("options"), (100, 30)).unwrap();
    a.load_initial_project(t.0.join("p.mak")).unwrap();
    a.options.syntax_checking = false;
    a.execute(Command::Form);
    plain(&mut a, K::Enter);
    let b = a.design_objects().unwrap()[1].0;
    a.design_select(b, false).unwrap();
    let before: Vec<_> = a
        .project
        .documents()
        .map(|(id, d)| (id, d.content().clone()))
        .collect();
    assert!(a.design_rename("Changed").is_err());
    for (id, c) in before {
        assert_eq!(*a.project.document(id).unwrap().content(), c);
    }
    let t = Temp::new();
    let mut a = t.app();
    let r = root(&mut a);
    let b = a
        .design_place(C::CommandButton, 0, r, Rect::new(1, 1, 8, 2))
        .unwrap();
    fs::write(t.0.join("defaults.bi"), "DEFINT A-Z\n").unwrap();
    let doc = a.design_document().unwrap();
    a.project
        .replace_text(doc, 0..0, "'$INCLUDE: 'defaults.bi'\n")
        .unwrap();
    a.design_event(b, "CLICK").unwrap();
    assert!(a
        .project
        .document(doc)
        .unwrap()
        .code()
        .contains("DEFINT A-Z\n\nEND SUB"));
    a.compile_current().unwrap();
}

#[test]
fn imported_type_shadow_is_not_an_object_reference_during_rename() {
    let t = Temp::new();
    let mut a = t.app();
    let r = root(&mut a);
    let b = a
        .design_place(C::CommandButton, 0, r, Rect::new(1, 1, 8, 2))
        .unwrap();
    let doc = a.design_document().unwrap();
    let name = node(&mut a, b).name;
    let types = a.project.new_module("Types").unwrap();
    a.project
        .replace_text(types, 0..0, "TYPE Imported\nCaption AS STRING\nEND TYPE\n")
        .unwrap();
    let code = format!(
        "DIM {name} AS Imported\n{name}.Caption = \"UDT field\"\nSUB {name}_Click\nEND SUB\n"
    );
    a.project.replace_text(doc, 0..0, &code).unwrap();
    a.design_rename("RunButton").unwrap();
    let code = a.project.document(doc).unwrap().code();
    assert!(code.contains(&format!("{name}.Caption = \"UDT field\"")));
    assert!(code.contains("SUB RunButton_CLICK"));
}

#[test]
fn events_with_the_same_name_are_local_to_their_form() {
    let t = Temp::new();
    let mut a = t.app();
    let r1 = root(&mut a);
    let b1 = a
        .design_place(C::CommandButton, 0, r1, Rect::new(1, 1, 8, 2))
        .unwrap();
    let d1 = a.design_document().unwrap();
    a.design_event(b1, "CLICK").unwrap();
    key(&mut a, K::F(12), M::SHIFT);
    a.design_event(r1, "LOAD").unwrap();
    key(&mut a, K::F(12), M::SHIFT);
    a.execute(Command::NewForm);
    let r2 = root(&mut a);
    let b2 = a
        .design_place(C::CommandButton, 0, r2, Rect::new(1, 1, 8, 2))
        .unwrap();
    let d2 = a.design_document().unwrap();
    a.design_event(b2, "CLICK").unwrap();
    assert_eq!(a.active_document(), Some(d2));
    key(&mut a, K::F(12), M::SHIFT);
    a.design_event(r2, "LOAD").unwrap();
    assert_eq!(a.active_document(), Some(d2));
    a.compile_current().unwrap();
    assert!(a
        .project
        .document(d1)
        .unwrap()
        .code()
        .contains("SUB Form_LOAD"));
    assert!(a
        .project
        .document(d2)
        .unwrap()
        .code()
        .contains("SUB Form_LOAD"));
}
