use analyzed_bridge as build_support;
use analyzed_bridge::ast;

use std::{
    env,
    error::Error,
    fs,
    path::{Path, PathBuf},
};

const PACKAGE: &str = "ra_ap_ide_db";
const GENERATED_DIR: &str = "ra_ap_ide_db_bridge";

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (generated, _) = build_support::prepare_bridge_package(PACKAGE, GENERATED_DIR)?;
    patch_ide_db_source(&generated.join("src/lib.rs"))?;
    patch_search_source(&generated.join("src/search.rs"))?;
    patch_symbol_index_source(&generated.join("src/symbol_index.rs"))?;
    println!("cargo:rerun-if-changed=build.rs");
    Ok(())
}

fn patch_ide_db_source(lib_rs: &Path) -> Result<(), Box<dyn Error>> {
    let mut source = fs::read_to_string(lib_rs)?;

    let visibility = owned_source_path("visibility.rs");
    build_support::mount_module(&mut source, None, "visibility", &visibility);
    println!("cargo:rerun-if-changed={}", visibility.display());
    build_support::append::<ast::Struct>(
        &mut source,
        "RootDatabase",
        &[build_support::Field {
            vis: None,
            name: "visible_files",
            ty: "Option<std::sync::Arc<rustc_hash::FxHashSet<vfs::FileId>>>",
        }],
    )?;
    build_support::append_record_fields(
        &mut source,
        "clone",
        "Self",
        &[build_support::FieldInit {
            name: "visible_files",
            value: Some("self.visible_files.clone()"),
        }],
    )?;
    build_support::append_record_fields(
        &mut source,
        "new",
        "RootDatabase",
        &[build_support::FieldInit {
            name: "visible_files",
            value: Some("None"),
        }],
    )?;
    fs::write(lib_rs, source)?;
    Ok(())
}

fn patch_search_source(search_rs: &Path) -> Result<(), Box<dyn Error>> {
    let mut source = fs::read_to_string(search_rs)?;

    build_support::retarget_use(&mut source, "all_crates", "crate::visibility::all_crates")?;
    let search_scope = owned_source_path("search_scope.rs");
    source.push_str(&format!(
        "\n#[path = {:?}]\nmod search_scope;\n",
        search_scope.to_string_lossy().into_owned()
    ));
    println!("cargo:rerun-if-changed={}", search_scope.display());
    build_support::rename::<ast::Fn>(&mut source, "reverse_dependencies", "_reverse_dependencies")?;
    build_support::add_attr::<ast::Fn>(
        &mut source,
        "_reverse_dependencies",
        "#[allow(dead_code)]",
    )?;

    fs::write(search_rs, source)?;
    Ok(())
}

fn patch_symbol_index_source(symbol_index_rs: &Path) -> Result<(), Box<dyn Error>> {
    let mut source = fs::read_to_string(symbol_index_rs)?;

    let world_symbols = owned_source_path("symbol_index.rs");
    source.push_str(&format!(
        "\n#[path = {:?}]\nmod world_symbols;\npub use world_symbols::world_symbols;\n",
        world_symbols.to_string_lossy().into_owned()
    ));
    println!("cargo:rerun-if-changed={}", world_symbols.display());
    build_support::rename::<ast::Fn>(&mut source, "world_symbols", "_world_symbols")?;
    build_support::add_attr::<ast::Fn>(&mut source, "_world_symbols", "#[allow(dead_code)]")?;
    build_support::rename::<ast::Fn>(
        &mut source,
        "resolve_path_to_modules",
        "_resolve_path_to_modules",
    )?;
    build_support::add_attr::<ast::Fn>(
        &mut source,
        "_resolve_path_to_modules",
        "#[allow(dead_code)]",
    )?;
    build_support::add_use(
        &mut source,
        None,
        "crate::visibility::resolve_path_to_modules",
    )?;

    fs::write(symbol_index_rs, source)?;
    Ok(())
}

fn owned_source_path(file_name: &str) -> PathBuf {
    PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set by Cargo"))
        .join("src")
        .join(file_name)
}
