mod sourcegen;

use expect_test::Expect;
use ide_db::{
    assists::AssistResolveStrategy,
    base_db::{fixture::WithFixture, SourceDatabaseExt},
    RootDatabase,
};
use stdx::trim_indent;
use test_utils::{assert_eq_text, extract_annotations};

use crate::{DiagnosticsConfig, Severity};

/// Takes a multi-file input fixture with annotated cursor positions,
/// and checks that:
///  * a diagnostic is produced
///  * the first diagnostic fix trigger range touches the input cursor position
///  * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
#[track_caller]
pub(crate) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
    check_nth_fix(0, ra_fixture_before, ra_fixture_after);
}
/// Takes a multi-file input fixture with annotated cursor positions,
/// and checks that:
///  * a diagnostic is produced
///  * every diagnostic fixes trigger range touches the input cursor position
///  * that the contents of the file containing the cursor match `after` after each diagnostic fix is applied
pub(crate) fn check_fixes(ra_fixture_before: &str, ra_fixtures_after: Vec<&str>) {
    for (i, ra_fixture_after) in ra_fixtures_after.iter().enumerate() {
        check_nth_fix(i, ra_fixture_before, ra_fixture_after)
    }
}

#[track_caller]
fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) {
    let after = trim_indent(ra_fixture_after);

    let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
    let diagnostic = super::diagnostics(
        &db,
        &DiagnosticsConfig::default(),
        &AssistResolveStrategy::All,
        file_position.file_id,
    )
    .pop()
    .expect("no diagnostics");
    let fix = &diagnostic.fixes.expect("diagnostic misses fixes")[nth];
    let actual = {
        let source_change = fix.source_change.as_ref().unwrap();
        let file_id = *source_change.source_file_edits.keys().next().unwrap();
        let mut actual = db.file_text(file_id).to_string();

        for edit in source_change.source_file_edits.values() {
            edit.apply(&mut actual);
        }
        actual
    };

    assert_eq_text!(&after, &actual);
    assert!(
        fix.target.contains_inclusive(file_position.offset),
        "diagnostic fix range {:?} does not touch cursor position {:?}",
        fix.target,
        file_position.offset
    );
}

/// Checks that there's a diagnostic *without* fix at `$0`.
pub(crate) fn check_no_fix(ra_fixture: &str) {
    let (db, file_position) = RootDatabase::with_position(ra_fixture);
    let diagnostic = super::diagnostics(
        &db,
        &DiagnosticsConfig::default(),
        &AssistResolveStrategy::All,
        file_position.file_id,
    )
    .pop()
    .unwrap();
    assert!(diagnostic.fixes.is_none(), "got a fix when none was expected: {:?}", diagnostic);
}

pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) {
    let (db, file_id) = RootDatabase::with_single_file(ra_fixture);
    let diagnostics = super::diagnostics(
        &db,
        &DiagnosticsConfig::default(),
        &AssistResolveStrategy::All,
        file_id,
    );
    expect.assert_debug_eq(&diagnostics)
}

#[track_caller]
pub(crate) fn check_diagnostics(ra_fixture: &str) {
    let mut config = DiagnosticsConfig::default();
    config.disabled.insert("inactive-code".to_string());
    check_diagnostics_with_config(config, ra_fixture)
}

#[track_caller]
pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixture: &str) {
    let (db, files) = RootDatabase::with_many_files(ra_fixture);
    for file_id in files {
        let diagnostics = super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_id);

        let expected = extract_annotations(&*db.file_text(file_id));
        let mut actual = diagnostics
            .into_iter()
            .map(|d| {
                let mut annotation = String::new();
                if let Some(fixes) = &d.fixes {
                    assert!(!fixes.is_empty());
                    annotation.push_str("💡 ")
                }
                annotation.push_str(match d.severity {
                    Severity::Error => "error",
                    Severity::WeakWarning => "weak",
                });
                annotation.push_str(": ");
                annotation.push_str(&d.message);
                (d.range, annotation)
            })
            .collect::<Vec<_>>();
        actual.sort_by_key(|(range, _)| range.start());
        assert_eq!(expected, actual);
    }
}

#[test]
fn test_disabled_diagnostics() {
    let mut config = DiagnosticsConfig::default();
    config.disabled.insert("unresolved-module".into());

    let (db, file_id) = RootDatabase::with_single_file(r#"mod foo;"#);

    let diagnostics = super::diagnostics(&db, &config, &AssistResolveStrategy::All, file_id);
    assert!(diagnostics.is_empty());

    let diagnostics = super::diagnostics(
        &db,
        &DiagnosticsConfig::default(),
        &AssistResolveStrategy::All,
        file_id,
    );
    assert!(!diagnostics.is_empty());
}
