use tb_frontend::{
    forms::FormCatalog,
    source::{SourceSegment, SourceUnit},
};
use tb_ui::frm::{self, FormFile};
use tb_vm::{compile_project, project::ProjectCompiler};
fn unit(name: &str, text: &str) -> SourceUnit {
    SourceUnit::new(name, &format!("{name}.bas"), text)
}
fn compare(cache: &mut ProjectCompiler, units: &[SourceUnit], forms: &[FormFile]) -> bool {
    let mut catalog = FormCatalog::default();
    for f in forms {
        catalog.append(&f.catalog());
    }
    let a = cache.compile("APP", units, &catalog, forms);
    let b = compile_project("APP", units, &catalog, forms);
    match (a, b) {
        (Ok(a), Ok(b)) => {
            assert_eq!(a.to_tbc(), b.to_tbc());
            true
        }
        (Err(a), Err(b)) => {
            assert_eq!(
                a.iter().map(ToString::to_string).collect::<Vec<_>>(),
                b.iter().map(ToString::to_string).collect::<Vec<_>>()
            );
            false
        }
        (a, b) => panic!("cache/full mismatch {a:?} / {b:?}"),
    }
}
#[test]
fn cached_private_edits_parse_and_compile_only_changed_modules() {
    let mut c = ProjectCompiler::default();
    let mut units = vec![
        unit("A", "CALL Work\nEND"),
        unit("B", "SUB Work\nPRINT 1\nEND SUB"),
        unit("C", "SUB Independent\nPRINT 3\nEND SUB"),
    ];
    assert!(compare(&mut c, &units, &[]));
    assert_eq!(c.stats.compiled, 3);
    assert!(compare(&mut c, &units, &[]));
    assert_eq!(
        (c.stats.parsed, c.stats.compiled, c.stats.reused),
        (0, 0, 3)
    );
    units[1].segments[0].text = units[1].segments[0].text.replace("PRINT 1", "PRINT 2");
    assert!(compare(&mut c, &units, &[]));
    assert_eq!(
        (c.stats.parsed, c.stats.compiled, c.stats.reused),
        (1, 1, 2)
    );
    units[1].segments[0].text = "SUB Work\nPRINT 2\nEND SUB\nSUB Second\nPRINT 4\nEND SUB".into();
    assert!(compare(&mut c, &units, &[]));
    units[1].segments[0].text = units[1].segments[0]
        .text
        .replace("PRINT 2", "PRINT 2\nPRINT 3");
    assert!(compare(&mut c, &units, &[]));
    assert_eq!(
        (c.stats.compiled, c.stats.reused),
        (1, 2),
        "Verschobene fremde Prozedurzeilen ändern keinen Importvertrag"
    );
    units[1].segments[0].text = "SUB Work\nPRINT )\nEND SUB".into();
    assert!(!compare(&mut c, &units, &[]));
    units[1].segments[0].text = "SUB Work\nPRINT 5\nEND SUB".into();
    assert!(compare(&mut c, &units, &[]));
}
#[test]
fn constants_types_common_removed_symbols_and_order_match_full_compiler() {
    let mut c = ProjectCompiler::default();
    let mut u = vec![
        unit(
            "A",
            "CONST A=2\nTYPE T\nx AS INTEGER\nEND TYPE\nCOMMON SHARED N%\n",
        ),
        unit("B", "CONST B=A+1\n"),
        unit(
            "C",
            "DIM value AS T\nvalue.x=B\nCOMMON SHARED N%\nN%=B\nPRINT value.x\n",
        ),
    ];
    assert!(compare(&mut c, &u, &[]));
    u[0].segments[0].text = u[0].segments[0].text.replace("A=2", "A=4");
    assert!(compare(&mut c, &u, &[]));
    u[0].segments[0].text = u[0].segments[0].text.replace("INTEGER", "LONG");
    assert!(compare(&mut c, &u, &[]));
    u[0].segments[0].text = u[0].segments[0].text.replace("N%", "N$");
    compare(&mut c, &u, &[]);
    u[0].segments[0].text = u[0].segments[0].text.replace("CONST A=4", "' removed");
    assert!(!compare(&mut c, &u, &[]));
    u.swap(0, 2);
    assert!(!compare(&mut c, &u, &[]));
    u.remove(0);
    compare(&mut c, &u, &[]);
}
#[test]
fn includes_source_ids_data_and_forms_relink_without_patching_old_products() {
    let mut c = ProjectCompiler::default();
    let mut u = vec![
        unit(
            "A",
            "DATA 3,\"Hi\"\nREAD a%, b$\nPRINT a%;b$\nCALL Work\nEND",
        ),
        unit("B", "SUB Work\nPRINT 2\nEND SUB"),
    ];
    u[0].segments.insert(
        0,
        SourceSegment {
            file: "shared.bi".into(),
            first_line: 7,
            text: "CONST N=1\n".into(),
        },
    );
    assert!(compare(&mut c, &u, &[]));
    u[0].segments[0].text = "CONST N=2\n".into();
    assert!(compare(&mut c, &u, &[]));
    u.swap(0, 1);
    assert!(compare(&mut c, &u, &[]));
    u[0].segments.insert(
        0,
        SourceSegment {
            file: "extra.bi".into(),
            first_line: 1,
            text: "' extra source\n".into(),
        },
    );
    assert!(compare(&mut c, &u, &[]));
    for index in [None, Some(0), Some(1)] {
        let property = index.map_or(String::new(), |index| format!("  Index = {index}\n"));
        let text = format!(
            "VERSION 1.00\nBEGIN Form Main\n BEGIN CommandButton Button\n{property} END\nEND\n"
        );
        let form = frm::read_text("Main.frm", &text).unwrap();
        let units = [unit("Main", "'$FORM\n")];
        assert!(compare(&mut c, &units, &[form]));
    }
}

#[test]
fn unused_constants_do_not_invalidate_independent_units_and_declare_changes_do() {
    let mut c = ProjectCompiler::default();
    let mut u = vec![
        unit("A", "CONST A=1\n"),
        unit("B", "CONST B=A+1\n"),
        unit("C", "PRINT B\n"),
        unit("D", "PRINT 7\n"),
    ];
    assert!(compare(&mut c, &u, &[]));
    u[0].segments[0].text = "CONST A=3\n".into();
    assert!(compare(&mut c, &u, &[]));
    assert_eq!((c.stats.compiled, c.stats.reused), (3, 1));
    let mut u = vec![
        unit("A", "DECLARE SUB Work(n%)\nCALL Work(1)\nEND"),
        unit("B", "SUB Work(n%)\nPRINT n%\nEND SUB"),
    ];
    assert!(compare(&mut c, &u, &[]));
    u[1].segments[0].text = "SUB Work(n$)\nPRINT n$\nEND SUB".into();
    assert!(!compare(&mut c, &u, &[]));
    u[0].segments[0].text = "DECLARE SUB Work(n$)\nCALL Work(\"ok\")\nEND".into();
    assert!(compare(&mut c, &u, &[]));
}
