use std::{path::Path, process::Command, string::String};

use miden_assembly_syntax::source_file;
use miden_core::{
    serde::{Deserializable, SliceReader},
    utils::hash_string_to_word,
};
use miden_mast_package::{
    SectionId,
    debug_info::{DebugFunctionsSection, DebugSourcesSection, DebugTypesSection},
};
use miden_package_registry::PackageRegistry;
use tempfile::TempDir;

use super::*;
use crate::testing::{TestContext, TestRegistry};

#[test]
fn builds_library_package_from_project_profiles() {
    let tempdir = TempDir::new().unwrap();
    let manifest_path = tempdir.path().join("miden-project.toml");
    write_file(
        &manifest_path,
        r#"[package]
name = "libpkg"
version = "1.2.3"
description = "sample library"

[lib]
path = "lib.masm"
"#,
    );
    write_file(
        &tempdir.path().join("lib.masm"),
        r#"pub proc helper
    push.1
    push.2
    add
end
"#,
    );

    let mut context = TestContext::new();

    let dev = context
        .assemble_library_package(&manifest_path, None)
        .expect("failed to assemble under dev profile");
    assert_eq!(&dev.name, "libpkg");
    assert_eq!(dev.version.to_string(), "1.2.3");
    assert_eq!(dev.description.as_deref(), Some("sample library"));
    assert_eq!(dev.kind, TargetType::Library);
    assert!(dev.mast.mast_forest().debug_info().num_asm_ops() > 0);

    let release = context
        .assemble_library_package(&manifest_path, Some("release"))
        .expect("failed to assemble under release profile");
    assert_eq!(release.mast.mast_forest().debug_info().num_asm_ops(), 0);
}

#[test]
fn builds_executable_target_from_shared_source_tree() {
    let tempdir = TempDir::new().unwrap();
    let manifest_path = tempdir.path().join("miden-project.toml");
    write_file(
        &manifest_path,
        r#"[package]
name = "app"
version = "1.0.0"

[lib]
path = "lib.masm"

[[bin]]
name = "primary"
path = "main.masm"

[[bin]]
name = "alternate"
path = "main2.masm"
"#,
    );
    write_file(
        &tempdir.path().join("lib.masm"),
        r#"pub proc helper
    push.1
end
"#,
    );
    write_file(
        &tempdir.path().join("shared.masm"),
        r#"pub proc helper
    push.2
end
"#,
    );
    write_file(
        &tempdir.path().join("main.masm"),
        r#"use $exec::lib
use $exec::shared

begin
    exec.lib::helper
    exec.shared::helper
end
"#,
    );
    write_file(
        &tempdir.path().join("main2.masm"),
        r#"begin
    push.9
end
"#,
    );

    let mut context = TestContext::new();
    let package = context
        .assemble_executable_package(&manifest_path, Some("primary"), None)
        .expect("executable build should succeed");

    assert_eq!(&package.name, "app:primary");
    assert_eq!(package.kind, TargetType::Executable);
    assert!(package.is_program());
}

#[test]
fn omitted_path_targets_require_explicit_sources() {
    let tempdir = TempDir::new().unwrap();
    let manifest_path = tempdir.path().join("miden-project.toml");
    write_file(
        &manifest_path,
        r#"[package]
name = "generated"
version = "1.0.0"

[lib]
"#,
    );

    let mut context = TestContext::new();
    let error = context
        .assemble_library_package(&manifest_path, None)
        .expect_err("assembly without sources should fail");
    assert!(error.to_string().contains("assemble_with_sources"));

    let root = Module::parse(
        "generated::temp",
        ModuleKind::Library,
        source_file!(
            context,
            r#"pub proc helper
    push.1
end
"#
        ),
        context.source_manager(),
    )
    .unwrap();

    let mut project_assembler = context.project_assembler_for_path(&manifest_path).unwrap();
    let package = project_assembler
        .assemble_with_sources(
            ProjectTargetSelector::Library,
            "dev",
            ProjectSourceInputs { root, support: Default::default() },
        )
        .expect("assembly with sources should succeed");
    assert_eq!(&package.name, "generated");
    assert_eq!(package.kind, TargetType::Library);
    assert!(PackageBuildProvenance::from_package(&package).unwrap().is_none());
}

#[test]
fn builds_kernel_package_and_supports_kernel_conversion() {
    let tempdir = TempDir::new().unwrap();
    let manifest_path = tempdir.path().join("miden-project.toml");
    write_file(
        &manifest_path,
        r#"[package]
name = "kernel-pkg"
version = "1.0.0"

[lib]
kind = "kernel"
path = "kernel.masm"
"#,
    );
    write_file(
        &tempdir.path().join("kernel.masm"),
        r#"pub proc foo
    caller
end
"#,
    );

    let mut registry = TestRegistry::default();
    let package = Assembler::default()
        .for_project_at_path(&manifest_path, &mut registry)
        .unwrap()
        .assemble(ProjectTargetSelector::Library, "dev")
        .expect("kernel build should succeed");

    assert_eq!(package.kind, TargetType::Kernel);
    assert!(package.try_into_kernel_library().is_ok());
}

#[test]
fn emitted_debug_sections_deserialize_from_the_build_assembler_state() {
    let tempdir = TempDir::new().unwrap();
    let manifest_path = tempdir.path().join("miden-project.toml");
    write_file(
        &manifest_path,
        r#"[package]
name = "debuggable"
version = "1.0.0"

[lib]
path = "lib.masm"
"#,
    );
    write_file(
        &tempdir.path().join("lib.masm"),
        r#"pub proc entry
    push.1
    drop
end
"#,
    );

    let mut context = TestContext::new();
    let package = context
        .assemble_library_package(&manifest_path, Some("dev"))
        .expect("debug build should succeed");

    let debug_sources = package
        .sections
        .iter()
        .find(|section| section.id == SectionId::DEBUG_SOURCES)
        .expect("package should contain DEBUG_SOURCES");
    let debug_functions = package
        .sections
        .iter()
        .find(|section| section.id == SectionId::DEBUG_FUNCTIONS)
        .expect("package should contain DEBUG_FUNCTIONS");
    let debug_types = package
        .sections
        .iter()
        .find(|section| section.id == SectionId::DEBUG_TYPES)
        .expect("package should contain DEBUG_TYPES");

    let mut sources_reader = SliceReader::new(debug_sources.data.as_ref());
    let debug_sources = DebugSourcesSection::read_from(&mut sources_reader)
        .expect("DEBUG_SOURCES should deserialize");
    assert_eq!(debug_sources.version, 1);
    assert_eq!(debug_sources.files.len(), 1);

    let mut functions_reader = SliceReader::new(debug_functions.data.as_ref());
    let debug_functions = DebugFunctionsSection::read_from(&mut functions_reader)
        .expect("DEBUG_FUNCTIONS should deserialize");
    assert_eq!(debug_functions.version, 1);
    assert_eq!(debug_functions.functions.len(), 1);

    let mut types_reader = SliceReader::new(debug_types.data.as_ref());
    let debug_types =
        DebugTypesSection::read_from(&mut types_reader).expect("DEBUG_TYPES should deserialize");
    assert_eq!(debug_types.version, 1);
}

#[test]
fn trim_paths_rewrites_mast_and_package_debug_paths() {
    let cwd = std::env::current_dir().unwrap();
    let tempdir = tempfile::Builder::new().prefix("trim-paths-").tempdir_in(&cwd).unwrap();
    let manifest_path = tempdir.path().join("miden-project.toml");
    write_file(
        &manifest_path,
        r#"[package]
name = "trimmed"
version = "1.0.0"

[lib]
path = "lib.masm"

[profile.dev]
trim-paths = true
"#,
    );
    write_file(
        &tempdir.path().join("lib.masm"),
        r#"pub proc entry
    push.1
    push.2
    add
end
"#,
    );

    let mut context = TestContext::new();
    let package = context
        .assemble_library_package(&manifest_path, Some("dev"))
        .expect("trim-paths build should succeed");
    let tempdir_prefix = tempdir.path().display().to_string();

    let asm_op_path = package
        .mast
        .mast_forest()
        .debug_info()
        .asm_ops()
        .iter()
        .find_map(|asm_op| asm_op.location().map(|location| location.uri.path().to_string()))
        .expect("assembled package should contain asm-op locations");
    assert!(
        !asm_op_path.contains(tempdir_prefix.as_str()),
        "asm-op path was not trimmed: {asm_op_path}"
    );
    assert!(
        !Path::new(&asm_op_path).is_absolute(),
        "asm-op path remained absolute: {asm_op_path}"
    );

    let debug_sources = package
        .sections
        .iter()
        .find(|section| section.id == SectionId::DEBUG_SOURCES)
        .expect("package should contain DEBUG_SOURCES");
    let mut sources_reader = SliceReader::new(debug_sources.data.as_ref());
    let debug_sources = DebugSourcesSection::read_from(&mut sources_reader)
        .expect("DEBUG_SOURCES should deserialize");
    assert!(!debug_sources.files.is_empty());
    for path in debug_sources.strings.iter() {
        assert!(
            !path.contains(tempdir_prefix.as_str()),
            "package debug path was not trimmed: {path}"
        );
        assert!(
            !Path::new(path.as_ref()).is_absolute(),
            "package debug path remained absolute: {path}"
        );
    }
}

#[test]
fn assembles_mixed_dependencies_and_inherits_static_runtime_deps() {
    let tempdir = TempDir::new().unwrap();
    let mut context = TestContext::new();

    let runtime =
        context.assemble_library_package_with_export("runtime", "1.0.0", "deps::runtime::leaf", []);
    let runtime_digest = runtime.digest();
    context.registry_mut().add_package(runtime.into());

    let regdep =
        context.assemble_library_package_with_export("regdep", "1.0.0", "deps::regdep::leaf", []);
    let regdep_digest = regdep.digest();
    context.registry_mut().add_package(regdep.into());

    let predep =
        context.assemble_library_package_with_export("predep", "1.0.0", "deps::predep::leaf", []);
    let predep_path = tempdir.path().join("predep.masp");
    predep.write_to_file(&predep_path).unwrap();

    let pathdep_dir = tempdir.path().join("pathdep");
    write_file(
        &pathdep_dir.join("miden-project.toml"),
        r#"[package]
name = "pathdep"
version = "1.0.0"

[lib]
path = "lib.masm"
namespace = "deps::pathdep"

[dependencies]
runtime = "=1.0.0"
"#,
    );
    write_file(
        &pathdep_dir.join("lib.masm"),
        r#"use ::deps::runtime

pub proc call_runtime
    exec.runtime::leaf
end
"#,
    );

    let gitdep_repo = tempdir.path().join("gitdep");
    write_file(
        &gitdep_repo.join("miden-project.toml"),
        r#"[package]
name = "gitdep"
version = "1.0.0"

[lib]
path = "lib.masm"
namespace = "deps::gitdep"
"#,
    );
    write_file(
        &gitdep_repo.join("lib.masm"),
        r#"pub proc leaf
    push.7
end
"#,
    );
    run_git(&gitdep_repo, &["init", "-b", "main"]);
    run_git(&gitdep_repo, &["config", "user.email", "test@example.com"]);
    run_git(&gitdep_repo, &["config", "user.name", "Test"]);
    run_git(&gitdep_repo, &["config", "commit.gpgsign", "false"]);
    run_git(&gitdep_repo, &["add", "."]);
    run_git(&gitdep_repo, &["commit", "-m", "init"]);

    let root_dir = tempdir.path().join("root");
    write_file(
        &root_dir.join("miden-project.toml"),
        &format!(
            r#"[package]
name = "root"
version = "1.0.0"

[lib]
path = "lib.masm"

[dependencies]
pathdep = {{ path = "../pathdep", linkage = "static" }}
gitdep = {{ git = "{}", branch = "main" }}
regdep = "=1.0.0"
predep = {{ path = "../predep.masp" }}
"#,
            gitdep_repo.display()
        ),
    );
    write_file(
        &root_dir.join("lib.masm"),
        r#"use ::deps::pathdep
use ::deps::gitdep

pub proc entry
    exec.pathdep::call_runtime
    exec.gitdep::leaf
end
"#,
    );

    let package = context
        .assemble_library_package(root_dir.join("miden-project.toml"), Some("dev"))
        .expect("mixed dependency build should succeed");

    let dependency_names = package
        .manifest
        .dependencies()
        .map(|dependency| dependency.name.to_string())
        .collect::<Vec<_>>();
    assert_eq!(dependency_names, vec!["gitdep", "predep", "regdep", "runtime"]);
    assert_eq!(
        context.registry().loaded_packages(),
        vec![
            format!("runtime@1.0.0#{runtime_digest}"),
            format!("regdep@1.0.0#{regdep_digest}")
        ]
    );
    assert!(!dependency_names.iter().any(|name| name == "pathdep"));
    assert_eq!(package.kind, TargetType::Library);
    assert_eq!(
        runtime_digest,
        package.manifest.dependencies().find(|d| &d.name == "runtime").unwrap().digest
    );
    assert_eq!(
        package
            .manifest
            .dependencies()
            .find(|d| &d.name == "runtime")
            .unwrap()
            .version
            .to_string(),
        "1.0.0"
    );
    assert!(
        context
            .registry()
            .is_semver_available(&PackageId::from("pathdep"), &"1.0.0".parse().unwrap())
    );
    assert!(
        context
            .registry()
            .is_semver_available(&PackageId::from("gitdep"), &"1.0.0".parse().unwrap())
    );
}

#[test]
fn assembles_mixed_path_and_git_dependencies_with_shared_registry_semver_resolution() {
    let tempdir = TempDir::new().unwrap();
    let mut context = TestContext::new();

    let shared_120 =
        context.assemble_library_package_with_export("shared", "1.2.0", "deps::shared::leaf", []);
    let shared_120_digest = shared_120.digest();
    context.registry_mut().add_package(shared_120.into());

    let shared_130 =
        context.assemble_library_package_with_export("shared", "1.3.0", "deps::shared::leaf", []);
    let shared_130_digest = shared_130.digest();
    context.registry_mut().add_package(shared_130.into());

    let pathdep_dir = tempdir.path().join("pathdep");
    write_file(
        &pathdep_dir.join("miden-project.toml"),
        r#"[package]
name = "pathdep"
version = "1.0.0"

[lib]
path = "lib.masm"
namespace = "deps::pathdep"

[dependencies]
shared = "^1.0.0"
"#,
    );
    write_file(
        &pathdep_dir.join("lib.masm"),
        r#"use ::deps::shared

pub proc call_shared
    exec.shared::leaf
    push.1
    drop
end
"#,
    );

    let gitdep_repo = tempdir.path().join("gitdep");
    write_file(
        &gitdep_repo.join("miden-project.toml"),
        r#"[package]
name = "gitdep"
version = "1.0.0"

[lib]
path = "lib.masm"
namespace = "deps::gitdep"

[dependencies]
shared = "=1.2.0"
"#,
    );
    write_file(
        &gitdep_repo.join("lib.masm"),
        r#"use ::deps::shared

pub proc call_shared
    exec.shared::leaf
    push.2
    drop
end
"#,
    );
    run_git(&gitdep_repo, &["init", "-b", "main"]);
    run_git(&gitdep_repo, &["config", "user.email", "test@example.com"]);
    run_git(&gitdep_repo, &["config", "user.name", "Test"]);
    run_git(&gitdep_repo, &["config", "commit.gpgsign", "false"]);
    run_git(&gitdep_repo, &["add", "."]);
    run_git(&gitdep_repo, &["commit", "-m", "init"]);

    let root_dir = tempdir.path().join("root");
    let root_manifest = root_dir.join("miden-project.toml");
    write_file(
        &root_manifest,
        &format!(
            r#"[package]
name = "root"
version = "1.0.0"

[lib]
path = "lib.masm"

[dependencies]
pathdep = {{ path = "../pathdep" }}
gitdep = {{ git = "{}", branch = "main" }}
"#,
            gitdep_repo.display()
        ),
    );
    write_file(
        &root_dir.join("lib.masm"),
        r#"use ::deps::pathdep
use ::deps::gitdep

pub proc entry
    exec.pathdep::call_shared
    exec.gitdep::call_shared
end
"#,
    );

    let package = context
        .assemble_library_package(&root_manifest, None)
        .expect("compatible shared registry dependency should assemble");
    let shared_dependency = package
        .manifest
        .dependencies()
        .find(|dependency| &dependency.name == "shared")
        .expect("root package should retain the shared runtime dependency");
    assert_eq!(shared_dependency.version.to_string(), "1.2.0");
    assert_eq!(shared_dependency.digest, shared_120_digest);

    let shared_loads = context
        .registry()
        .loaded_packages()
        .into_iter()
        .filter(|entry| entry.starts_with("shared@"))
        .collect::<Vec<_>>();
    assert!(
        shared_loads
            .iter()
            .any(|entry| entry == &format!("shared@1.2.0#{shared_120_digest}"))
    );
    assert!(
        !shared_loads
            .iter()
            .any(|entry| entry == &format!("shared@1.3.0#{shared_130_digest}"))
    );
}

#[test]
fn assembles_mixed_path_and_git_dependencies_with_shared_registry_digest_resolution() {
    let tempdir = TempDir::new().unwrap();
    let mut context = TestContext::new();

    let shared_100 =
        context.assemble_library_package_with_export("shared", "1.0.0", "deps::shared::leaf", []);
    let shared_digest = shared_100.digest();
    context.registry_mut().add_package(shared_100.into());

    let shared_200 =
        context.assemble_library_package_with_export("shared", "2.0.0", "deps::shared::leaf", []);
    assert_eq!(shared_200.digest(), shared_digest);
    context.registry_mut().add_package(shared_200.into());

    let pathdep_dir = tempdir.path().join("pathdep");
    write_file(
        &pathdep_dir.join("miden-project.toml"),
        &format!(
            r#"[package]
name = "pathdep"
version = "1.0.0"

[lib]
path = "lib.masm"
namespace = "deps::pathdep"

[dependencies]
shared = "{shared_digest}"
"#
        ),
    );
    write_file(
        &pathdep_dir.join("lib.masm"),
        r#"use ::deps::shared

pub proc call_shared
    exec.shared::leaf
    push.1
    drop
end
"#,
    );

    let gitdep_repo = tempdir.path().join("gitdep");
    write_file(
        &gitdep_repo.join("miden-project.toml"),
        &format!(
            r#"[package]
name = "gitdep"
version = "1.0.0"

[lib]
path = "lib.masm"
namespace = "deps::gitdep"

[dependencies]
shared = "1.0.0#{shared_digest}"
"#
        ),
    );
    write_file(
        &gitdep_repo.join("lib.masm"),
        r#"use ::deps::shared

pub proc call_shared
    exec.shared::leaf
    push.2
    drop
end
"#,
    );
    run_git(&gitdep_repo, &["init", "-b", "main"]);
    run_git(&gitdep_repo, &["config", "user.email", "test@example.com"]);
    run_git(&gitdep_repo, &["config", "user.name", "Test"]);
    run_git(&gitdep_repo, &["config", "commit.gpgsign", "false"]);
    run_git(&gitdep_repo, &["add", "."]);
    run_git(&gitdep_repo, &["commit", "-m", "init"]);

    let root_dir = tempdir.path().join("root");
    let root_manifest = root_dir.join("miden-project.toml");
    write_file(
        &root_manifest,
        &format!(
            r#"[package]
name = "root"
version = "1.0.0"

[lib]
path = "lib.masm"

[dependencies]
pathdep = {{ path = "../pathdep" }}
gitdep = {{ git = "{}", branch = "main" }}
"#,
            gitdep_repo.display()
        ),
    );
    write_file(
        &root_dir.join("lib.masm"),
        r#"use ::deps::pathdep
use ::deps::gitdep

pub proc entry
    exec.pathdep::call_shared
    exec.gitdep::call_shared
end
"#,
    );

    let package = context
        .assemble_library_package(&root_manifest, None)
        .expect("digest-compatible shared registry dependency should assemble");
    let shared_dependency = package
        .manifest
        .dependencies()
        .find(|dependency| &dependency.name == "shared")
        .expect("root package should retain the shared runtime dependency");
    assert_eq!(shared_dependency.version.to_string(), "1.0.0");
    assert_eq!(shared_dependency.digest, shared_digest);

    let shared_loads = context
        .registry()
        .loaded_packages()
        .into_iter()
        .filter(|entry| entry.starts_with("shared@"))
        .collect::<Vec<_>>();
    assert!(
        shared_loads
            .iter()
            .any(|entry| entry == &format!("shared@1.0.0#{shared_digest}"))
    );
    assert!(
        !shared_loads
            .iter()
            .any(|entry| entry == &format!("shared@2.0.0#{shared_digest}"))
    );
}

#[test]
fn runtime_dependency_conflict_requires_matching_digest() {
    let tempdir = TempDir::new().unwrap();
    let mut context = TestContext::new();

    let runtime_a_digest = hash_string_to_word("runtime-a");
    let runtime_b_digest = hash_string_to_word("runtime-b");

    let depa = context.assemble_library_package_with_export(
        "depa",
        "1.0.0",
        "deps::depa::leaf",
        [("runtime", "1.0.0", TargetType::Library, runtime_a_digest)],
    );
    let depa_path = tempdir.path().join("depa.masp");
    depa.write_to_file(&depa_path).unwrap();

    let depb = context.assemble_library_package_with_export(
        "depb",
        "1.0.0",
        "deps::depb::leaf",
        [("runtime", "1.0.0", TargetType::Library, runtime_b_digest)],
    );
    let depb_path = tempdir.path().join("depb.masp");
    depb.write_to_file(&depb_path).unwrap();

    let root_dir = tempdir.path().join("root");
    let root_manifest = root_dir.join("miden-project.toml");
    write_file(
        &root_manifest,
        r#"[package]
name = "root"
version = "1.0.0"

[lib]
path = "lib.masm"

[dependencies]
depa = { path = "../depa.masp" }
depb = { path = "../depb.masp" }
"#,
    );
    write_file(
        &root_dir.join("lib.masm"),
        r#"pub proc entry
    exec.::deps::depa::leaf
    exec.::deps::depb::leaf
end
"#,
    );

    let error = context
        .assemble_library_package(&root_manifest, None)
        .expect_err("runtime dependency digest conflicts should fail");
    assert!(error.to_string().contains("conflicting runtime dependency 'runtime'"));
}

#[test]
fn statically_linked_dynamic_dependencies_propagate_multiple_levels() {
    let tempdir = TempDir::new().unwrap();
    let mut context = TestContext::new();

    let runtime = Arc::<MastPackage>::from(context.assemble_library_package_with_export(
        "runtime",
        "1.0.0",
        "deps::runtime::leaf",
        [],
    ));
    let runtime_digest = runtime.digest();
    context.registry_mut().add_package(runtime);

    let mid_dir = tempdir.path().join("mid");
    write_file(
        &mid_dir.join("miden-project.toml"),
        r#"[package]
name = "mid"
version = "1.0.0"

[lib]
path = "lib.masm"
namespace = "deps::mid"

[dependencies]
runtime = "=1.0.0"
"#,
    );
    write_file(
        &mid_dir.join("lib.masm"),
        r#"use ::deps::runtime

pub proc call_runtime
    exec.runtime::leaf
end
"#,
    );

    let top_dir = tempdir.path().join("top");
    write_file(
        &top_dir.join("miden-project.toml"),
        r#"[package]
name = "top"
version = "1.0.0"

[lib]
path = "lib.masm"
namespace = "deps::top"

[dependencies]
mid = { path = "../mid", linkage = "static" }
"#,
    );
    write_file(
        &top_dir.join("lib.masm"),
        r#"use ::deps::mid

pub proc call_mid
    exec.mid::call_runtime
end
"#,
    );

    let root_dir = tempdir.path().join("root");
    let root_manifest = root_dir.join("miden-project.toml");
    write_file(
        &root_manifest,
        r#"[package]
name = "root"
version = "1.0.0"

[lib]
path = "lib.masm"

[dependencies]
top = { path = "../top", linkage = "static" }
"#,
    );
    write_file(
        &root_dir.join("lib.masm"),
        r#"pub proc entry
    exec.::deps::top::call_mid
end
"#,
    );

    let package = context
        .assemble_library_package(&root_manifest, None)
        .expect("multi-level static propagation should succeed");

    assert_eq!(
        package
            .manifest
            .dependencies()
            .map(|dep| format!("{}@{}#{}", &dep.name, dep.version, dep.digest))
            .collect::<Vec<_>>(),
        vec![format!("runtime@1.0.0#{runtime_digest}")]
    );
}

fn write_file(path: &FsPath, contents: &str) {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent).unwrap();
    }
    fs::write(path, contents).unwrap();
}

fn run_git(dir: &FsPath, args: &[&str]) {
    let output = Command::new("git").current_dir(dir).args(args).output().unwrap();
    assert!(
        output.status.success(),
        "git {} failed in '{}': {}",
        args.join(" "),
        dir.display(),
        String::from_utf8_lossy(&output.stderr)
    );
}

#[test]
fn workspace_dependency_stays_on_the_workspace_member_version() {
    let tempdir = TempDir::new().unwrap();
    let root_dir = tempdir.path().join("workspace-dep");
    fs::create_dir_all(&root_dir).unwrap();
    fs::create_dir_all(root_dir.join("dep")).unwrap();
    fs::create_dir_all(root_dir.join("app")).unwrap();

    write_file(
        &root_dir.join("miden-project.toml"),
        r#"[workspace]
members = ["dep", "app"]

[workspace.dependencies]
dep = { path = "dep" }
"#,
    );
    let dep_dir = root_dir.join("dep");
    write_file(
        &dep_dir.join("miden-project.toml"),
        r#"[package]
name = "dep"
version = "0.2.0"

[lib]
path = "mod.masm"

"#,
    );
    write_file(&dep_dir.join("mod.masm"), r#"pub proc foo add end"#);

    let app_dir = root_dir.join("app");
    let app_manifest = app_dir.join("miden-project.toml");
    write_file(
        &app_manifest,
        r#"[package]
name = "app"
version = "0.1.0"

[lib]
path = "mod.masm"

[dependencies]
dep.workspace = true
"#,
    );
    write_file(&app_dir.join("mod.masm"), r#"pub proc bar push.1 push.2 exec.::dep::foo end"#);

    let mut context = TestContext::new();

    // Add a pre-existing version of 'dep' that does not match the effective version requirement
    let dep010 = Arc::<MastPackage>::from(context.assemble_library_package_with_export(
        "dep",
        "0.1.0",
        "dep::foo",
        [],
    ));
    context.registry_mut().add_package(dep010.clone());

    let package = context
        .assemble_library_package(&app_manifest, None)
        .expect("failed to assemble 'app'");

    assert_eq!(
        package
            .manifest
            .dependencies()
            .map(|dep| format!("{}@{}#{}", &dep.name, dep.version, dep.digest))
            .collect::<Vec<_>>(),
        vec![format!("dep@0.2.0#{}", dep010.digest())]
    );
}

#[test]
fn path_dependency_is_published_and_reused_when_sources_match() {
    let tempdir = TempDir::new().unwrap();
    let dep_dir = tempdir.path().join("dep");
    write_file(
        &dep_dir.join("miden-project.toml"),
        r#"[package]
name = "dep"
version = "1.0.0"

[lib]
path = "lib.masm"
"#,
    );
    write_file(
        &dep_dir.join("lib.masm"),
        r#"pub proc foo
    push.1
end
"#,
    );

    let root_dir = tempdir.path().join("root");
    let root_manifest = root_dir.join("miden-project.toml");
    write_file(
        &root_manifest,
        r#"[package]
name = "root"
version = "1.0.0"

[lib]
path = "lib.masm"

[dependencies]
dep = { path = "../dep" }
"#,
    );
    write_file(
        &root_dir.join("lib.masm"),
        r#"pub proc entry
    exec.::dep::foo
end
"#,
    );

    let mut context = TestContext::new();
    let first = context
        .assemble_library_package(&root_manifest, None)
        .expect("first build should succeed");
    assert!(
        context
            .registry()
            .is_semver_available(&PackageId::from("dep"), &"1.0.0".parse().unwrap())
    );
    assert!(context.registry().loaded_packages().is_empty());

    let expected_dependency = first
        .manifest
        .dependencies()
        .map(|dep| format!("{}@{}#{}", &dep.name, dep.version, dep.digest))
        .collect::<Vec<_>>();
    context.registry().clear_loaded_packages();

    let second = context
        .assemble_library_package(&root_manifest, None)
        .expect("second build should reuse canonical dependency");

    let dep_record = context
        .registry()
        .get_by_semver(&PackageId::from("dep"), &"1.0.0".parse().unwrap())
        .expect("dependency should be registered");
    assert_eq!(
        context.registry().loaded_packages(),
        vec![format!("dep@{}", dep_record.version())]
    );
    assert_eq!(
        second
            .manifest
            .dependencies()
            .map(|dep| format!("{}@{}#{}", &dep.name, dep.version, dep.digest))
            .collect::<Vec<_>>(),
        expected_dependency
    );
}

#[test]
fn root_package_is_not_auto_published_when_assembling_source_dependencies() {
    let tempdir = TempDir::new().unwrap();
    let dep_dir = tempdir.path().join("dep");
    write_file(
        &dep_dir.join("miden-project.toml"),
        r#"[package]
name = "dep"
version = "1.0.0"

[lib]
path = "lib.masm"
"#,
    );
    write_file(
        &dep_dir.join("lib.masm"),
        r#"pub proc foo
    push.1
end
"#,
    );

    let root_dir = tempdir.path().join("root");
    let root_manifest = root_dir.join("miden-project.toml");
    write_file(
        &root_manifest,
        r#"[package]
name = "root"
version = "1.0.0"

[lib]
path = "lib.masm"

[dependencies]
dep = { path = "../dep" }
"#,
    );
    write_file(
        &root_dir.join("lib.masm"),
        r#"pub proc entry
    exec.::dep::foo
end
"#,
    );

    let mut context = TestContext::new();
    let package = context
        .assemble_library_package(&root_manifest, None)
        .expect("assembly with a source dependency should succeed");

    assert_eq!(&package.name, "root");
    assert!(
        context
            .registry()
            .is_semver_available(&PackageId::from("dep"), &"1.0.0".parse().unwrap())
    );
    assert!(
        !context
            .registry()
            .is_semver_available(&PackageId::from("root"), &"1.0.0".parse().unwrap())
    );
}

#[test]
fn path_dependency_source_changes_require_semver_bump() {
    let tempdir = TempDir::new().unwrap();
    let dep_dir = tempdir.path().join("dep");
    write_file(
        &dep_dir.join("miden-project.toml"),
        r#"[package]
name = "dep"
version = "1.0.0"

[lib]
path = "lib.masm"
"#,
    );
    let dep_source = dep_dir.join("lib.masm");
    write_file(
        &dep_source,
        r#"pub proc foo
    push.1
end
"#,
    );

    let root_dir = tempdir.path().join("root");
    let root_manifest = root_dir.join("miden-project.toml");
    write_file(
        &root_manifest,
        r#"[package]
name = "root"
version = "1.0.0"

[lib]
path = "lib.masm"

[dependencies]
dep = { path = "../dep" }
"#,
    );
    write_file(
        &root_dir.join("lib.masm"),
        r#"pub proc entry
    exec.::dep::foo
end
"#,
    );

    let mut context = TestContext::new();
    context
        .assemble_library_package(&root_manifest, None)
        .expect("initial build should succeed");

    write_file(
        &dep_source,
        r#"pub proc foo
    push.2
end
"#,
    );

    let error = context
        .assemble_library_package(&root_manifest, None)
        .expect_err("changed dependency sources should require a semver bump");
    assert!(error.to_string().contains("bump the semantic version"));
}

#[test]
fn transitive_path_dependency_source_changes_require_semver_bump() {
    let tempdir = TempDir::new().unwrap();
    let leaf_dir = tempdir.path().join("leaf");
    write_file(
        &leaf_dir.join("miden-project.toml"),
        r#"[package]
name = "leaf"
version = "1.0.0"

[lib]
path = "lib.masm"
namespace = "deps::leaf"
"#,
    );
    let leaf_source = leaf_dir.join("lib.masm");
    write_file(
        &leaf_source,
        r#"pub proc foo
    push.1
end
"#,
    );

    let dep_dir = tempdir.path().join("dep");
    write_file(
        &dep_dir.join("miden-project.toml"),
        r#"[package]
name = "dep"
version = "1.0.0"

[lib]
path = "lib.masm"
namespace = "deps::dep"

[dependencies]
leaf = { path = "../leaf", linkage = "static" }
"#,
    );
    write_file(
        &dep_dir.join("lib.masm"),
        r#"use ::deps::leaf

pub proc call_leaf
    exec.leaf::foo
end
"#,
    );

    let root_dir = tempdir.path().join("root");
    let root_manifest = root_dir.join("miden-project.toml");
    write_file(
        &root_manifest,
        r#"[package]
name = "root"
version = "1.0.0"

[lib]
path = "lib.masm"

[dependencies]
dep = { path = "../dep" }
"#,
    );
    write_file(
        &root_dir.join("lib.masm"),
        r#"pub proc entry
    exec.::deps::dep::call_leaf
end
"#,
    );

    let mut context = TestContext::new();
    context
        .assemble_library_package(&root_manifest, None)
        .expect("initial build should succeed");

    write_file(
        &leaf_source,
        r#"pub proc foo
    push.2
end
"#,
    );

    let error = context
        .assemble_library_package(&root_manifest, None)
        .expect_err("changed transitive dependency sources should require a semver bump");
    assert!(error.to_string().contains("package 'dep' version '1.0.0'"));
    assert!(error.to_string().contains("different source provenance"));
}

#[test]
fn source_dependency_profile_changes_require_semver_bump() {
    let tempdir = TempDir::new().unwrap();
    let dep_dir = tempdir.path().join("dep");
    write_file(
        &dep_dir.join("miden-project.toml"),
        r#"[package]
name = "dep"
version = "1.0.0"

[lib]
path = "lib.masm"
"#,
    );
    write_file(
        &dep_dir.join("lib.masm"),
        r#"pub proc foo
    push.1
end
"#,
    );

    let root_dir = tempdir.path().join("root");
    let root_manifest = root_dir.join("miden-project.toml");
    write_file(
        &root_manifest,
        r#"[package]
name = "root"
version = "1.0.0"

[lib]
path = "lib.masm"

[dependencies]
dep = { path = "../dep" }
"#,
    );
    write_file(
        &root_dir.join("lib.masm"),
        r#"pub proc entry
    exec.::dep::foo
end
"#,
    );

    let mut context = TestContext::new();
    context
        .assemble_library_package(&root_manifest, Some("dev"))
        .expect("initial dev build should succeed");
    context.registry().clear_loaded_packages();

    let error = context
        .assemble_library_package(&root_manifest, Some("release"))
        .expect_err("changing package-shaping profile inputs should require a semver bump");
    assert!(error.to_string().contains("package 'dep' version '1.0.0'"));
    assert!(error.to_string().contains("different source provenance"));
    assert!(error.to_string().contains("trim_paths=true"));
}

#[test]
fn source_dependency_rebuilds_when_canonical_artifact_is_unreadable() {
    let tempdir = TempDir::new().unwrap();
    let dep_dir = tempdir.path().join("dep");
    write_file(
        &dep_dir.join("miden-project.toml"),
        r#"[package]
name = "dep"
version = "1.0.0"

[lib]
path = "lib.masm"
"#,
    );
    write_file(
        &dep_dir.join("lib.masm"),
        r#"pub proc foo
    push.1
end
"#,
    );

    let root_dir = tempdir.path().join("root");
    let root_manifest = root_dir.join("miden-project.toml");
    write_file(
        &root_manifest,
        r#"[package]
name = "root"
version = "1.0.0"

[lib]
path = "lib.masm"

[dependencies]
dep = { path = "../dep" }
"#,
    );
    write_file(
        &root_dir.join("lib.masm"),
        r#"pub proc entry
    exec.::dep::foo
end
"#,
    );

    let mut context = TestContext::new();
    context
        .assemble_library_package(&root_manifest, None)
        .expect("initial build should succeed");

    let dep_record = context
        .registry()
        .get_by_semver(&PackageId::from("dep"), &"1.0.0".parse().unwrap())
        .expect("dependency should be registered");
    let dep_version = dep_record.version().clone();
    let removed = context.registry_mut().remove_package(&PackageId::from("dep"), &dep_version);
    assert!(removed.is_some(), "expected indexed dependency artifact to exist");
    context.registry().clear_loaded_packages();

    context.assemble_library_package(&root_manifest, None).expect(
        "source dependency should rebuild from source when the canonical artifact is unreadable",
    );
    assert_eq!(context.registry().loaded_packages(), vec![format!("dep@{}", dep_version)]);
}

#[test]
fn workspace_manifest_changes_without_effect_allow_reuse_of_member_packages() {
    let tempdir = TempDir::new().unwrap();
    let workspace_dir = tempdir.path().join("workspace");
    let dep_dir = workspace_dir.join("dep");
    let app_dir = workspace_dir.join("app");
    fs::create_dir_all(&dep_dir).unwrap();
    fs::create_dir_all(&app_dir).unwrap();

    let workspace_manifest = workspace_dir.join("miden-project.toml");
    write_file(
        &workspace_manifest,
        r#"[workspace]
members = ["dep", "app"]

[workspace.dependencies]
dep = { path = "dep" }
"#,
    );
    write_file(
        &dep_dir.join("miden-project.toml"),
        r#"[package]
name = "dep"
version = "1.0.0"

[lib]
path = "mod.masm"
"#,
    );
    write_file(
        &dep_dir.join("mod.masm"),
        r#"pub proc foo
    push.1
end
"#,
    );

    let app_manifest = app_dir.join("miden-project.toml");
    write_file(
        &app_manifest,
        r#"[package]
name = "app"
version = "1.0.0"

[lib]
path = "mod.masm"

[dependencies]
dep.workspace = true
"#,
    );
    write_file(
        &app_dir.join("mod.masm"),
        r#"pub proc bar
    exec.::dep::foo
end
"#,
    );

    let mut context = TestContext::new();
    let first = context
        .assemble_library_package(&app_manifest, None)
        .expect("initial workspace build should succeed");
    assert!(
        context
            .registry()
            .is_semver_available(&PackageId::from("dep"), &"1.0.0".parse().unwrap())
    );

    let expected_dependency = first
        .manifest
        .dependencies()
        .map(|dep| format!("{}@{}#{}", &dep.name, dep.version, dep.digest))
        .collect::<Vec<_>>();
    context.registry().clear_loaded_packages();

    write_file(
        &workspace_manifest,
        r#"[workspace]
members = ["dep", "app"]

[workspace.dependencies]
dep = { path = "dep" }

# comment changes provenance hashing for workspace member builds
"#,
    );

    let second = context
        .assemble_library_package(&app_manifest, None)
        .expect("workspace manifest comment changes should still allow reuse");

    let dep_record = context
        .registry()
        .get_by_semver(&PackageId::from("dep"), &"1.0.0".parse().unwrap())
        .expect("workspace dependency should be registered");
    assert_eq!(
        context.registry().loaded_packages(),
        vec![format!("dep@{}", dep_record.version())]
    );
    assert_eq!(second.digest(), first.digest());
    assert_eq!(
        second
            .manifest
            .dependencies()
            .map(|dep| format!("{}@{}#{}", &dep.name, dep.version, dep.digest))
            .collect::<Vec<_>>(),
        expected_dependency
    );
}

#[test]
fn git_dependency_reuses_canonical_revision_and_rejects_new_commit_without_semver_bump() {
    let tempdir = TempDir::new().unwrap();
    let gitdep_repo = tempdir.path().join("gitdep");
    write_file(
        &gitdep_repo.join("miden-project.toml"),
        r#"[package]
name = "gitdep"
version = "1.0.0"

[lib]
path = "lib.masm"
"#,
    );
    let git_source = gitdep_repo.join("lib.masm");
    write_file(
        &git_source,
        r#"pub proc leaf
    push.7
end
"#,
    );
    run_git(&gitdep_repo, &["init", "-b", "main"]);
    run_git(&gitdep_repo, &["config", "user.email", "test@example.com"]);
    run_git(&gitdep_repo, &["config", "user.name", "Test"]);
    run_git(&gitdep_repo, &["config", "commit.gpgsign", "false"]);
    run_git(&gitdep_repo, &["add", "."]);
    run_git(&gitdep_repo, &["commit", "-m", "init"]);

    let root_dir = tempdir.path().join("root");
    let root_manifest = root_dir.join("miden-project.toml");
    write_file(
        &root_manifest,
        &format!(
            r#"[package]
name = "root"
version = "1.0.0"

[lib]
path = "lib.masm"

[dependencies]
gitdep = {{ git = "{}", branch = "main" }}
"#,
            gitdep_repo.display()
        ),
    );
    write_file(
        &root_dir.join("lib.masm"),
        r#"pub proc entry
    exec.::gitdep::leaf
end
"#,
    );

    let mut context = TestContext::new();
    context
        .assemble_library_package(&root_manifest, None)
        .expect("initial build should succeed");
    context.registry().clear_loaded_packages();

    context
        .assemble_library_package(&root_manifest, None)
        .expect("matching revision should reuse canonical dependency");
    let dep_record = context
        .registry()
        .get_by_semver(&PackageId::from("gitdep"), &"1.0.0".parse().unwrap())
        .expect("git dependency should be registered");
    assert_eq!(
        context.registry().loaded_packages(),
        vec![format!("gitdep@{}", dep_record.version())]
    );

    write_file(
        &git_source,
        r#"pub proc leaf
    push.8
end
"#,
    );
    run_git(&gitdep_repo, &["add", "."]);
    run_git(&gitdep_repo, &["commit", "-m", "change"]);

    let error = context
        .assemble_library_package(&root_manifest, None)
        .expect_err("new git revision should require a semver bump");
    assert!(error.to_string().contains("bump the semantic version"));
}

#[test]
fn omitted_path_dependency_requires_canonical_registry_entry() {
    let tempdir = TempDir::new().unwrap();
    let dep_dir = tempdir.path().join("dep");
    write_file(
        &dep_dir.join("miden-project.toml"),
        r#"[package]
name = "dep"
version = "1.0.0"

[lib]
"#,
    );

    let root_dir = tempdir.path().join("root");
    let root_manifest = root_dir.join("miden-project.toml");
    write_file(
        &root_manifest,
        r#"[package]
name = "root"
version = "1.0.0"

[lib]
path = "lib.masm"

[dependencies]
dep = { path = "../dep" }
"#,
    );
    write_file(
        &root_dir.join("lib.masm"),
        r#"pub proc entry
    exec.::dep::foo
end
"#,
    );

    let mut context = TestContext::new();
    let missing = context
        .assemble_library_package(&root_manifest, None)
        .expect_err("omitted-path dependency should require a canonical registry entry");
    assert!(missing.to_string().contains("was not found in the package registry"));

    let dep = Arc::<MastPackage>::from(context.assemble_library_package_with_export(
        "dep",
        "1.0.0",
        "dep::foo",
        [],
    ));
    let dep_digest = dep.digest();
    context.registry_mut().add_package(dep);
    context.registry().clear_loaded_packages();

    let package = context
        .assemble_library_package(&root_manifest, None)
        .expect("canonical registry entry should satisfy omitted-path dependency");
    assert_eq!(
        package
            .manifest
            .dependencies()
            .map(|dep| format!("{}@{}#{}", &dep.name, dep.version, dep.digest))
            .collect::<Vec<_>>(),
        vec![format!("dep@1.0.0#{dep_digest}")]
    );
}

#[test]
fn workspace_member_source_dependencies_preserve_workspace_inheritance() {
    let tempdir = TempDir::new().unwrap();
    let workspace_dir = tempdir.path().join("workspace");
    let dep_dir = workspace_dir.join("dep");
    let app_dir = workspace_dir.join("app");
    fs::create_dir_all(&dep_dir).unwrap();
    fs::create_dir_all(&app_dir).unwrap();

    write_file(
        &workspace_dir.join("miden-project.toml"),
        r#"[workspace]
members = ["dep", "app"]

[workspace.package]
version = "1.0.0"

[workspace.dependencies]
dep = { path = "dep" }
"#,
    );
    write_file(
        &dep_dir.join("miden-project.toml"),
        r#"[package]
name = "dep"
version.workspace = true

[lib]
path = "mod.masm"
"#,
    );
    write_file(
        &dep_dir.join("mod.masm"),
        r#"pub proc foo
    push.1
end
"#,
    );

    let app_manifest = app_dir.join("miden-project.toml");
    write_file(
        &app_manifest,
        r#"[package]
name = "app"
version = "1.0.0"

[lib]
path = "mod.masm"

[dependencies]
dep.workspace = true
"#,
    );
    write_file(
        &app_dir.join("mod.masm"),
        r#"pub proc bar
    exec.::dep::foo
end
"#,
    );

    let mut context = TestContext::new();
    let package = context
        .assemble_library_package(&app_manifest, None)
        .expect("workspace member dependency should assemble with inherited workspace config");
    assert!(
        context
            .registry()
            .is_semver_available(&PackageId::from("dep"), &"1.0.0".parse().unwrap())
    );

    let dependencies = package.manifest.dependencies().collect::<Vec<_>>();
    assert_eq!(dependencies.len(), 1);
    assert_eq!(dependencies[0].name, PackageId::from("dep"));
    assert_eq!(dependencies[0].version.to_string(), "1.0.0");
}

#[test]
fn executable_packages_preserve_kernel_when_converted_back_to_program() {
    let tempdir = TempDir::new().unwrap();
    let manifest_path = write_kernel_program_project(tempdir.path());

    let mut context = TestContext::new();
    let kernel_package = context
        .assemble_library_package(&manifest_path, None)
        .expect("kernel package build should succeed");
    let expected_kernel = kernel_package
        .try_into_kernel_library()
        .expect("kernel package should round-trip as a kernel library")
        .kernel()
        .clone();
    let package = context
        .assemble_executable_package(&manifest_path, Some("main"), None)
        .expect("executable package build should succeed");
    let kernel_dependency = package
        .manifest
        .dependencies()
        .find(|dependency| dependency.kind == TargetType::Kernel)
        .cloned()
        .expect("executable package should record the linked kernel runtime dependency");
    let embedded_kernel_package = package
        .sections
        .iter()
        .find(|section| section.id == SectionId::KERNEL)
        .map(|section| MastPackage::read_from_bytes(section.data.as_ref()).unwrap())
        .expect("executable package should embed the linked kernel package");
    assert_eq!(embedded_kernel_package.kind, TargetType::Kernel);
    assert_eq!(embedded_kernel_package.name, kernel_dependency.name);
    assert_eq!(embedded_kernel_package.version, kernel_dependency.version);
    assert_eq!(embedded_kernel_package.digest(), kernel_dependency.digest);

    let round_tripped_package = MastPackage::read_from_bytes(&package.to_bytes())
        .expect("serialized executable package should round-trip");
    let round_tripped_program = round_tripped_package
        .try_into_program()
        .expect("executable package conversion should preserve kernel information");

    assert_eq!(round_tripped_program.kernel(), &expected_kernel);
}

#[test]
fn executable_packages_preserve_transitive_kernel_when_converted_back_to_program() {
    let tempdir = TempDir::new().unwrap();
    let (root_manifest, kernel_manifest) = write_transitive_kernel_program_project(tempdir.path());

    let mut context = TestContext::new();
    let kernel_package = context
        .assemble_library_package(&kernel_manifest, None)
        .expect("kernel package build should succeed");
    let expected_kernel = kernel_package
        .try_into_kernel_library()
        .expect("kernel package should round-trip as a kernel library")
        .kernel()
        .clone();
    let package = context
        .assemble_executable_package(&root_manifest, Some("main"), None)
        .expect("executable package build should succeed");
    let kernel_dependency = package
        .manifest
        .dependencies()
        .find(|dependency| dependency.kind == TargetType::Kernel)
        .cloned()
        .expect("executable package should record the transitive kernel runtime dependency");
    let embedded_kernel_package = package
        .sections
        .iter()
        .find(|section| section.id == SectionId::KERNEL)
        .map(|section| MastPackage::read_from_bytes(section.data.as_ref()).unwrap())
        .expect("executable package should embed the transitive kernel package");
    assert_eq!(embedded_kernel_package.kind, TargetType::Kernel);
    assert_eq!(embedded_kernel_package.name, kernel_dependency.name);
    assert_eq!(embedded_kernel_package.version, kernel_dependency.version);
    assert_eq!(embedded_kernel_package.digest(), kernel_dependency.digest);

    let round_tripped_package = MastPackage::read_from_bytes(&package.to_bytes())
        .expect("serialized executable package should round-trip");
    let round_tripped_program = round_tripped_package
        .try_into_program()
        .expect("executable package conversion should preserve transitive kernel information");

    assert_eq!(round_tripped_program.kernel(), &expected_kernel);
}

#[test]
fn library_packages_with_transitive_kernels_do_not_embed_kernel_sections() {
    let tempdir = TempDir::new().unwrap();
    write_transitive_kernel_program_project(tempdir.path());
    let mid_manifest = tempdir.path().join("mid").join("miden-project.toml");

    let mut context = TestContext::new();
    let package = context
        .assemble_library_package(&mid_manifest, None)
        .expect("library package build should succeed");

    assert!(
        package
            .manifest
            .dependencies()
            .any(|dependency| dependency.kind == TargetType::Kernel)
    );
    assert!(!package.sections.iter().any(|section| section.id == SectionId::KERNEL));
}

#[test]
fn preassembled_libraries_prefer_store_kernel_over_embedded_copy() {
    let tempdir = TempDir::new().unwrap();
    let (_, kernel_manifest) = write_transitive_kernel_program_project(tempdir.path());
    let mid_manifest = tempdir.path().join("mid").join("miden-project.toml");
    let mid_package_path = tempdir.path().join("mid-embedded.masp");

    let mut build_context = TestContext::new();
    let kernel_package = build_context
        .assemble_library_package(&kernel_manifest, None)
        .expect("kernel package build should succeed");
    let expected_kernel = kernel_package
        .try_into_kernel_library()
        .expect("kernel package should round-trip as a kernel library")
        .kernel()
        .clone();
    let mut mid_package = MastPackage::read_from_bytes(
        &build_context
            .assemble_library_package(&mid_manifest, None)
            .expect("mid package build should succeed")
            .to_bytes(),
    )
    .expect("mid package should deserialize");
    let mut mismatched_kernel_package = MastPackage::read_from_bytes(&kernel_package.to_bytes())
        .expect("kernel should deserialize");
    mismatched_kernel_package.version = "2.0.0".parse().unwrap();
    mid_package
        .sections
        .push(Section::new(SectionId::KERNEL, mismatched_kernel_package.to_bytes()));
    mid_package.write_to_file(&mid_package_path).unwrap();

    let root_manifest =
        write_preassembled_kernel_executable_project(tempdir.path(), &mid_package_path);
    let mut context = TestContext::new();
    context.registry_mut().add_package(kernel_package.clone());

    let package = context
        .assemble_executable_package(&root_manifest, Some("main"), None)
        .expect("executable package build should prefer the store kernel");
    let embedded_kernel_package = package
        .sections
        .iter()
        .find(|section| section.id == SectionId::KERNEL)
        .map(|section| MastPackage::read_from_bytes(section.data.as_ref()).unwrap())
        .expect("executable package should embed the store-provided kernel package");
    assert_eq!(embedded_kernel_package.version, kernel_package.version);
    assert_eq!(embedded_kernel_package.digest(), kernel_package.digest());

    let round_tripped_program = MastPackage::read_from_bytes(&package.to_bytes())
        .expect("serialized executable package should round-trip")
        .try_into_program()
        .expect("program reconstruction should use the store-provided kernel");
    assert_eq!(round_tripped_program.kernel(), &expected_kernel);
}

#[test]
fn preassembled_libraries_fall_back_to_embedded_kernel_when_store_is_missing() {
    let tempdir = TempDir::new().unwrap();
    let (_, kernel_manifest) = write_transitive_kernel_program_project(tempdir.path());
    let mid_manifest = tempdir.path().join("mid").join("miden-project.toml");
    let mid_package_path = tempdir.path().join("mid-embedded.masp");

    let mut build_context = TestContext::new();
    let kernel_package = build_context
        .assemble_library_package(&kernel_manifest, None)
        .expect("kernel package build should succeed");
    let expected_kernel = kernel_package
        .try_into_kernel_library()
        .expect("kernel package should round-trip as a kernel library")
        .kernel()
        .clone();
    let mut mid_package = MastPackage::read_from_bytes(
        &build_context
            .assemble_library_package(&mid_manifest, None)
            .expect("mid package build should succeed")
            .to_bytes(),
    )
    .expect("mid package should deserialize");
    mid_package
        .sections
        .push(Section::new(SectionId::KERNEL, kernel_package.to_bytes()));
    mid_package.write_to_file(&mid_package_path).unwrap();

    let root_manifest =
        write_preassembled_kernel_executable_project(tempdir.path(), &mid_package_path);
    let mut context = TestContext::new();
    let package = context
        .assemble_executable_package(&root_manifest, Some("main"), None)
        .expect("executable package build should fall back to the embedded kernel");
    let embedded_kernel_package = package
        .sections
        .iter()
        .find(|section| section.id == SectionId::KERNEL)
        .map(|section| MastPackage::read_from_bytes(section.data.as_ref()).unwrap())
        .expect("executable package should embed the fallback kernel package");
    assert_eq!(embedded_kernel_package.version, kernel_package.version);
    assert_eq!(embedded_kernel_package.digest(), kernel_package.digest());

    let round_tripped_program = MastPackage::read_from_bytes(&package.to_bytes())
        .expect("serialized executable package should round-trip")
        .try_into_program()
        .expect("program reconstruction should use the embedded fallback kernel");
    assert_eq!(round_tripped_program.kernel(), &expected_kernel);
}

#[test]
fn preassembled_libraries_fall_back_to_embedded_kernel_when_store_artifact_is_unreadable() {
    let tempdir = TempDir::new().unwrap();
    let (_, kernel_manifest) = write_transitive_kernel_program_project(tempdir.path());
    let mid_manifest = tempdir.path().join("mid").join("miden-project.toml");
    let mid_package_path = tempdir.path().join("mid-embedded.masp");

    let mut build_context = TestContext::new();
    let kernel_package = build_context
        .assemble_library_package(&kernel_manifest, None)
        .expect("kernel package build should succeed");
    let expected_kernel = kernel_package
        .try_into_kernel_library()
        .expect("kernel package should round-trip as a kernel library")
        .kernel()
        .clone();
    let kernel_version = miden_package_registry::Version::new(
        kernel_package.version.clone(),
        kernel_package.digest(),
    );
    let mut mid_package = MastPackage::read_from_bytes(
        &build_context
            .assemble_library_package(&mid_manifest, None)
            .expect("mid package build should succeed")
            .to_bytes(),
    )
    .expect("mid package should deserialize");
    mid_package
        .sections
        .push(Section::new(SectionId::KERNEL, kernel_package.to_bytes()));
    mid_package.write_to_file(&mid_package_path).unwrap();

    let root_manifest =
        write_preassembled_kernel_executable_project(tempdir.path(), &mid_package_path);
    let mut context = TestContext::new();
    context.registry_mut().add_package(kernel_package.clone());
    let removed = context
        .registry_mut()
        .remove_package(&PackageId::from("kernelpkg"), &kernel_version);
    assert!(removed.is_some(), "expected indexed kernel artifact to exist");
    context.registry().clear_loaded_packages();

    let package = context
        .assemble_executable_package(&root_manifest, Some("main"), None)
        .expect("embedded kernel should be used when the indexed artifact is unreadable");
    let embedded_kernel_package = package
        .sections
        .iter()
        .find(|section| section.id == SectionId::KERNEL)
        .map(|section| MastPackage::read_from_bytes(section.data.as_ref()).unwrap())
        .expect("executable package should embed the fallback kernel package");
    assert_eq!(embedded_kernel_package.version, kernel_package.version);
    assert_eq!(embedded_kernel_package.digest(), kernel_package.digest());
    assert_eq!(
        context.registry().loaded_packages(),
        vec![format!("kernelpkg@{}", kernel_version)]
    );

    let round_tripped_program = MastPackage::read_from_bytes(&package.to_bytes())
        .expect("serialized executable package should round-trip")
        .try_into_program()
        .expect("program reconstruction should use the embedded fallback kernel");
    assert_eq!(round_tripped_program.kernel(), &expected_kernel);
}

#[test]
fn preassembled_libraries_without_store_or_embedded_kernel_leave_runtime_kernel_to_caller() {
    let tempdir = TempDir::new().unwrap();
    write_transitive_kernel_program_project(tempdir.path());
    let mid_manifest = tempdir.path().join("mid").join("miden-project.toml");
    let mid_package_path = tempdir.path().join("mid.masp");

    let mut build_context = TestContext::new();
    build_context
        .assemble_library_package(&mid_manifest, None)
        .expect("mid package build should succeed")
        .write_to_file(&mid_package_path)
        .unwrap();

    let root_manifest =
        write_preassembled_kernel_executable_project(tempdir.path(), &mid_package_path);
    let mut context = TestContext::new();
    let package = context
        .assemble_executable_package(&root_manifest, Some("main"), None)
        .expect("executable package build should succeed without an available kernel artifact");

    assert!(
        package
            .manifest
            .dependencies()
            .any(|dependency| dependency.kind == TargetType::Kernel)
    );
    assert!(!package.sections.iter().any(|section| section.id == SectionId::KERNEL));

    let round_tripped_program = MastPackage::read_from_bytes(&package.to_bytes())
        .expect("serialized executable package should round-trip")
        .try_into_program()
        .expect("packages without a recoverable kernel should still convert to a program");
    assert!(round_tripped_program.kernel().is_empty());
}

#[test]
fn embedded_kernel_package_must_match_runtime_dependency() {
    let tempdir = TempDir::new().unwrap();
    let manifest_path = write_kernel_program_project(tempdir.path());

    let mut context = TestContext::new();
    let package = context
        .assemble_executable_package(&manifest_path, Some("main"), None)
        .expect("executable package build should succeed");
    let mut round_tripped_package = MastPackage::read_from_bytes(&package.to_bytes())
        .expect("serialized executable package should round-trip");
    let kernel_dependency = round_tripped_package
        .manifest
        .dependencies()
        .find(|dependency| dependency.kind == TargetType::Kernel)
        .cloned()
        .expect("executable package should record a kernel dependency");
    let embedded_kernel_section = round_tripped_package
        .sections
        .iter_mut()
        .find(|section| section.id == SectionId::KERNEL)
        .expect("executable package should embed a kernel package");
    let mut embedded_kernel_package =
        MastPackage::read_from_bytes(embedded_kernel_section.data.as_ref())
            .expect("embedded kernel package should deserialize");
    embedded_kernel_package.version = "2.0.0".parse().unwrap();
    embedded_kernel_section.data = embedded_kernel_package.to_bytes().into();

    let error = round_tripped_package
        .try_into_program()
        .expect_err("mismatched embedded kernel metadata should be rejected");
    let kernel_name = kernel_dependency.name.to_string();
    assert!(error.to_string().contains("does not match the embedded kernel package"));
    assert!(error.to_string().contains(&kernel_name));
}

#[test]
fn executable_packages_without_embedded_kernel_section_fall_back_to_empty_kernel() {
    let tempdir = TempDir::new().unwrap();
    let manifest_path = write_kernel_program_project(tempdir.path());

    let mut context = TestContext::new();
    let package = context
        .assemble_executable_package(&manifest_path, Some("main"), None)
        .expect("executable package build should succeed");
    let mut round_tripped_package = MastPackage::read_from_bytes(&package.to_bytes())
        .expect("serialized executable package should round-trip");
    round_tripped_package.sections.retain(|section| section.id != SectionId::KERNEL);

    let round_tripped_program = round_tripped_package
        .try_into_program()
        .expect("packages without embedded kernels should still convert to a program");

    assert!(round_tripped_program.kernel().is_empty());
}

#[test]
fn preassembled_dependency_must_match_graph_selected_artifact() {
    let tempdir = TempDir::new().unwrap();
    let dep_package_path = tempdir.path().join("dep.masp");
    let dep_v1 =
        MastPackage::generate("dep".into(), "1.0.0".parse().unwrap(), TargetType::Library, []);
    dep_v1.write_to_file(&dep_package_path).unwrap();

    let root_dir = tempdir.path().join("root");
    let root_manifest = root_dir.join("miden-project.toml");
    write_file(
        &root_manifest,
        r#"[package]
name = "root"
version = "1.0.0"

[lib]
path = "lib.masm"

[dependencies]
dep = { path = "../dep.masp" }
"#,
    );
    write_file(
        &root_dir.join("lib.masm"),
        r#"pub proc entry
    exec.::dep::foo
end
"#,
    );

    let mut context = TestContext::new();
    let mut project_assembler = context.project_assembler_for_path(&root_manifest).unwrap();
    let dep_v2 =
        MastPackage::generate("dep".into(), "1.0.1".parse().unwrap(), TargetType::Library, []);
    dep_v2.write_to_file(&dep_package_path).unwrap();

    let error = project_assembler
        .assemble(ProjectTargetSelector::Library, "dev")
        .expect_err("mutating the preassembled artifact after graph construction should fail");
    assert!(error.to_string().contains("no longer matches the dependency graph selection"));
}

fn write_kernel_program_project(root: &FsPath) -> PathBuf {
    let manifest_path = root.join("miden-project.toml");
    write_file(
        &manifest_path,
        r#"[package]
name = "app"
version = "1.0.0"

[lib]
kind = "kernel"
path = "kernel.masm"

[[bin]]
name = "main"
path = "main.masm"
"#,
    );
    write_file(
        &root.join("kernel.masm"),
        r#"pub proc foo
    caller
end
"#,
    );
    write_file(
        &root.join("main.masm"),
        r#"begin
    syscall.foo
end
"#,
    );

    manifest_path
}

fn write_transitive_kernel_program_project(root: &FsPath) -> (PathBuf, PathBuf) {
    let kernel_dir = root.join("kernel");
    let kernel_manifest = kernel_dir.join("miden-project.toml");
    write_file(
        &kernel_manifest,
        r#"[package]
name = "kernelpkg"
version = "1.0.0"

[lib]
kind = "kernel"
path = "kernel.masm"
"#,
    );
    write_file(
        &kernel_dir.join("kernel.masm"),
        r#"pub proc foo
    caller
end
"#,
    );

    let mid_dir = root.join("mid");
    write_file(
        &mid_dir.join("miden-project.toml"),
        r#"[package]
name = "mid"
version = "1.0.0"

[lib]
path = "lib.masm"
namespace = "deps::mid"

[dependencies]
kernelpkg = { path = "../kernel" }
"#,
    );
    write_file(
        &mid_dir.join("lib.masm"),
        r#"pub proc call_kernel
    syscall.foo
end
"#,
    );

    let root_dir = root.join("app");
    let root_manifest = root_dir.join("miden-project.toml");
    write_file(
        &root_manifest,
        r#"[package]
name = "app"
version = "1.0.0"

[[bin]]
name = "main"
path = "main.masm"

[dependencies]
mid = { path = "../mid", linkage = "static" }
"#,
    );
    write_file(
        &root_dir.join("main.masm"),
        r#"begin
    exec.::deps::mid::call_kernel
end
"#,
    );

    (root_manifest, kernel_manifest)
}

fn write_preassembled_kernel_executable_project(
    root: &FsPath,
    dependency_package_path: &FsPath,
) -> PathBuf {
    let manifest_path = root.join("preassembled-app").join("miden-project.toml");
    write_file(
        &manifest_path,
        &format!(
            r#"[package]
name = "app"
version = "1.0.0"

[[bin]]
name = "main"
path = "main.masm"

[dependencies]
mid = {{ path = "{}", linkage = "static" }}
"#,
            dependency_package_path.display()
        ),
    );
    write_file(
        &manifest_path.parent().unwrap().join("main.masm"),
        r#"begin
    exec.::deps::mid::call_kernel
end
"#,
    );

    manifest_path
}
