use std::{
    collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
    env, fs, io,
    path::{Path, PathBuf},
};

use crate::{
    ast::{MatchPatternKind, Program},
    check::{
        self, CheckedFunction, CheckedProgram, ExternalEnum, ExternalEnumVariant, ExternalFunction,
        ExternalStruct, TExpr, TExprKind,
    },
    diag::Diagnostic,
    lexer, llvm, lower,
    sexpr::{Atom, SExpr, SExprKind},
    std_runtime, test_runner,
    token::Span,
    types::Type,
};

pub struct ProjectOutput {
    pub text: String,
    pub sources: Vec<SourceFile>,
    pub artifact: ProjectArtifact,
}

pub struct ProjectTestSuccess {
    pub output: String,
    pub report: test_runner::TestReport,
    pub sources: Vec<SourceFile>,
    pub artifact: ProjectArtifact,
}

pub struct ProjectTestFailure {
    pub diagnostics: Vec<Diagnostic>,
    pub report: Option<test_runner::TestReport>,
    pub sources: Vec<SourceFile>,
    pub artifact: Option<ProjectArtifact>,
}

pub struct ToolProject {
    pub sources: Vec<SourceFile>,
    pub artifact: ProjectArtifact,
}

pub struct ToolFailure {
    pub diagnostics: Vec<Diagnostic>,
    pub sources: Vec<SourceFile>,
    pub artifact: Option<ProjectArtifact>,
}

#[derive(Debug, Clone)]
pub struct SourceFile {
    pub path: String,
    pub source: String,
}

#[derive(Debug, Clone)]
pub struct ProjectArtifact {
    pub manifest_path: String,
    pub project_root: String,
    pub source_root: String,
    pub project_name: String,
    pub entry: String,
    pub modules: Vec<ProjectArtifactModule>,
    pub workspace: Option<WorkspaceArtifact>,
}

#[derive(Debug, Clone)]
pub struct ProjectArtifactModule {
    pub name: String,
    pub path: String,
    pub imports: Vec<String>,
    pub c_imports: Vec<ProjectArtifactCImport>,
}

#[derive(Debug, Clone)]
pub struct ProjectArtifactCImport {
    pub source_module: String,
    pub name: String,
    pub symbol: String,
    pub params: Vec<String>,
    pub return_type: String,
    pub abi: String,
}

#[derive(Debug, Clone)]
pub struct WorkspaceArtifact {
    pub workspace_root: String,
    pub workspace_manifest: String,
    pub members: Vec<String>,
    pub packages: Vec<WorkspaceArtifactPackage>,
    pub dependencies: Vec<WorkspaceArtifactDependency>,
    pub selected_build_entry_package: Option<String>,
}

#[derive(Debug, Clone)]
pub struct WorkspaceArtifactPackage {
    pub name: String,
    pub version: String,
    pub root: String,
    pub manifest: String,
    pub source_root: String,
    pub entry: String,
    pub modules: Vec<ProjectArtifactModule>,
    pub test_count: usize,
}

#[derive(Debug, Clone)]
pub struct WorkspaceArtifactDependency {
    pub from: String,
    pub to: String,
    pub kind: String,
    pub path: String,
}

#[derive(Clone)]
struct Manifest {
    path: PathBuf,
    source: String,
    project_root: PathBuf,
    name: String,
    source_root: String,
    entry: String,
}

#[derive(Clone)]
struct WorkspaceManifest {
    path: PathBuf,
    source: String,
    root: PathBuf,
    members: Vec<String>,
    default_package: Option<String>,
}

#[derive(Clone)]
struct PackageManifest {
    path: PathBuf,
    root: PathBuf,
    member: String,
    name: String,
    version: String,
    source_root: String,
    entry: String,
    dependencies: Vec<PackageDependency>,
}

#[derive(Clone)]
struct PackageDependency {
    key: String,
    path: String,
    span: Span,
}

#[derive(Clone)]
struct PackageUnit {
    manifest: PackageManifest,
    modules: Vec<ModuleUnit>,
    dependency_indices: Vec<usize>,
}

#[derive(Debug, Clone)]
struct WorkspaceImportBinding {
    provider_package: usize,
    provider_module: usize,
    kind: DeclKind,
    import_span: Span,
}

#[derive(Clone)]
struct ModuleUnit {
    name: String,
    file: String,
    exports: Vec<NameSpan>,
    imports: Vec<ImportDecl>,
    program: Program,
    local_functions: HashMap<String, DeclInfo>,
    local_structs: HashMap<String, DeclInfo>,
    local_enums: HashMap<String, DeclInfo>,
    local_aliases: HashMap<String, DeclInfo>,
}

#[derive(Debug, Clone)]
struct ImportDecl {
    module: String,
    module_span: Span,
    names: Vec<NameSpan>,
}

#[derive(Debug, Clone)]
struct NameSpan {
    name: String,
    span: Span,
}

#[derive(Debug, Clone)]
struct DeclInfo {
    span: Span,
    kind: DeclKind,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DeclKind {
    Function,
    CImport,
    Struct,
    Enum,
    TypeAlias,
}

#[derive(Debug, Clone)]
struct ImportBinding {
    provider: String,
    kind: DeclKind,
    import_span: Span,
}

pub fn is_project_input(path: &str) -> bool {
    let path = Path::new(path);
    if path.is_dir() {
        return path.join("slovo.toml").is_file();
    }
    path.file_name().and_then(|name| name.to_str()) == Some("slovo.toml") && path.is_file()
}

pub fn load_project_sources_for_tools(input: &str) -> Result<ToolProject, ToolFailure> {
    let manifest_path = manifest_path(input);
    let manifest_file = manifest_path.display().to_string();
    let manifest_source = fs::read_to_string(&manifest_path).map_err(|err| ToolFailure {
        diagnostics: vec![Diagnostic::new(
            &manifest_file,
            "ProjectManifestReadFailed",
            format!("cannot read project manifest `{}`: {}", manifest_file, err),
        )],
        sources: Vec::new(),
        artifact: None,
    })?;

    if is_workspace_manifest_source(&manifest_source) {
        return load_workspace_sources_for_tools(manifest_path, manifest_source);
    }

    let manifest =
        parse_manifest(manifest_path, manifest_source.clone()).map_err(|diagnostics| {
            ToolFailure {
                diagnostics,
                sources: vec![SourceFile {
                    path: manifest_file.clone(),
                    source: manifest_source.clone(),
                }],
                artifact: None,
            }
        })?;

    let mut sources = vec![SourceFile {
        path: manifest.path.display().to_string(),
        source: manifest.source.clone(),
    }];
    let artifact = empty_artifact(&manifest);
    let source_root = manifest.project_root.join(&manifest.source_root);
    if !source_root.is_dir() {
        return Err(ToolFailure {
            diagnostics: vec![Diagnostic::new(
                &manifest.path.display().to_string(),
                "ProjectSourceRootMissing",
                format!(
                    "project source root `{}` does not exist",
                    source_root.display()
                ),
            )],
            sources,
            artifact: Some(artifact),
        });
    }
    if let Err(diagnostic) = validate_source_root_boundary(&manifest, &source_root) {
        return Err(ToolFailure {
            diagnostics: vec![diagnostic],
            sources,
            artifact: Some(artifact),
        });
    }
    let canonical_source_root = fs::canonicalize(&source_root).ok();

    let module_paths = discover_module_paths(&source_root).map_err(|err| ToolFailure {
        diagnostics: vec![Diagnostic::new(
            &manifest.path.display().to_string(),
            "ProjectSourceReadFailed",
            format!(
                "cannot read source root `{}`: {}",
                source_root.display(),
                err
            ),
        )],
        sources: sources.clone(),
        artifact: Some(artifact.clone()),
    })?;
    if module_paths.is_empty() {
        return Err(ToolFailure {
            diagnostics: vec![Diagnostic::new(
                &manifest.path.display().to_string(),
                "ProjectSourceReadFailed",
                format!(
                    "project source root `{}` contains no `.slo` modules",
                    source_root.display()
                ),
            )],
            sources,
            artifact: Some(artifact),
        });
    }

    let mut modules = Vec::new();
    let mut diagnostics = Vec::new();
    for path in module_paths {
        let file = path.display().to_string();
        if let Some(root) = &canonical_source_root {
            match fs::canonicalize(&path) {
                Ok(canonical) if canonical.starts_with(root) => {}
                Ok(_) => {
                    diagnostics.push(Diagnostic::new(
                        &manifest.path.display().to_string(),
                        "ProjectManifestInvalid",
                        format!("project module `{}` escapes the source root", file),
                    ));
                    continue;
                }
                Err(err) => {
                    diagnostics.push(Diagnostic::new(
                        &manifest.path.display().to_string(),
                        "ProjectSourceReadFailed",
                        format!("cannot canonicalize module `{}`: {}", file, err),
                    ));
                    continue;
                }
            }
        }
        match fs::read_to_string(&path) {
            Ok(source) => {
                sources.push(SourceFile {
                    path: file.clone(),
                    source: source.clone(),
                });
                modules.push(ProjectArtifactModule {
                    name: path
                        .file_stem()
                        .and_then(|stem| stem.to_str())
                        .unwrap_or("")
                        .to_string(),
                    path: file,
                    imports: tool_imports(&source),
                    c_imports: tool_c_imports(&source),
                });
            }
            Err(err) => diagnostics.push(Diagnostic::new(
                &manifest.path.display().to_string(),
                "ProjectSourceReadFailed",
                format!("cannot read module `{}`: {}", file, err),
            )),
        }
    }
    load_tool_standard_import_modules(&manifest, &mut modules, &mut sources, &mut diagnostics);

    let artifact = ProjectArtifact {
        modules,
        ..artifact
    };
    if diagnostics.is_empty() {
        Ok(ToolProject {
            sources: sources.into_iter().skip(1).collect(),
            artifact,
        })
    } else {
        Err(ToolFailure {
            diagnostics,
            sources,
            artifact: Some(artifact),
        })
    }
}

pub fn compile_to_llvm(input: &str) -> Result<ProjectOutput, ProjectTestFailure> {
    let checked = load_checked_project(input, true).map_err(|failure| ProjectTestFailure {
        diagnostics: failure.diagnostics,
        report: None,
        sources: failure.sources,
        artifact: failure.artifact,
    })?;
    let llvm =
        llvm::emit(&checked.artifact.manifest_path, &checked.program).map_err(|diagnostics| {
            ProjectTestFailure {
                diagnostics,
                report: None,
                sources: checked.sources.clone(),
                artifact: Some(checked.artifact.clone()),
            }
        })?;

    Ok(ProjectOutput {
        text: llvm,
        sources: checked.sources,
        artifact: checked.artifact,
    })
}

pub fn check_project(input: &str) -> Result<ProjectOutput, ProjectTestFailure> {
    let checked = load_checked_project(input, false).map_err(|failure| ProjectTestFailure {
        diagnostics: failure.diagnostics,
        report: None,
        sources: failure.sources,
        artifact: failure.artifact,
    })?;
    llvm::emit(&checked.artifact.manifest_path, &checked.program).map_err(|diagnostics| {
        ProjectTestFailure {
            diagnostics,
            report: None,
            sources: checked.sources.clone(),
            artifact: Some(checked.artifact.clone()),
        }
    })?;

    Ok(ProjectOutput {
        text: String::new(),
        sources: checked.sources,
        artifact: checked.artifact,
    })
}

pub fn run_tests(
    input: &str,
    filter: Option<&str>,
) -> Result<ProjectTestSuccess, ProjectTestFailure> {
    let checked = load_checked_project(input, false).map_err(|failure| ProjectTestFailure {
        diagnostics: failure.diagnostics,
        report: None,
        sources: failure.sources,
        artifact: failure.artifact,
    })?;

    match test_runner::run(&checked.artifact.manifest_path, &checked.program, filter) {
        Ok(success) => Ok(ProjectTestSuccess {
            output: success.output,
            report: success.report,
            sources: checked.sources,
            artifact: checked.artifact,
        }),
        Err(failure) => Err(ProjectTestFailure {
            diagnostics: failure.diagnostics,
            report: failure.report,
            sources: checked.sources,
            artifact: Some(checked.artifact),
        }),
    }
}

pub fn list_tests(
    input: &str,
    filter: Option<&str>,
) -> Result<ProjectTestSuccess, ProjectTestFailure> {
    let checked = load_checked_project(input, false).map_err(|failure| ProjectTestFailure {
        diagnostics: failure.diagnostics,
        report: None,
        sources: failure.sources,
        artifact: failure.artifact,
    })?;

    let success = test_runner::list(&checked.program, filter);
    Ok(ProjectTestSuccess {
        output: success.output,
        report: success.report,
        sources: checked.sources,
        artifact: checked.artifact,
    })
}

struct CheckedProject {
    program: CheckedProgram,
    sources: Vec<SourceFile>,
    artifact: ProjectArtifact,
}

struct ProjectLoadFailure {
    diagnostics: Vec<Diagnostic>,
    sources: Vec<SourceFile>,
    artifact: Option<ProjectArtifact>,
}

fn load_checked_project(
    input: &str,
    require_entry_wrapper: bool,
) -> Result<CheckedProject, ProjectLoadFailure> {
    let manifest_path = manifest_path(input);
    let manifest_file = manifest_path.display().to_string();
    let manifest_source = match fs::read_to_string(&manifest_path) {
        Ok(source) => source,
        Err(err) => {
            return Err(ProjectLoadFailure {
                diagnostics: vec![Diagnostic::new(
                    &manifest_file,
                    "ProjectManifestReadFailed",
                    format!("cannot read project manifest `{}`: {}", manifest_file, err),
                )],
                sources: Vec::new(),
                artifact: None,
            });
        }
    };

    if is_workspace_manifest_source(&manifest_source) {
        return load_checked_workspace(manifest_path, manifest_source, require_entry_wrapper);
    }

    let manifest = match parse_manifest(manifest_path, manifest_source.clone()) {
        Ok(manifest) => manifest,
        Err(diagnostics) => {
            return Err(ProjectLoadFailure {
                sources: vec![SourceFile {
                    path: manifest_file,
                    source: manifest_source,
                }],
                diagnostics,
                artifact: None,
            });
        }
    };

    let mut sources = vec![SourceFile {
        path: manifest.path.display().to_string(),
        source: manifest.source.clone(),
    }];

    let mut artifact = empty_artifact(&manifest);
    let source_root = manifest.project_root.join(&manifest.source_root);
    let source_root_file = source_root.display().to_string();
    if !source_root.is_dir() {
        return Err(ProjectLoadFailure {
            diagnostics: vec![Diagnostic::new(
                &manifest.path.display().to_string(),
                "ProjectSourceRootMissing",
                format!("project source root `{}` does not exist", source_root_file),
            )],
            sources,
            artifact: Some(artifact),
        });
    }

    if let Err(diagnostic) = validate_source_root_boundary(&manifest, &source_root) {
        return Err(ProjectLoadFailure {
            diagnostics: vec![diagnostic],
            sources,
            artifact: Some(artifact),
        });
    }
    let canonical_source_root = fs::canonicalize(&source_root).ok();

    let module_paths = match discover_module_paths(&source_root) {
        Ok(paths) => paths,
        Err(err) => {
            return Err(ProjectLoadFailure {
                diagnostics: vec![Diagnostic::new(
                    &manifest.path.display().to_string(),
                    "ProjectSourceReadFailed",
                    format!("cannot read source root `{}`: {}", source_root_file, err),
                )],
                sources,
                artifact: Some(artifact),
            });
        }
    };

    if module_paths.is_empty() {
        return Err(ProjectLoadFailure {
            diagnostics: vec![Diagnostic::new(
                &manifest.path.display().to_string(),
                "ProjectSourceReadFailed",
                format!(
                    "project source root `{}` contains no `.slo` modules",
                    source_root_file
                ),
            )],
            sources,
            artifact: Some(artifact),
        });
    }

    let mut modules = Vec::new();
    let mut diagnostics = Vec::new();
    for path in module_paths {
        let file = path.display().to_string();
        if let Some(root) = &canonical_source_root {
            match fs::canonicalize(&path) {
                Ok(canonical) if canonical.starts_with(root) => {}
                Ok(_) => {
                    diagnostics.push(Diagnostic::new(
                        &manifest.path.display().to_string(),
                        "ProjectManifestInvalid",
                        format!("project module `{}` escapes the source root", file),
                    ));
                    continue;
                }
                Err(err) => {
                    diagnostics.push(Diagnostic::new(
                        &manifest.path.display().to_string(),
                        "ProjectSourceReadFailed",
                        format!("cannot canonicalize module `{}`: {}", file, err),
                    ));
                    continue;
                }
            }
        }
        match fs::read_to_string(&path) {
            Ok(source) => {
                sources.push(SourceFile {
                    path: file.clone(),
                    source: source.clone(),
                });
                match parse_module(&path, file, source) {
                    Ok(module) => modules.push(module),
                    Err(mut errs) => diagnostics.append(&mut errs),
                }
            }
            Err(err) => diagnostics.push(Diagnostic::new(
                &manifest.path.display().to_string(),
                "ProjectSourceReadFailed",
                format!("cannot read module `{}`: {}", file, err),
            )),
        }
    }
    load_standard_import_modules(&manifest, &mut modules, &mut sources, &mut diagnostics);

    artifact = artifact_from_modules(&manifest, &modules, None);

    if !diagnostics.is_empty() {
        return Err(ProjectLoadFailure {
            diagnostics,
            sources,
            artifact: Some(artifact),
        });
    }

    match resolve_and_check(manifest, modules, sources, require_entry_wrapper) {
        Ok(checked) => Ok(checked),
        Err(failure) => Err(failure),
    }
}

struct WorkspaceLoaded {
    workspace: WorkspaceManifest,
    packages: Vec<PackageUnit>,
    sources: Vec<SourceFile>,
    artifact: ProjectArtifact,
}

fn is_workspace_manifest_source(source: &str) -> bool {
    manifest_lines(source)
        .iter()
        .any(|line| line.text.trim() == "[workspace]")
}

fn load_workspace_sources_for_tools(
    manifest_path: PathBuf,
    manifest_source: String,
) -> Result<ToolProject, ToolFailure> {
    match load_workspace_base(manifest_path, manifest_source, None) {
        Ok(loaded) => Ok(ToolProject {
            sources: loaded
                .sources
                .into_iter()
                .filter(|source| source.path.ends_with(".slo"))
                .collect(),
            artifact: loaded.artifact,
        }),
        Err(failure) => Err(ToolFailure {
            diagnostics: failure.diagnostics,
            sources: failure.sources,
            artifact: failure.artifact,
        }),
    }
}

fn load_checked_workspace(
    manifest_path: PathBuf,
    manifest_source: String,
    require_entry_wrapper: bool,
) -> Result<CheckedProject, ProjectLoadFailure> {
    let loaded = load_workspace_base(manifest_path, manifest_source, None)?;
    resolve_and_check_workspace(loaded, require_entry_wrapper)
}

fn load_workspace_base(
    manifest_path: PathBuf,
    manifest_source: String,
    selected_build_entry_package: Option<String>,
) -> Result<WorkspaceLoaded, ProjectLoadFailure> {
    let manifest_file = manifest_path.display().to_string();
    let workspace = match parse_workspace_manifest(manifest_path, manifest_source.clone()) {
        Ok(workspace) => workspace,
        Err(diagnostics) => {
            return Err(ProjectLoadFailure {
                sources: vec![SourceFile {
                    path: manifest_file,
                    source: manifest_source,
                }],
                diagnostics,
                artifact: None,
            });
        }
    };

    let mut sources = vec![SourceFile {
        path: workspace.path.display().to_string(),
        source: workspace.source.clone(),
    }];
    let mut diagnostics = Vec::new();
    let mut packages = Vec::new();

    for member in &workspace.members {
        let package_root = workspace.root.join(member);
        let package_manifest = package_root.join("slovo.toml");
        let package_file = package_manifest.display().to_string();
        let package_source = match fs::read_to_string(&package_manifest) {
            Ok(source) => source,
            Err(err) => {
                diagnostics.push(Diagnostic::new(
                    &workspace.path.display().to_string(),
                    "WorkspaceMemberManifestMissing",
                    format!(
                        "workspace member `{}` must contain `slovo.toml`: {}",
                        member, err
                    ),
                ));
                continue;
            }
        };
        sources.push(SourceFile {
            path: package_file.clone(),
            source: package_source.clone(),
        });

        let manifest = match parse_package_manifest(
            package_manifest,
            package_source,
            package_root,
            member.clone(),
        ) {
            Ok(manifest) => manifest,
            Err(mut errs) => {
                diagnostics.append(&mut errs);
                continue;
            }
        };

        if let Err(diagnostic) = validate_package_root_boundary(&workspace, &manifest) {
            diagnostics.push(diagnostic);
            continue;
        }

        let source_root = manifest.root.join(&manifest.source_root);
        let source_root_file = source_root.display().to_string();
        if !source_root.is_dir() {
            diagnostics.push(Diagnostic::new(
                &manifest.path.display().to_string(),
                "PackageSourceRootMissing",
                format!("package source root `{}` does not exist", source_root_file),
            ));
            continue;
        }
        if let Err(diagnostic) = validate_package_source_root_boundary(&manifest, &source_root) {
            diagnostics.push(diagnostic);
            continue;
        }
        let canonical_source_root = fs::canonicalize(&source_root).ok();
        let module_paths = match discover_module_paths(&source_root) {
            Ok(paths) => paths,
            Err(err) => {
                diagnostics.push(Diagnostic::new(
                    &manifest.path.display().to_string(),
                    "PackageSourceReadFailed",
                    format!("cannot read source root `{}`: {}", source_root_file, err),
                ));
                continue;
            }
        };
        if module_paths.is_empty() {
            diagnostics.push(Diagnostic::new(
                &manifest.path.display().to_string(),
                "PackageSourceReadFailed",
                format!(
                    "package source root `{}` contains no `.slo` modules",
                    source_root_file
                ),
            ));
            continue;
        }

        let mut modules = Vec::new();
        for path in module_paths {
            let file = path.display().to_string();
            if let Some(root) = &canonical_source_root {
                match fs::canonicalize(&path) {
                    Ok(canonical) if canonical.starts_with(root) => {}
                    Ok(_) => {
                        diagnostics.push(Diagnostic::new(
                            &manifest.path.display().to_string(),
                            "PackageManifestInvalid",
                            format!("package module `{}` escapes the source root", file),
                        ));
                        continue;
                    }
                    Err(err) => {
                        diagnostics.push(Diagnostic::new(
                            &manifest.path.display().to_string(),
                            "PackageSourceReadFailed",
                            format!("cannot canonicalize module `{}`: {}", file, err),
                        ));
                        continue;
                    }
                }
            }
            match fs::read_to_string(&path) {
                Ok(source) => {
                    sources.push(SourceFile {
                        path: file.clone(),
                        source: source.clone(),
                    });
                    match parse_module(&path, file, source) {
                        Ok(module) => modules.push(module),
                        Err(mut errs) => diagnostics.append(&mut errs),
                    }
                }
                Err(err) => diagnostics.push(Diagnostic::new(
                    &manifest.path.display().to_string(),
                    "PackageSourceReadFailed",
                    format!("cannot read module `{}`: {}", file, err),
                )),
            }
        }
        load_package_standard_import_modules(
            &manifest,
            &mut modules,
            &mut sources,
            &mut diagnostics,
        );
        packages.push(PackageUnit {
            manifest,
            modules,
            dependency_indices: Vec::new(),
        });
    }

    validate_workspace_packages(&workspace, &mut packages, &mut diagnostics);
    let artifact = workspace_artifact(
        &workspace,
        &packages,
        None,
        None,
        selected_build_entry_package,
    );

    if !diagnostics.is_empty() {
        return Err(ProjectLoadFailure {
            diagnostics,
            sources,
            artifact: Some(artifact),
        });
    }

    Ok(WorkspaceLoaded {
        workspace,
        packages,
        sources,
        artifact,
    })
}

fn manifest_path(input: &str) -> PathBuf {
    let path = Path::new(input);
    if path.is_dir() {
        path.join("slovo.toml")
    } else {
        path.to_path_buf()
    }
}

fn empty_artifact(manifest: &Manifest) -> ProjectArtifact {
    ProjectArtifact {
        manifest_path: manifest.path.display().to_string(),
        project_root: manifest.project_root.display().to_string(),
        source_root: manifest.source_root.clone(),
        project_name: manifest.name.clone(),
        entry: manifest.entry.clone(),
        modules: Vec::new(),
        workspace: None,
    }
}

fn artifact_from_modules(
    manifest: &Manifest,
    modules: &[ModuleUnit],
    order: Option<&[usize]>,
) -> ProjectArtifact {
    let module_indices = order
        .map(|order| order.to_vec())
        .unwrap_or_else(|| (0..modules.len()).collect());
    ProjectArtifact {
        manifest_path: manifest.path.display().to_string(),
        project_root: manifest.project_root.display().to_string(),
        source_root: manifest.source_root.clone(),
        project_name: manifest.name.clone(),
        entry: manifest.entry.clone(),
        modules: module_indices
            .iter()
            .map(|index| ProjectArtifactModule {
                name: modules[*index].name.clone(),
                path: modules[*index].file.clone(),
                imports: modules[*index]
                    .imports
                    .iter()
                    .map(|import| import.module.clone())
                    .collect(),
                c_imports: modules[*index]
                    .program
                    .c_imports
                    .iter()
                    .map(|import| project_artifact_c_import(&modules[*index].name, import))
                    .collect(),
            })
            .collect(),
        workspace: None,
    }
}

fn workspace_artifact(
    workspace: &WorkspaceManifest,
    packages: &[PackageUnit],
    package_order: Option<&[usize]>,
    module_orders: Option<&HashMap<usize, Vec<usize>>>,
    selected_build_entry_package: Option<String>,
) -> ProjectArtifact {
    let mut package_indices = package_order
        .map(|order| order.to_vec())
        .unwrap_or_else(|| (0..packages.len()).collect());
    if package_order.is_none() {
        package_indices.sort_by(|left, right| {
            packages[*left]
                .manifest
                .name
                .cmp(&packages[*right].manifest.name)
                .then_with(|| {
                    packages[*left]
                        .manifest
                        .member
                        .cmp(&packages[*right].manifest.member)
                })
        });
    }

    let mut flat_modules = Vec::new();
    let mut artifact_packages = Vec::new();
    for package_index in &package_indices {
        let package = &packages[*package_index];
        let module_indices = module_orders
            .and_then(|orders| orders.get(package_index))
            .cloned()
            .unwrap_or_else(|| (0..package.modules.len()).collect());
        let modules = module_indices
            .iter()
            .map(|module_index| {
                let module = &package.modules[*module_index];
                ProjectArtifactModule {
                    name: module.name.clone(),
                    path: module.file.clone(),
                    imports: module
                        .imports
                        .iter()
                        .map(|import| import.module.clone())
                        .collect(),
                    c_imports: module
                        .program
                        .c_imports
                        .iter()
                        .map(|import| project_artifact_c_import(&module.name, import))
                        .collect(),
                }
            })
            .collect::<Vec<_>>();
        for module in &modules {
            let qualified_module = format!("{}.{}", package.manifest.name, module.name);
            flat_modules.push(ProjectArtifactModule {
                name: qualified_module.clone(),
                path: module.path.clone(),
                imports: module.imports.clone(),
                c_imports: module
                    .c_imports
                    .iter()
                    .map(|import| {
                        let mut import = import.clone();
                        import.source_module = qualified_module.clone();
                        import
                    })
                    .collect(),
            });
        }
        artifact_packages.push(WorkspaceArtifactPackage {
            name: package.manifest.name.clone(),
            version: package.manifest.version.clone(),
            root: package.manifest.root.display().to_string(),
            manifest: package.manifest.path.display().to_string(),
            source_root: package.manifest.source_root.clone(),
            entry: package.manifest.entry.clone(),
            test_count: package
                .modules
                .iter()
                .map(|module| module.program.tests.len())
                .sum(),
            modules,
        });
    }

    let mut dependencies = Vec::new();
    for (from_index, package) in packages.iter().enumerate() {
        for to_index in &package.dependency_indices {
            dependencies.push(WorkspaceArtifactDependency {
                from: packages[from_index].manifest.name.clone(),
                to: packages[*to_index].manifest.name.clone(),
                kind: "local-path".to_string(),
                path: packages[*to_index].manifest.member.clone(),
            });
        }
    }
    dependencies.sort_by(|left, right| {
        left.from
            .cmp(&right.from)
            .then_with(|| left.to.cmp(&right.to))
            .then_with(|| left.path.cmp(&right.path))
    });

    ProjectArtifact {
        manifest_path: workspace.path.display().to_string(),
        project_root: workspace.root.display().to_string(),
        source_root: String::new(),
        project_name: workspace
            .root
            .file_name()
            .and_then(|name| name.to_str())
            .unwrap_or("workspace")
            .to_string(),
        entry: selected_build_entry_package
            .clone()
            .unwrap_or_else(|| "main".to_string()),
        modules: flat_modules,
        workspace: Some(WorkspaceArtifact {
            workspace_root: workspace.root.display().to_string(),
            workspace_manifest: workspace.path.display().to_string(),
            members: workspace.members.clone(),
            packages: artifact_packages,
            dependencies,
            selected_build_entry_package,
        }),
    }
}

fn parse_manifest(path: PathBuf, source: String) -> Result<Manifest, Vec<Diagnostic>> {
    let file = path.display().to_string();
    let mut errors = Vec::new();
    let mut in_project = false;
    let mut saw_project = false;
    let mut name = None::<String>;
    let mut source_root = None::<String>;
    let mut entry = None::<String>;

    for line in manifest_lines(&source) {
        let trimmed = line.text.trim();
        if trimmed.is_empty() || trimmed.starts_with('#') {
            continue;
        }
        if trimmed.starts_with('[') {
            if trimmed == "[project]" {
                in_project = true;
                saw_project = true;
            } else {
                errors.push(
                    Diagnostic::new(&file, "ProjectManifestInvalid", "unknown manifest section")
                        .with_span(line.span),
                );
                in_project = false;
            }
            continue;
        }

        if !in_project {
            errors.push(
                Diagnostic::new(
                    &file,
                    "ProjectManifestInvalid",
                    "manifest keys must be inside `[project]`",
                )
                .with_span(line.span),
            );
            continue;
        }

        let Some((key, value)) = trimmed.split_once('=') else {
            errors.push(
                Diagnostic::new(&file, "ProjectManifestInvalid", "manifest key must use `=`")
                    .with_span(line.span),
            );
            continue;
        };
        let key = key.trim();
        let value = value.trim();
        let parsed = match parse_manifest_string(value) {
            Some(value) => value,
            None => {
                errors.push(
                    Diagnostic::new(
                        &file,
                        "ProjectManifestInvalid",
                        "manifest values must be strings",
                    )
                    .with_span(line.span),
                );
                continue;
            }
        };

        match key {
            "name" => set_manifest_key(
                &file,
                &mut errors,
                &mut name,
                parsed,
                line.span,
                "name",
                "ProjectManifestInvalid",
                "project",
            ),
            "source_root" => set_manifest_key(
                &file,
                &mut errors,
                &mut source_root,
                parsed,
                line.span,
                "source_root",
                "ProjectManifestInvalid",
                "project",
            ),
            "entry" => set_manifest_key(
                &file,
                &mut errors,
                &mut entry,
                parsed,
                line.span,
                "entry",
                "ProjectManifestInvalid",
                "project",
            ),
            _ => errors.push(
                Diagnostic::new(
                    &file,
                    "ProjectManifestInvalid",
                    format!("unknown project manifest key `{}`", key),
                )
                .with_span(line.span),
            ),
        }
    }

    if !saw_project {
        errors.push(Diagnostic::new(
            &file,
            "ProjectManifestInvalid",
            "project manifest is missing `[project]` section",
        ));
    }

    let Some(name) = name else {
        errors.push(Diagnostic::new(
            &file,
            "ProjectManifestInvalid",
            "project manifest is missing required key `name`",
        ));
        return Err(errors);
    };

    if !is_project_name(&name) {
        errors.push(Diagnostic::new(
            &file,
            "ProjectManifestInvalid",
            "project name must be lowercase ASCII and may contain only `a-z`, `0-9`, and `-` after the first letter",
        ));
    }

    let source_root = source_root.unwrap_or_else(|| "src".to_string());
    if !is_safe_relative_source_root(&source_root) {
        errors.push(Diagnostic::new(
            &file,
            "ProjectManifestInvalid",
            "project source_root must be a relative path under the project root",
        ));
    }

    let entry = entry.unwrap_or_else(|| "main".to_string());
    if !is_flat_module_identifier(&entry) {
        errors.push(Diagnostic::new(
            &file,
            "ProjectManifestInvalid",
            "project entry must be a flat module identifier",
        ));
    }

    if !errors.is_empty() {
        return Err(errors);
    }

    let project_root = path
        .parent()
        .filter(|parent| !parent.as_os_str().is_empty())
        .unwrap_or_else(|| Path::new("."))
        .to_path_buf();

    Ok(Manifest {
        path,
        source,
        project_root,
        name,
        source_root,
        entry,
    })
}

fn parse_workspace_manifest(
    path: PathBuf,
    source: String,
) -> Result<WorkspaceManifest, Vec<Diagnostic>> {
    let file = path.display().to_string();
    let mut errors = Vec::new();
    let mut in_workspace = false;
    let mut saw_workspace = false;
    let mut members = None::<Vec<String>>;
    let mut default_package = None::<String>;

    for line in manifest_lines(&source) {
        let trimmed = line.text.trim();
        if trimmed.is_empty() || trimmed.starts_with('#') {
            continue;
        }
        if trimmed.starts_with('[') {
            if trimmed == "[workspace]" {
                in_workspace = true;
                saw_workspace = true;
            } else {
                errors.push(
                    Diagnostic::new(
                        &file,
                        "WorkspaceManifestInvalid",
                        "unknown workspace manifest section",
                    )
                    .with_span(line.span),
                );
                in_workspace = false;
            }
            continue;
        }

        if !in_workspace {
            errors.push(
                Diagnostic::new(
                    &file,
                    "WorkspaceManifestInvalid",
                    "workspace manifest keys must be inside `[workspace]`",
                )
                .with_span(line.span),
            );
            continue;
        }

        let Some((key, value)) = trimmed.split_once('=') else {
            errors.push(
                Diagnostic::new(
                    &file,
                    "WorkspaceManifestInvalid",
                    "workspace manifest key must use `=`",
                )
                .with_span(line.span),
            );
            continue;
        };
        match key.trim() {
            "members" => match parse_manifest_string_array(value.trim()) {
                Some(parsed) if !parsed.is_empty() => {
                    if members.replace(parsed).is_some() {
                        errors.push(
                            Diagnostic::new(
                                &file,
                                "WorkspaceManifestInvalid",
                                "duplicate workspace manifest key `members`",
                            )
                            .with_span(line.span),
                        );
                    }
                }
                _ => errors.push(
                    Diagnostic::new(
                        &file,
                        "WorkspaceManifestInvalid",
                        "workspace members must be a non-empty array of strings",
                    )
                    .with_span(line.span),
                ),
            },
            "default_package" => match parse_manifest_string(value.trim()) {
                Some(parsed) => {
                    if default_package.replace(parsed).is_some() {
                        errors.push(
                            Diagnostic::new(
                                &file,
                                "WorkspaceManifestInvalid",
                                "duplicate workspace manifest key `default_package`",
                            )
                            .with_span(line.span),
                        );
                    }
                }
                None => errors.push(
                    Diagnostic::new(
                        &file,
                        "WorkspaceManifestInvalid",
                        "workspace default_package must be a string",
                    )
                    .with_span(line.span),
                ),
            },
            other => errors.push(
                Diagnostic::new(
                    &file,
                    "WorkspaceManifestInvalid",
                    format!("unknown workspace manifest key `{}`", other),
                )
                .with_span(line.span),
            ),
        }
    }

    if !saw_workspace {
        errors.push(Diagnostic::new(
            &file,
            "WorkspaceManifestInvalid",
            "workspace manifest is missing `[workspace]` section",
        ));
    }

    let mut members = members.unwrap_or_else(|| {
        errors.push(Diagnostic::new(
            &file,
            "WorkspaceManifestInvalid",
            "workspace manifest is missing required key `members`",
        ));
        Vec::new()
    });

    let mut normalized_members = Vec::new();
    let mut seen_members = BTreeMap::<String, String>::new();
    for member in members {
        if let Some(normalized) = normalize_workspace_member(&member) {
            if let Some(original) = seen_members.insert(normalized.clone(), member.clone()) {
                errors.push(Diagnostic::new(
                    &file,
                    "DuplicateWorkspaceMember",
                    format!(
                        "workspace member path `{}` duplicates `{}` after normalization",
                        member, original
                    ),
                ));
            }
            normalized_members.push(normalized);
        } else {
            errors.push(Diagnostic::new(
                &file,
                "WorkspaceMemberPathEscape",
                format!(
                    "workspace member path `{}` must be relative and stay under the workspace root",
                    member
                ),
            ));
        }
    }
    members = normalized_members;
    members.sort();

    if let Some(default_package) = &default_package {
        if !is_project_name(default_package) {
            errors.push(Diagnostic::new(
                &file,
                "WorkspaceManifestInvalid",
                "workspace default_package must be a package name",
            ));
        }
    }

    if !errors.is_empty() {
        return Err(errors);
    }

    let root = path
        .parent()
        .filter(|parent| !parent.as_os_str().is_empty())
        .unwrap_or_else(|| Path::new("."))
        .to_path_buf();

    Ok(WorkspaceManifest {
        path,
        source,
        root,
        members,
        default_package,
    })
}

fn parse_package_manifest(
    path: PathBuf,
    source: String,
    root: PathBuf,
    member: String,
) -> Result<PackageManifest, Vec<Diagnostic>> {
    let file = path.display().to_string();
    let mut errors = Vec::new();
    let mut section = "";
    let mut saw_package = false;
    let mut saw_project = false;
    let mut name = None::<String>;
    let mut version = None::<String>;
    let mut source_root = None::<String>;
    let mut entry = None::<String>;
    let mut dependencies = Vec::new();
    let mut dependency_keys = BTreeSet::<String>::new();

    for line in manifest_lines(&source) {
        let trimmed = line.text.trim();
        if trimmed.is_empty() || trimmed.starts_with('#') {
            continue;
        }
        if trimmed.starts_with('[') {
            match trimmed {
                "[package]" => {
                    section = "package";
                    saw_package = true;
                }
                "[dependencies]" => section = "dependencies",
                "[project]" => {
                    section = "project";
                    saw_project = true;
                    errors.push(
                        Diagnostic::new(
                            &file,
                            "PackageManifestInvalid",
                            "workspace package manifest may not contain `[project]`",
                        )
                        .with_span(line.span),
                    );
                }
                _ => {
                    section = "";
                    errors.push(
                        Diagnostic::new(
                            &file,
                            "PackageManifestInvalid",
                            "unknown package manifest section",
                        )
                        .with_span(line.span),
                    );
                }
            }
            continue;
        }

        let Some((key, value)) = trimmed.split_once('=') else {
            errors.push(
                Diagnostic::new(
                    &file,
                    "PackageManifestInvalid",
                    "package manifest key must use `=`",
                )
                .with_span(line.span),
            );
            continue;
        };
        let key = key.trim();
        let value = value.trim();
        match section {
            "package" => {
                let parsed = match parse_manifest_string(value) {
                    Some(value) => value,
                    None => {
                        errors.push(
                            Diagnostic::new(
                                &file,
                                "PackageManifestInvalid",
                                "package manifest values must be strings",
                            )
                            .with_span(line.span),
                        );
                        continue;
                    }
                };
                match key {
                    "name" => set_manifest_key(
                        &file,
                        &mut errors,
                        &mut name,
                        parsed,
                        line.span,
                        "name",
                        "PackageManifestInvalid",
                        "package",
                    ),
                    "version" => set_manifest_key(
                        &file,
                        &mut errors,
                        &mut version,
                        parsed,
                        line.span,
                        "version",
                        "PackageManifestInvalid",
                        "package",
                    ),
                    "source_root" => set_manifest_key(
                        &file,
                        &mut errors,
                        &mut source_root,
                        parsed,
                        line.span,
                        "source_root",
                        "PackageManifestInvalid",
                        "package",
                    ),
                    "entry" => set_manifest_key(
                        &file,
                        &mut errors,
                        &mut entry,
                        parsed,
                        line.span,
                        "entry",
                        "PackageManifestInvalid",
                        "package",
                    ),
                    other => errors.push(
                        Diagnostic::new(
                            &file,
                            "PackageManifestInvalid",
                            format!("unknown package manifest key `{}`", other),
                        )
                        .with_span(line.span),
                    ),
                }
            }
            "dependencies" => {
                let valid_key = if is_project_name(key) {
                    true
                } else {
                    errors.push(
                        Diagnostic::new(
                            &file,
                            "InvalidPackageDependencyName",
                            format!(
                                "package dependency name `{}` must start with `a-z` and contain only `a-z`, `0-9`, and `-`",
                                key
                            ),
                        )
                        .with_span(line.span),
                    );
                    false
                };
                let unique_key = if dependency_keys.insert(key.to_string()) {
                    true
                } else {
                    errors.push(
                        Diagnostic::new(
                            &file,
                            "DuplicatePackageDependencyName",
                            format!("duplicate package dependency name `{}`", key),
                        )
                        .with_span(line.span),
                    );
                    false
                };

                match parse_dependency_path(value) {
                    Some(path) if valid_key && unique_key => dependencies.push(PackageDependency {
                        key: key.to_string(),
                        path,
                        span: line.span,
                    }),
                    Some(_) => {}
                    None => errors.push(
                        Diagnostic::new(
                            &file,
                            "UnsupportedDependency",
                            "workspace dependencies must use `{ path = \"...\" }` local path records only",
                        )
                        .with_span(line.span),
                    ),
                }
            }
            "" | "project" => errors.push(
                Diagnostic::new(
                    &file,
                    "PackageManifestInvalid",
                    "package manifest keys must be inside `[package]` or `[dependencies]`",
                )
                .with_span(line.span),
            ),
            _ => unreachable!("package manifest parser section is known"),
        }
    }

    if saw_project && saw_package {
        errors.push(Diagnostic::new(
            &file,
            "PackageManifestInvalid",
            "package manifest may not combine `[project]` and `[package]`",
        ));
    }
    if !saw_package {
        errors.push(Diagnostic::new(
            &file,
            "PackageManifestInvalid",
            "package manifest is missing `[package]` section",
        ));
    }

    let Some(name) = name else {
        errors.push(Diagnostic::new(
            &file,
            "PackageManifestInvalid",
            "package manifest is missing required key `name`",
        ));
        return Err(errors);
    };
    let Some(version) = version else {
        errors.push(Diagnostic::new(
            &file,
            "PackageManifestInvalid",
            "package manifest is missing required key `version`",
        ));
        return Err(errors);
    };

    if !is_project_name(&name) {
        errors.push(Diagnostic::new(
            &file,
            "InvalidPackageName",
            "package name must start with `a-z` and contain only `a-z`, `0-9`, and `-`",
        ));
    }
    if !is_package_version(&version) {
        errors.push(Diagnostic::new(
            &file,
            "InvalidPackageVersion",
            "package version must have literal `MAJOR.MINOR.PATCH` numeric shape",
        ));
    }

    let source_root = source_root.unwrap_or_else(|| "src".to_string());
    if !is_safe_relative_source_root(&source_root) {
        errors.push(Diagnostic::new(
            &file,
            "PackageSourceRootEscape",
            "package source_root must be a relative path under the package root",
        ));
    }

    let entry = entry.unwrap_or_else(|| "main".to_string());
    if !is_flat_module_identifier(&entry) {
        errors.push(Diagnostic::new(
            &file,
            "PackageManifestInvalid",
            "package entry must be a flat module identifier",
        ));
    }

    if !errors.is_empty() {
        return Err(errors);
    }

    Ok(PackageManifest {
        path,
        root,
        member,
        name,
        version,
        source_root,
        entry,
        dependencies,
    })
}

struct ManifestLine<'a> {
    text: &'a str,
    span: Span,
}

fn manifest_lines(source: &str) -> Vec<ManifestLine<'_>> {
    let mut lines = Vec::new();
    let mut offset = 0;
    for line in source.split_inclusive('\n') {
        let text = line.strip_suffix('\n').unwrap_or(line);
        lines.push(ManifestLine {
            text,
            span: Span::new(offset, offset + text.len()),
        });
        offset += line.len();
    }
    if source.is_empty() {
        lines.push(ManifestLine {
            text: "",
            span: Span::new(0, 0),
        });
    }
    lines
}

fn parse_manifest_string(value: &str) -> Option<String> {
    let value = value.trim();
    let inner = value.strip_prefix('"')?.strip_suffix('"')?;
    if inner.contains('"') || inner.contains('\\') {
        return None;
    }
    Some(inner.to_string())
}

fn parse_manifest_string_array(value: &str) -> Option<Vec<String>> {
    let inner = value.trim().strip_prefix('[')?.strip_suffix(']')?.trim();
    if inner.is_empty() {
        return Some(Vec::new());
    }
    let mut values = Vec::new();
    for item in inner.split(',') {
        values.push(parse_manifest_string(item.trim())?);
    }
    Some(values)
}

fn parse_dependency_path(value: &str) -> Option<String> {
    let inner = value.trim().strip_prefix('{')?.strip_suffix('}')?.trim();
    let (key, value) = inner.split_once('=')?;
    if key.trim() != "path" {
        return None;
    }
    parse_manifest_string(value.trim())
}

fn set_manifest_key(
    file: &str,
    errors: &mut Vec<Diagnostic>,
    slot: &mut Option<String>,
    value: String,
    span: Span,
    key: &str,
    code: &'static str,
    manifest_kind: &str,
) {
    if slot.replace(value).is_some() {
        errors.push(
            Diagnostic::new(
                file,
                code,
                format!("duplicate {} manifest key `{}`", manifest_kind, key),
            )
            .with_span(span),
        );
    }
}

fn is_package_version(value: &str) -> bool {
    let mut parts = value.split('.');
    let (Some(major), Some(minor), Some(patch), None) =
        (parts.next(), parts.next(), parts.next(), parts.next())
    else {
        return false;
    };
    [major, minor, patch]
        .iter()
        .all(|part| !part.is_empty() && part.bytes().all(|byte| byte.is_ascii_digit()))
}

fn is_project_name(value: &str) -> bool {
    let mut chars = value.chars();
    let Some(first) = chars.next() else {
        return false;
    };
    first.is_ascii_lowercase()
        && chars.all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '-')
}

fn is_flat_module_identifier(value: &str) -> bool {
    let mut chars = value.chars();
    let Some(first) = chars.next() else {
        return false;
    };
    (first.is_ascii_alphabetic() || first == '_')
        && chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '-')
}

fn is_safe_relative_source_root(value: &str) -> bool {
    if value.is_empty() {
        return false;
    }
    let path = Path::new(value);
    if path.is_absolute() {
        return false;
    }
    path.components().all(|component| match component {
        std::path::Component::Normal(name) => name.to_str() != Some("generated-source"),
        std::path::Component::CurDir => true,
        _ => false,
    })
}

fn normalize_workspace_member(value: &str) -> Option<String> {
    normalize_rel_path(value, false)
}

fn normalize_dependency_path(
    workspace: &WorkspaceManifest,
    package: &PackageManifest,
    value: &str,
) -> Result<String, Diagnostic> {
    if Path::new(value).is_absolute() || value.is_empty() {
        return Err(Diagnostic::new(
            &package.path.display().to_string(),
            "DependencyPathEscape",
            format!(
                "dependency path `{}` must be relative and stay under the workspace root",
                value
            ),
        ));
    }
    let combined = Path::new(&package.member).join(value);
    let normalized = normalize_components(&combined).ok_or_else(|| {
        Diagnostic::new(
            &package.path.display().to_string(),
            "DependencyPathEscape",
            format!(
                "dependency path `{}` must stay under the workspace root",
                value
            ),
        )
    })?;

    let target = workspace.root.join(&normalized);
    let workspace_root = fs::canonicalize(&workspace.root).ok();
    let target_root = fs::canonicalize(target).ok();
    if let (Some(workspace_root), Some(target_root)) = (workspace_root, target_root) {
        if !target_root.starts_with(&workspace_root) {
            return Err(Diagnostic::new(
                &package.path.display().to_string(),
                "DependencyPathEscape",
                format!(
                    "dependency path `{}` must not escape the workspace root",
                    value
                ),
            ));
        }
    }

    Ok(normalized)
}

fn normalize_rel_path(value: &str, allow_parent_within_root: bool) -> Option<String> {
    if value.is_empty() {
        return None;
    }
    let path = Path::new(value);
    if path.is_absolute() {
        return None;
    }
    if !allow_parent_within_root
        && path
            .components()
            .any(|component| matches!(component, std::path::Component::ParentDir))
    {
        return None;
    }
    normalize_components(path)
}

fn normalize_components(path: &Path) -> Option<String> {
    let mut components = Vec::new();
    for component in path.components() {
        match component {
            std::path::Component::CurDir => {}
            std::path::Component::Normal(value) => {
                let value = value.to_str()?;
                if value.is_empty() {
                    return None;
                }
                components.push(value.to_string());
            }
            std::path::Component::ParentDir => {
                components.pop()?;
            }
            _ => return None,
        }
    }
    if components.is_empty() {
        return None;
    }
    Some(components.join("/"))
}

fn validate_source_root_boundary(
    manifest: &Manifest,
    source_root: &Path,
) -> Result<(), Diagnostic> {
    let project_root = fs::canonicalize(&manifest.project_root).map_err(|err| {
        Diagnostic::new(
            &manifest.path.display().to_string(),
            "ProjectManifestInvalid",
            format!(
                "cannot canonicalize project root `{}`: {}",
                manifest.project_root.display(),
                err
            ),
        )
    })?;
    let source_root = fs::canonicalize(source_root).map_err(|err| {
        Diagnostic::new(
            &manifest.path.display().to_string(),
            "ProjectManifestInvalid",
            format!(
                "cannot canonicalize project source root `{}`: {}",
                source_root.display(),
                err
            ),
        )
    })?;

    if !source_root.starts_with(&project_root) {
        return Err(Diagnostic::new(
            &manifest.path.display().to_string(),
            "ProjectManifestInvalid",
            "project source_root must not escape the project root",
        ));
    }

    Ok(())
}

fn validate_package_root_boundary(
    workspace: &WorkspaceManifest,
    manifest: &PackageManifest,
) -> Result<(), Diagnostic> {
    let workspace_root = fs::canonicalize(&workspace.root).map_err(|err| {
        Diagnostic::new(
            &workspace.path.display().to_string(),
            "WorkspaceManifestInvalid",
            format!(
                "cannot canonicalize workspace root `{}`: {}",
                workspace.root.display(),
                err
            ),
        )
    })?;
    let package_root = fs::canonicalize(&manifest.root).map_err(|err| {
        Diagnostic::new(
            &workspace.path.display().to_string(),
            "WorkspaceManifestInvalid",
            format!(
                "cannot canonicalize workspace member `{}`: {}",
                manifest.member, err
            ),
        )
    })?;
    if !package_root.starts_with(&workspace_root) {
        return Err(Diagnostic::new(
            &workspace.path.display().to_string(),
            "WorkspaceMemberPathEscape",
            format!(
                "workspace member `{}` must not escape the workspace root",
                manifest.member
            ),
        ));
    }
    Ok(())
}

fn validate_package_source_root_boundary(
    manifest: &PackageManifest,
    source_root: &Path,
) -> Result<(), Diagnostic> {
    let package_root = fs::canonicalize(&manifest.root).map_err(|err| {
        Diagnostic::new(
            &manifest.path.display().to_string(),
            "PackageManifestInvalid",
            format!(
                "cannot canonicalize package root `{}`: {}",
                manifest.root.display(),
                err
            ),
        )
    })?;
    let source_root = fs::canonicalize(source_root).map_err(|err| {
        Diagnostic::new(
            &manifest.path.display().to_string(),
            "PackageManifestInvalid",
            format!(
                "cannot canonicalize package source root `{}`: {}",
                source_root.display(),
                err
            ),
        )
    })?;
    if !source_root.starts_with(&package_root) {
        return Err(Diagnostic::new(
            &manifest.path.display().to_string(),
            "PackageSourceRootEscape",
            "package source_root must not escape the package root",
        ));
    }
    Ok(())
}

fn discover_module_paths(source_root: &Path) -> io::Result<Vec<PathBuf>> {
    let mut paths = Vec::new();
    for entry in fs::read_dir(source_root)? {
        let entry = entry?;
        let path = entry.path();
        if path.is_file() && path.extension().and_then(|ext| ext.to_str()) == Some("slo") {
            paths.push(path);
        }
    }
    paths.sort_by(|left, right| left.file_name().cmp(&right.file_name()));
    Ok(paths)
}

fn load_tool_standard_import_modules(
    manifest: &Manifest,
    modules: &mut Vec<ProjectArtifactModule>,
    sources: &mut Vec<SourceFile>,
    diagnostics: &mut Vec<Diagnostic>,
) {
    let mut loaded = modules
        .iter()
        .map(|module| module.name.clone())
        .collect::<HashSet<_>>();
    let mut queue = standard_import_queue(modules.iter().flat_map(|module| module.imports.iter()));

    while let Some(import_name) = queue.pop_front() {
        if !loaded.insert(import_name.clone()) {
            continue;
        }

        let Some(path) = standard_module_path(&manifest.project_root, &import_name) else {
            diagnostics.push(missing_standard_module(manifest, &import_name));
            continue;
        };
        let file = path.display().to_string();
        match fs::read_to_string(&path) {
            Ok(source) => {
                let imports = tool_imports(&source);
                queue.extend(
                    imports
                        .iter()
                        .filter(|name| standard_module_leaf(name).is_some())
                        .filter(|name| !loaded.contains(*name))
                        .cloned(),
                );
                sources.push(SourceFile {
                    path: file.clone(),
                    source: source.clone(),
                });
                let mut c_imports = tool_c_imports(&source);
                for c_import in &mut c_imports {
                    c_import.source_module = import_name.clone();
                }
                modules.push(ProjectArtifactModule {
                    name: import_name,
                    path: file,
                    imports,
                    c_imports,
                });
            }
            Err(err) => diagnostics.push(Diagnostic::new(
                &manifest.path.display().to_string(),
                "ProjectSourceReadFailed",
                format!("cannot read standard-library module `{}`: {}", file, err),
            )),
        }
    }
}

fn load_standard_import_modules(
    manifest: &Manifest,
    modules: &mut Vec<ModuleUnit>,
    sources: &mut Vec<SourceFile>,
    diagnostics: &mut Vec<Diagnostic>,
) {
    let mut loaded = modules
        .iter()
        .map(|module| module.name.clone())
        .collect::<HashSet<_>>();
    let mut queue = standard_import_queue(
        modules
            .iter()
            .flat_map(|module| module.imports.iter().map(|i| &i.module)),
    );

    while let Some(import_name) = queue.pop_front() {
        if !loaded.insert(import_name.clone()) {
            continue;
        }

        let Some(path) = standard_module_path(&manifest.project_root, &import_name) else {
            diagnostics.push(missing_standard_module(manifest, &import_name));
            continue;
        };
        let file = path.display().to_string();
        match fs::read_to_string(&path) {
            Ok(source) => {
                sources.push(SourceFile {
                    path: file.clone(),
                    source: source.clone(),
                });
                match parse_module(&path, file, source) {
                    Ok(mut module) => {
                        module.program.module = import_name.clone();
                        module.name = import_name.clone();
                        queue.extend(
                            module
                                .imports
                                .iter()
                                .map(|import| &import.module)
                                .filter(|name| standard_module_leaf(name).is_some())
                                .filter(|name| !loaded.contains(*name))
                                .cloned(),
                        );
                        modules.push(module);
                    }
                    Err(mut errs) => diagnostics.append(&mut errs),
                }
            }
            Err(err) => diagnostics.push(Diagnostic::new(
                &manifest.path.display().to_string(),
                "ProjectSourceReadFailed",
                format!("cannot read standard-library module `{}`: {}", file, err),
            )),
        }
    }
}

fn load_package_standard_import_modules(
    manifest: &PackageManifest,
    modules: &mut Vec<ModuleUnit>,
    sources: &mut Vec<SourceFile>,
    diagnostics: &mut Vec<Diagnostic>,
) {
    let mut loaded = modules
        .iter()
        .map(|module| module.name.clone())
        .collect::<HashSet<_>>();
    let mut queue = standard_import_queue(
        modules
            .iter()
            .flat_map(|module| module.imports.iter().map(|i| &i.module)),
    );

    while let Some(import_name) = queue.pop_front() {
        if !loaded.insert(import_name.clone()) {
            continue;
        }

        let Some(path) = standard_module_path(&manifest.root, &import_name) else {
            diagnostics.push(missing_package_standard_module(manifest, &import_name));
            continue;
        };
        let file = path.display().to_string();
        match fs::read_to_string(&path) {
            Ok(source) => {
                sources.push(SourceFile {
                    path: file.clone(),
                    source: source.clone(),
                });
                match parse_module(&path, file, source) {
                    Ok(mut module) => {
                        module.program.module = import_name.clone();
                        module.name = import_name.clone();
                        queue.extend(
                            module
                                .imports
                                .iter()
                                .map(|import| &import.module)
                                .filter(|name| standard_module_leaf(name).is_some())
                                .filter(|name| !loaded.contains(*name))
                                .cloned(),
                        );
                        modules.push(module);
                    }
                    Err(mut errs) => diagnostics.append(&mut errs),
                }
            }
            Err(err) => diagnostics.push(Diagnostic::new(
                &manifest.path.display().to_string(),
                "PackageSourceReadFailed",
                format!("cannot read standard-library module `{}`: {}", file, err),
            )),
        }
    }
}

fn standard_import_queue<'a>(imports: impl Iterator<Item = &'a String>) -> VecDeque<String> {
    imports
        .filter(|name| standard_module_leaf(name).is_some())
        .cloned()
        .collect::<BTreeSet<_>>()
        .into_iter()
        .collect()
}

fn standard_module_path(project_root: &Path, import_name: &str) -> Option<PathBuf> {
    let leaf = standard_module_leaf(import_name)?;
    standard_library_candidates(project_root)
        .into_iter()
        .map(|root| root.join(format!("{}.slo", leaf)))
        .find(|path| path.is_file())
}

fn standard_module_leaf(import_name: &str) -> Option<&str> {
    let leaf = import_name.strip_prefix("std.")?;
    if leaf.is_empty()
        || leaf.contains('.')
        || leaf.contains('/')
        || leaf.contains('\\')
        || !leaf
            .bytes()
            .all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')
    {
        return None;
    }
    Some(leaf)
}

fn standard_library_candidates(project_root: &Path) -> Vec<PathBuf> {
    let mut candidates = Vec::new();
    if let Some(paths) = env::var_os("SLOVO_STD_PATH") {
        candidates.extend(env::split_paths(&paths));
    }
    candidates.extend(installed_standard_library_candidates());
    for ancestor in project_root.ancestors() {
        candidates.push(ancestor.join("std"));
        candidates.push(ancestor.join("lib/std"));
        candidates.push(ancestor.join("slovo/std"));
    }
    let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR"));
    for ancestor in compiler_root.ancestors() {
        candidates.push(ancestor.join("std"));
        candidates.push(ancestor.join("lib/std"));
        candidates.push(ancestor.join("slovo/std"));
    }
    candidates
}

fn installed_standard_library_candidates() -> Vec<PathBuf> {
    let Ok(exe) = env::current_exe() else {
        return Vec::new();
    };
    let Some(bin_dir) = exe.parent() else {
        return Vec::new();
    };
    vec![
        bin_dir.join("std"),
        bin_dir.join("../std"),
        bin_dir.join("../share/slovo/std"),
    ]
}

fn missing_standard_module(manifest: &Manifest, import_name: &str) -> Diagnostic {
    Diagnostic::new(
        &manifest.path.display().to_string(),
        "MissingStandardLibraryModule",
        format!(
            "standard-library module `{}` could not be found",
            import_name
        ),
    )
    .hint("set SLOVO_STD_PATH, install `share/slovo/std`, or run from a checkout that has `lib/std` sources")
}

fn missing_package_standard_module(manifest: &PackageManifest, import_name: &str) -> Diagnostic {
    Diagnostic::new(
        &manifest.path.display().to_string(),
        "MissingStandardLibraryModule",
        format!(
            "standard-library module `{}` could not be found",
            import_name
        ),
    )
    .hint("set SLOVO_STD_PATH, install `share/slovo/std`, or run from a checkout that has `lib/std` sources")
}

fn parse_module(path: &Path, file: String, source: String) -> Result<ModuleUnit, Vec<Diagnostic>> {
    let tokens = lexer::lex(&file, &source)?;
    let forms = crate::sexpr::parse(&file, &tokens)?;
    let mut errors = Vec::new();
    let mut module_decl = None::<(String, Vec<NameSpan>, SExpr)>;
    let mut imports = Vec::new();
    let mut decls_started = false;
    let mut lower_forms = Vec::new();

    for (index, form) in forms.iter().enumerate() {
        match head_ident(form) {
            Some("module") => {
                if index != 0 || module_decl.is_some() {
                    errors.push(
                        Diagnostic::new(
                            &file,
                            "InvalidModule",
                            "project module declaration must be the first top-level form",
                        )
                        .with_span(form.span),
                    );
                    continue;
                }
                match parse_module_decl(&file, form) {
                    Ok((name, exports, lowered_module)) => {
                        module_decl = Some((name, exports, lowered_module.clone()));
                        lower_forms.push(lowered_module);
                    }
                    Err(mut errs) => errors.append(&mut errs),
                }
            }
            Some("import") => {
                if module_decl.is_none() || decls_started {
                    errors.push(
                        Diagnostic::new(
                            &file,
                            "InvalidImport",
                            "imports must appear after the module declaration and before declarations",
                        )
                        .with_span(form.span),
                    );
                    continue;
                }
                match parse_import_decl(&file, form) {
                    Ok(import) => imports.push(import),
                    Err(mut errs) => errors.append(&mut errs),
                }
            }
            _ => {
                decls_started = true;
                lower_forms.push(form.clone());
            }
        }
    }

    let Some((name, exports, _)) = module_decl else {
        errors.push(Diagnostic::new(
            &file,
            "InvalidModule",
            "project source must start with `(module name)` or `(module name (export ...))`",
        ));
        return Err(errors);
    };

    let expected_name = path
        .file_stem()
        .and_then(|stem| stem.to_str())
        .unwrap_or("");
    if expected_name != name {
        errors.push(
            Diagnostic::new(
                &file,
                "MissingImport",
                format!(
                    "module declaration `{}` must match source file stem `{}`",
                    name, expected_name
                ),
            )
            .with_span(module_name_span(&lower_forms[0]).unwrap_or(lower_forms[0].span)),
        );
    }

    let imported_names = imports
        .iter()
        .flat_map(|import| import.names.iter().map(|name| name.name.clone()))
        .collect::<Vec<_>>();
    let program =
        match lower::lower_program_with_imported_names(&file, &lower_forms, &imported_names) {
            Ok(program) => program,
            Err(mut errs) => {
                errors.append(&mut errs);
                Program {
                    module: name.clone(),
                    type_aliases: Vec::new(),
                    enums: Vec::new(),
                    structs: Vec::new(),
                    c_imports: Vec::new(),
                    functions: Vec::new(),
                    tests: Vec::new(),
                }
            }
        };

    let (local_functions, local_structs, local_enums, local_aliases, mut duplicate_errors) =
        local_declarations(&file, &program);
    errors.append(&mut duplicate_errors);

    if errors.is_empty() {
        Ok(ModuleUnit {
            name,
            file,
            exports,
            imports,
            program,
            local_functions,
            local_structs,
            local_enums,
            local_aliases,
        })
    } else {
        Err(errors)
    }
}

fn parse_module_decl(
    file: &str,
    form: &SExpr,
) -> Result<(String, Vec<NameSpan>, SExpr), Vec<Diagnostic>> {
    let mut errors = Vec::new();
    let Some(items) = expect_list(form) else {
        return Err(vec![Diagnostic::new(
            file,
            "InvalidModule",
            "invalid module form",
        )
        .with_span(form.span)]);
    };

    if items.len() != 2 && items.len() != 3 {
        return Err(vec![Diagnostic::new(
            file,
            "InvalidModule",
            "project module form must be `(module name)` or `(module name (export ...))`",
        )
        .with_span(form.span)]);
    }

    let name = match expect_ident(&items[1]) {
        Some(name) => name.to_string(),
        None => {
            errors.push(
                Diagnostic::new(
                    file,
                    "InvalidModuleName",
                    "module name must be an identifier",
                )
                .with_span(items[1].span),
            );
            "<error>".to_string()
        }
    };

    let mut exports = Vec::new();
    if let Some(export_form) = items.get(2) {
        let Some(export_items) = expect_list(export_form) else {
            errors.push(
                Diagnostic::new(
                    file,
                    "InvalidExport",
                    "export list must be `(export name...)`",
                )
                .with_span(export_form.span),
            );
            return Err(errors);
        };
        if !matches!(export_items.first().and_then(expect_ident), Some("export")) {
            errors.push(
                Diagnostic::new(
                    file,
                    "InvalidExport",
                    "module option must be an export list",
                )
                .with_span(export_form.span),
            );
        } else {
            let mut seen = HashMap::new();
            for item in &export_items[1..] {
                match expect_ident(item) {
                    Some(name) => {
                        if let Some(original) = seen.insert(name.to_string(), item.span) {
                            errors.push(
                                Diagnostic::new(
                                    file,
                                    "DuplicateName",
                                    format!("duplicate exported name `{}`", name),
                                )
                                .with_span(item.span)
                                .related("original exported name", original),
                            );
                        }
                        if is_reserved_intrinsic(name) {
                            errors.push(
                                Diagnostic::new(
                                    file,
                                    "Visibility",
                                    format!("builtin or intrinsic `{}` cannot be exported", name),
                                )
                                .with_span(item.span),
                            );
                        }
                        exports.push(NameSpan {
                            name: name.to_string(),
                            span: item.span,
                        });
                    }
                    None => errors.push(
                        Diagnostic::new(
                            file,
                            "InvalidExport",
                            "exported name must be an identifier",
                        )
                        .with_span(item.span),
                    ),
                }
            }
        }
    }

    let lowered_module = SExpr {
        kind: SExprKind::List(vec![items[0].clone(), items[1].clone()]),
        span: form.span,
    };

    if errors.is_empty() {
        Ok((name, exports, lowered_module))
    } else {
        Err(errors)
    }
}

fn parse_import_decl(file: &str, form: &SExpr) -> Result<ImportDecl, Vec<Diagnostic>> {
    let mut errors = Vec::new();
    let Some(items) = expect_list(form) else {
        return Err(vec![Diagnostic::new(
            file,
            "InvalidImport",
            "invalid import form",
        )
        .with_span(form.span)]);
    };

    if items.len() != 3 {
        return Err(vec![Diagnostic::new(
            file,
            "InvalidImport",
            "import form must be `(import module (name...))`",
        )
        .with_span(form.span)]);
    }

    let module = match expect_ident(&items[1]) {
        Some(name) => name.to_string(),
        None => {
            errors.push(
                Diagnostic::new(file, "InvalidImport", "import module must be an identifier")
                    .with_span(items[1].span),
            );
            "<error>".to_string()
        }
    };

    let mut names = Vec::new();
    let mut seen = HashMap::new();
    match expect_list(&items[2]) {
        Some(import_items) if !import_items.is_empty() => {
            for item in import_items {
                match expect_ident(item) {
                    Some(name) => {
                        if let Some(original) = seen.insert(name.to_string(), item.span) {
                            errors.push(
                                Diagnostic::new(
                                    file,
                                    "DuplicateName",
                                    format!("duplicate imported name `{}`", name),
                                )
                                .with_span(item.span)
                                .related("original imported name", original),
                            );
                        }
                        if is_reserved_intrinsic(name) {
                            errors.push(
                                Diagnostic::new(
                                    file,
                                    "Visibility",
                                    format!("builtin or intrinsic `{}` cannot be imported", name),
                                )
                                .with_span(item.span),
                            );
                        }
                        names.push(NameSpan {
                            name: name.to_string(),
                            span: item.span,
                        });
                    }
                    None => errors.push(
                        Diagnostic::new(
                            file,
                            "InvalidImport",
                            "imported name must be an identifier",
                        )
                        .with_span(item.span),
                    ),
                }
            }
        }
        _ => errors.push(
            Diagnostic::new(
                file,
                "InvalidImport",
                "import list must contain imported names",
            )
            .with_span(items[2].span),
        ),
    }

    if errors.is_empty() {
        Ok(ImportDecl {
            module,
            module_span: items[1].span,
            names,
        })
    } else {
        Err(errors)
    }
}

fn tool_imports(source: &str) -> Vec<String> {
    let Ok(tokens) = lexer::lex("<tool>", source) else {
        return Vec::new();
    };
    let Ok(forms) = crate::sexpr::parse("<tool>", &tokens) else {
        return Vec::new();
    };
    forms
        .iter()
        .filter_map(|form| {
            let items = expect_list(form)?;
            if !matches!(items.first().and_then(expect_ident), Some("import")) {
                return None;
            }
            items.get(1).and_then(expect_ident).map(str::to_string)
        })
        .collect()
}

fn tool_c_imports(source: &str) -> Vec<ProjectArtifactCImport> {
    let Ok(tokens) = lexer::lex("<tool>", source) else {
        return Vec::new();
    };
    let Ok(forms) = crate::sexpr::parse("<tool>", &tokens) else {
        return Vec::new();
    };
    let Ok(program) = lower::lower_program("<tool>", &forms) else {
        return Vec::new();
    };
    program
        .c_imports
        .iter()
        .map(|import| project_artifact_c_import(&program.module, import))
        .collect()
}

pub(crate) fn project_artifact_c_import(
    source_module: &str,
    import: &crate::ast::CImportDecl,
) -> ProjectArtifactCImport {
    ProjectArtifactCImport {
        source_module: source_module.to_string(),
        name: import.name.clone(),
        symbol: import.name.clone(),
        params: import
            .params
            .iter()
            .map(|param| param.ty.to_string())
            .collect(),
        return_type: import.return_type.to_string(),
        abi: "experimental-fixture-c".to_string(),
    }
}

fn external_enum_from_module(module: &ModuleUnit, name: &str) -> Option<ExternalEnum> {
    module
        .program
        .enums
        .iter()
        .find(|enum_decl| enum_decl.name == name)
        .map(|enum_decl| ExternalEnum {
            name: name.to_string(),
            span: enum_decl.name_span,
            variants: enum_decl
                .variants
                .iter()
                .map(|variant| ExternalEnumVariant {
                    name: variant.name.clone(),
                    name_span: variant.name_span,
                    payload_ty: variant.payload_ty.clone(),
                    payload_ty_span: variant.payload_ty_span,
                })
                .collect(),
        })
}

fn external_enum_from_checked_module(
    module: &ModuleUnit,
    checked: &CheckedProgram,
    name: &str,
) -> Option<ExternalEnum> {
    let source_enum = module
        .program
        .enums
        .iter()
        .find(|enum_decl| enum_decl.name == name)?;
    let checked_enum = checked
        .enums
        .iter()
        .find(|enum_decl| enum_decl.name == name)?;

    Some(ExternalEnum {
        name: name.to_string(),
        span: source_enum.name_span,
        variants: checked_enum
            .variants
            .iter()
            .map(|checked_variant| {
                let source_variant = source_enum
                    .variants
                    .iter()
                    .find(|variant| variant.name == checked_variant.name);
                ExternalEnumVariant {
                    name: checked_variant.name.clone(),
                    name_span: source_variant
                        .map_or(source_enum.name_span, |variant| variant.name_span),
                    payload_ty: checked_variant.payload_ty.clone(),
                    payload_ty_span: source_variant.and_then(|variant| variant.payload_ty_span),
                }
            })
            .collect(),
    })
}

fn local_declarations(
    file: &str,
    program: &Program,
) -> (
    HashMap<String, DeclInfo>,
    HashMap<String, DeclInfo>,
    HashMap<String, DeclInfo>,
    HashMap<String, DeclInfo>,
    Vec<Diagnostic>,
) {
    let mut all = HashMap::<String, DeclInfo>::new();
    let mut functions = HashMap::new();
    let mut structs = HashMap::new();
    let mut enums = HashMap::new();
    let mut aliases = HashMap::new();
    let mut errors = Vec::new();

    for alias in &program.type_aliases {
        let decl = DeclInfo {
            span: alias.name_span,
            kind: DeclKind::TypeAlias,
        };
        if let Some(original) = all.insert(alias.name.clone(), decl.clone()) {
            if original.kind == DeclKind::CImport {
                errors.push(
                    Diagnostic::new(
                        file,
                        "DuplicateTopLevelName",
                        format!("duplicate top-level name `{}`", alias.name),
                    )
                    .with_span(alias.name_span)
                    .related("original declaration", original.span),
                );
            } else {
                errors.push(duplicate_name(
                    file,
                    &alias.name,
                    alias.name_span,
                    original.span,
                ));
            }
        }
        aliases.insert(alias.name.clone(), decl);
    }

    for function in &program.functions {
        let decl = DeclInfo {
            span: function.span,
            kind: DeclKind::Function,
        };
        if is_reserved_intrinsic(&function.name) {
            errors.push(
                Diagnostic::new(
                    file,
                    "DuplicateName",
                    format!("builtin or intrinsic `{}` is reserved", function.name),
                )
                .with_span(function.span),
            );
        }
        if let Some(original) = all.insert(function.name.clone(), decl.clone()) {
            errors.push(duplicate_name(
                file,
                &function.name,
                function.span,
                original.span,
            ));
        }
        functions.insert(function.name.clone(), decl);
    }

    for import in &program.c_imports {
        let decl = DeclInfo {
            span: import.span,
            kind: DeclKind::CImport,
        };
        if is_reserved_intrinsic(&import.name) {
            errors.push(
                Diagnostic::new(
                    file,
                    "ReservedName",
                    format!("builtin or intrinsic `{}` is reserved", import.name),
                )
                .with_span(import.name_span),
            );
        }
        if let Some(original) = all.insert(import.name.clone(), decl.clone()) {
            errors.push(
                Diagnostic::new(
                    file,
                    "DuplicateTopLevelName",
                    format!("duplicate top-level name `{}`", import.name),
                )
                .with_span(import.name_span)
                .related("original declaration", original.span),
            );
        }
        functions.insert(import.name.clone(), decl);
    }

    for struct_decl in &program.structs {
        let decl = DeclInfo {
            span: struct_decl.name_span,
            kind: DeclKind::Struct,
        };
        if is_reserved_intrinsic(&struct_decl.name) {
            errors.push(
                Diagnostic::new(
                    file,
                    "DuplicateName",
                    format!("builtin or intrinsic `{}` is reserved", struct_decl.name),
                )
                .with_span(struct_decl.name_span),
            );
        }
        if let Some(original) = all.insert(struct_decl.name.clone(), decl.clone()) {
            if original.kind == DeclKind::CImport {
                errors.push(
                    Diagnostic::new(
                        file,
                        "DuplicateTopLevelName",
                        format!("duplicate top-level name `{}`", struct_decl.name),
                    )
                    .with_span(struct_decl.name_span)
                    .related("original declaration", original.span),
                );
            } else {
                errors.push(duplicate_name(
                    file,
                    &struct_decl.name,
                    struct_decl.name_span,
                    original.span,
                ));
            }
        }
        structs.insert(struct_decl.name.clone(), decl);
    }

    for enum_decl in &program.enums {
        let decl = DeclInfo {
            span: enum_decl.name_span,
            kind: DeclKind::Enum,
        };
        if is_reserved_intrinsic(&enum_decl.name) {
            errors.push(
                Diagnostic::new(
                    file,
                    "DuplicateName",
                    format!("builtin or intrinsic `{}` is reserved", enum_decl.name),
                )
                .with_span(enum_decl.name_span),
            );
        }
        if let Some(original) = all.insert(enum_decl.name.clone(), decl.clone()) {
            if original.kind == DeclKind::CImport {
                errors.push(
                    Diagnostic::new(
                        file,
                        "DuplicateTopLevelName",
                        format!("duplicate top-level name `{}`", enum_decl.name),
                    )
                    .with_span(enum_decl.name_span)
                    .related("original declaration", original.span),
                );
            } else {
                errors.push(duplicate_name(
                    file,
                    &enum_decl.name,
                    enum_decl.name_span,
                    original.span,
                ));
            }
        }
        enums.insert(enum_decl.name.clone(), decl);
    }

    (functions, structs, enums, aliases, errors)
}

fn validate_workspace_packages(
    workspace: &WorkspaceManifest,
    packages: &mut [PackageUnit],
    diagnostics: &mut Vec<Diagnostic>,
) {
    let mut by_member = HashMap::<String, usize>::new();
    for (index, package) in packages.iter().enumerate() {
        by_member.insert(package.manifest.member.clone(), index);
    }

    let mut by_name = BTreeMap::<String, Vec<usize>>::new();
    for (index, package) in packages.iter().enumerate() {
        by_name
            .entry(package.manifest.name.clone())
            .or_default()
            .push(index);
    }
    for (name, indices) in by_name.iter().filter(|(_, indices)| indices.len() > 1) {
        for index in indices {
            diagnostics.push(Diagnostic::new(
                &packages[*index].manifest.path.display().to_string(),
                "DuplicatePackageName",
                format!("duplicate package name `{}`", name),
            ));
        }
    }
    if let Some(default_package) = &workspace.default_package {
        if !by_name.contains_key(default_package) {
            diagnostics.push(Diagnostic::new(
                &workspace.path.display().to_string(),
                "WorkspaceDefaultPackageMissing",
                format!(
                    "workspace default_package `{}` is not a workspace package",
                    default_package
                ),
            ));
        }
    }

    let mut resolved_dependencies = vec![Vec::<usize>::new(); packages.len()];
    for (package_index, package) in packages.iter().enumerate() {
        for dependency in &package.manifest.dependencies {
            let target_member =
                match normalize_dependency_path(workspace, &package.manifest, &dependency.path) {
                    Ok(member) => member,
                    Err(diagnostic) => {
                        diagnostics.push(diagnostic.with_span(dependency.span));
                        continue;
                    }
                };
            let Some(target_index) = by_member.get(&target_member).copied() else {
                diagnostics.push(
                    Diagnostic::new(
                        &package.manifest.path.display().to_string(),
                        "MissingPackageDependency",
                        format!(
                            "dependency `{}` path `{}` is not a workspace member",
                            dependency.key, dependency.path
                        ),
                    )
                    .with_span(dependency.span),
                );
                continue;
            };
            let target = &packages[target_index];
            if dependency.key != target.manifest.name {
                diagnostics.push(
                    Diagnostic::new(
                        &package.manifest.path.display().to_string(),
                        "DependencyNameMismatch",
                        format!(
                            "dependency key `{}` must match package name `{}`",
                            dependency.key, target.manifest.name
                        ),
                    )
                    .with_span(dependency.span),
                );
                continue;
            }
            if !resolved_dependencies[package_index].contains(&target_index) {
                resolved_dependencies[package_index].push(target_index);
            }
        }
    }

    let package_names = packages
        .iter()
        .map(|package| package.manifest.name.clone())
        .collect::<Vec<_>>();
    for (package, dependencies) in packages.iter_mut().zip(resolved_dependencies) {
        package.dependency_indices = dependencies;
        package
            .dependency_indices
            .sort_by(|left, right| package_names[*left].cmp(&package_names[*right]));
    }

    detect_package_cycle(packages, diagnostics);
}

fn detect_package_cycle(packages: &[PackageUnit], diagnostics: &mut Vec<Diagnostic>) {
    let mut state = vec![0_u8; packages.len()];
    let mut stack = Vec::new();
    let mut indices = (0..packages.len()).collect::<Vec<_>>();
    indices.sort_by(|left, right| {
        packages[*left]
            .manifest
            .name
            .cmp(&packages[*right].manifest.name)
    });
    for index in indices {
        if state[index] == 0 && dfs_package_cycle(index, packages, &mut state, &mut stack) {
            let cycle_start = stack
                .last()
                .and_then(|last| {
                    stack[..stack.len().saturating_sub(1)]
                        .iter()
                        .position(|idx| idx == last)
                })
                .unwrap_or(0);
            let cycle_nodes = &stack[cycle_start..];
            let cycle = cycle_nodes
                .iter()
                .map(|idx| packages[*idx].manifest.name.as_str())
                .collect::<Vec<_>>()
                .join(" -> ");
            let mut diagnostic = Diagnostic::new(
                &packages[index].manifest.path.display().to_string(),
                "PackageDependencyCycle",
                format!("workspace package dependencies contain a cycle: {}", cycle),
            );
            for edge in cycle_nodes.windows(2) {
                let from = &packages[edge[0]];
                let to = &packages[edge[1]];
                if let Some(dep) = from
                    .manifest
                    .dependencies
                    .iter()
                    .find(|dep| dep.key == to.manifest.name)
                {
                    diagnostic = diagnostic.related_in_file(
                        from.manifest.path.display().to_string(),
                        format!(
                            "package `{}` depends on `{}`",
                            from.manifest.name, to.manifest.name
                        ),
                        dep.span,
                    );
                }
            }
            diagnostics.push(diagnostic);
            return;
        }
    }
}

fn dfs_package_cycle(
    index: usize,
    packages: &[PackageUnit],
    state: &mut [u8],
    stack: &mut Vec<usize>,
) -> bool {
    state[index] = 1;
    stack.push(index);
    for next in &packages[index].dependency_indices {
        if state[*next] == 1 {
            stack.push(*next);
            return true;
        }
        if state[*next] == 0 && dfs_package_cycle(*next, packages, state, stack) {
            return true;
        }
    }
    stack.pop();
    state[index] = 2;
    false
}

fn resolve_and_check_workspace(
    loaded: WorkspaceLoaded,
    require_entry_wrapper: bool,
) -> Result<CheckedProject, ProjectLoadFailure> {
    let WorkspaceLoaded {
        workspace,
        packages,
        sources,
        ..
    } = loaded;

    let mut diagnostics = Vec::new();
    let mut module_maps = Vec::new();
    for package in &packages {
        let mut by_name = HashMap::<String, usize>::new();
        for (index, module) in package.modules.iter().enumerate() {
            if let Some(original) = by_name.insert(module.name.clone(), index) {
                diagnostics.push(duplicate_name(
                    &module.file,
                    &module.name,
                    Span::new(0, 0),
                    package.modules[original]
                        .program
                        .functions
                        .first()
                        .map_or(Span::new(0, 0), |f| f.span),
                ));
            }
        }
        module_maps.push(by_name);
    }

    let export_maps = packages
        .iter()
        .map(|package| build_export_maps(&package.modules, &mut diagnostics))
        .collect::<Vec<_>>();
    let import_maps =
        resolve_workspace_imports(&packages, &module_maps, &export_maps, &mut diagnostics);
    for (package_index, package) in packages.iter().enumerate() {
        detect_import_cycle(
            &package.modules,
            &module_maps[package_index],
            &mut diagnostics,
        );
    }

    if !diagnostics.is_empty() {
        return Err(ProjectLoadFailure {
            diagnostics,
            sources,
            artifact: Some(workspace_artifact(&workspace, &packages, None, None, None)),
        });
    }

    let package_order = package_topo_order(&packages);
    let mut module_orders = HashMap::<usize, Vec<usize>>::new();
    for package_index in &package_order {
        module_orders.insert(
            *package_index,
            topo_order(
                &packages[*package_index].modules,
                &module_maps[*package_index],
            ),
        );
    }

    let mut checked_by_module = HashMap::<(usize, String), CheckedProgram>::new();
    let mut check_errors = Vec::new();
    for package_index in &package_order {
        let package = &packages[*package_index];
        let module_order = module_orders
            .get(package_index)
            .expect("module order was computed");
        for module_index in module_order {
            let module = &package.modules[*module_index];
            let imports = &import_maps[*package_index][*module_index];
            let mut external_functions = Vec::new();
            let mut external_structs = Vec::new();
            let mut external_enums = Vec::new();
            for (name, binding) in imports {
                let provider = &packages[binding.provider_package].modules[binding.provider_module];
                let checked_provider =
                    checked_by_module.get(&(binding.provider_package, provider.name.clone()));
                match binding.kind {
                    DeclKind::Function => {
                        if let Some(function) = checked_provider
                            .and_then(|program| program.functions.iter().find(|f| f.name == *name))
                        {
                            external_functions.push(ExternalFunction {
                                name: name.clone(),
                                params: function.params.iter().map(|(_, ty)| ty.clone()).collect(),
                                return_type: function.return_type.clone(),
                                foreign: false,
                            });
                        } else if let Some(function) =
                            provider.program.functions.iter().find(|f| f.name == *name)
                        {
                            external_functions.push(ExternalFunction {
                                name: name.clone(),
                                params: function.params.iter().map(|p| p.ty.clone()).collect(),
                                return_type: function.return_type.clone(),
                                foreign: false,
                            });
                        }
                    }
                    DeclKind::CImport => {
                        if let Some(import) = checked_provider
                            .and_then(|program| program.c_imports.iter().find(|f| f.name == *name))
                        {
                            external_functions.push(ExternalFunction {
                                name: name.clone(),
                                params: import.params.iter().map(|(_, ty)| ty.clone()).collect(),
                                return_type: import.return_type.clone(),
                                foreign: true,
                            });
                        } else if let Some(import) =
                            provider.program.c_imports.iter().find(|f| f.name == *name)
                        {
                            external_functions.push(ExternalFunction {
                                name: name.clone(),
                                params: import.params.iter().map(|p| p.ty.clone()).collect(),
                                return_type: import.return_type.clone(),
                                foreign: true,
                            });
                        }
                    }
                    DeclKind::Struct => {
                        if let Some(struct_decl) = checked_provider
                            .and_then(|program| program.structs.iter().find(|s| s.name == *name))
                        {
                            external_structs.push(ExternalStruct {
                                name: name.clone(),
                                fields: struct_decl.fields.clone(),
                                span: struct_decl.span,
                            });
                        } else if let Some(struct_decl) =
                            provider.program.structs.iter().find(|s| s.name == *name)
                        {
                            external_structs.push(ExternalStruct {
                                name: name.clone(),
                                fields: struct_decl
                                    .fields
                                    .iter()
                                    .map(|field| (field.name.clone(), field.ty.clone()))
                                    .collect(),
                                span: struct_decl.name_span,
                            });
                        }
                    }
                    DeclKind::Enum => {
                        if let Some(enum_decl) = checked_provider
                            .and_then(|program| {
                                external_enum_from_checked_module(provider, program, name)
                            })
                            .or_else(|| external_enum_from_module(provider, name))
                        {
                            external_enums.push(enum_decl);
                        }
                    }
                    DeclKind::TypeAlias => {}
                }
            }

            match check::check_program_with_imports(
                &module.file,
                module.program.clone(),
                &external_functions,
                &external_structs,
                &external_enums,
            ) {
                Ok(checked) => {
                    checked_by_module.insert((*package_index, module.name.clone()), checked);
                }
                Err(mut errs) => check_errors.append(&mut errs),
            }
        }
    }

    if !check_errors.is_empty() {
        return Err(ProjectLoadFailure {
            diagnostics: check_errors,
            sources,
            artifact: Some(workspace_artifact(
                &workspace,
                &packages,
                Some(&package_order),
                Some(&module_orders),
                None,
            )),
        });
    }

    let mut combined = CheckedProgram {
        module: workspace
            .root
            .file_name()
            .and_then(|name| name.to_str())
            .unwrap_or("workspace")
            .to_string(),
        enums: Vec::new(),
        structs: Vec::new(),
        c_imports: Vec::new(),
        functions: Vec::new(),
        tests: Vec::new(),
    };

    for package_index in &package_order {
        let package = &packages[*package_index];
        let module_order = module_orders
            .get(package_index)
            .expect("module order was computed");
        for module_index in module_order {
            let module = &package.modules[*module_index];
            let mut checked = checked_by_module
                .remove(&(*package_index, module.name.clone()))
                .expect("module was checked");
            let symbols = workspace_symbol_map(
                &packages,
                *package_index,
                *module_index,
                &import_maps[*package_index][*module_index],
            );
            rewrite_checked_program(&mut checked, &symbols);
            combined.enums.append(&mut checked.enums);
            combined.structs.append(&mut checked.structs);
            combined.c_imports.append(&mut checked.c_imports);
            combined.functions.append(&mut checked.functions);
            combined.tests.append(&mut checked.tests);
        }
    }

    let mut selected_build_entry_package = None;
    if require_entry_wrapper {
        match select_workspace_build_entry(&workspace, &packages, &module_maps) {
            Ok(package_index) => {
                selected_build_entry_package = Some(package_index);
                add_workspace_entry_wrapper(
                    &packages,
                    package_index,
                    &module_maps[package_index],
                    &mut combined,
                    &mut diagnostics,
                );
            }
            Err(diagnostic) => diagnostics.push(diagnostic),
        }
        if !diagnostics.is_empty() {
            return Err(ProjectLoadFailure {
                diagnostics,
                sources,
                artifact: Some(workspace_artifact(
                    &workspace,
                    &packages,
                    Some(&package_order),
                    Some(&module_orders),
                    selected_build_entry_package.map(|index| packages[index].manifest.name.clone()),
                )),
            });
        }
    }

    let artifact = workspace_artifact(
        &workspace,
        &packages,
        Some(&package_order),
        Some(&module_orders),
        selected_build_entry_package.map(|index| packages[index].manifest.name.clone()),
    );

    Ok(CheckedProject {
        program: combined,
        sources,
        artifact,
    })
}

fn resolve_and_check(
    manifest: Manifest,
    modules: Vec<ModuleUnit>,
    sources: Vec<SourceFile>,
    require_entry_wrapper: bool,
) -> Result<CheckedProject, ProjectLoadFailure> {
    let mut diagnostics = Vec::new();
    let mut by_name = HashMap::<String, usize>::new();
    for (index, module) in modules.iter().enumerate() {
        if let Some(original) = by_name.insert(module.name.clone(), index) {
            diagnostics.push(duplicate_name(
                &module.file,
                &module.name,
                Span::new(0, 0),
                modules[original]
                    .program
                    .functions
                    .first()
                    .map_or(Span::new(0, 0), |f| f.span),
            ));
        }
    }

    if !by_name.contains_key(&manifest.entry) {
        diagnostics.push(Diagnostic::new(
            &manifest.path.display().to_string(),
            "MissingImport",
            format!("entry module `{}` does not exist", manifest.entry),
        ));
    }

    let export_maps = build_export_maps(&modules, &mut diagnostics);
    let import_maps = resolve_imports(&modules, &by_name, &export_maps, &mut diagnostics);
    detect_import_cycle(&modules, &by_name, &mut diagnostics);

    if !diagnostics.is_empty() {
        return Err(ProjectLoadFailure {
            diagnostics,
            sources,
            artifact: Some(artifact_from_modules(&manifest, &modules, None)),
        });
    }

    let order = topo_order(&modules, &by_name);
    let mut checked_by_module = HashMap::<String, CheckedProgram>::new();
    let mut check_errors = Vec::new();
    for module_index in &order {
        let module = &modules[*module_index];
        let imports = &import_maps[*module_index];
        let mut external_functions = Vec::new();
        let mut external_structs = Vec::new();
        let mut external_enums = Vec::new();
        for (name, binding) in imports {
            let provider = &modules[by_name[&binding.provider]];
            let checked_provider = checked_by_module.get(&binding.provider);
            match binding.kind {
                DeclKind::Function => {
                    if let Some(function) = checked_provider
                        .and_then(|program| program.functions.iter().find(|f| f.name == *name))
                    {
                        external_functions.push(ExternalFunction {
                            name: name.clone(),
                            params: function.params.iter().map(|(_, ty)| ty.clone()).collect(),
                            return_type: function.return_type.clone(),
                            foreign: false,
                        });
                    } else if let Some(function) =
                        provider.program.functions.iter().find(|f| f.name == *name)
                    {
                        external_functions.push(ExternalFunction {
                            name: name.clone(),
                            params: function.params.iter().map(|p| p.ty.clone()).collect(),
                            return_type: function.return_type.clone(),
                            foreign: false,
                        });
                    }
                }
                DeclKind::CImport => {
                    if let Some(import) = checked_provider
                        .and_then(|program| program.c_imports.iter().find(|f| f.name == *name))
                    {
                        external_functions.push(ExternalFunction {
                            name: name.clone(),
                            params: import.params.iter().map(|(_, ty)| ty.clone()).collect(),
                            return_type: import.return_type.clone(),
                            foreign: true,
                        });
                    } else if let Some(import) =
                        provider.program.c_imports.iter().find(|f| f.name == *name)
                    {
                        external_functions.push(ExternalFunction {
                            name: name.clone(),
                            params: import.params.iter().map(|p| p.ty.clone()).collect(),
                            return_type: import.return_type.clone(),
                            foreign: true,
                        });
                    }
                }
                DeclKind::Struct => {
                    if let Some(struct_decl) = checked_provider
                        .and_then(|program| program.structs.iter().find(|s| s.name == *name))
                    {
                        external_structs.push(ExternalStruct {
                            name: name.clone(),
                            fields: struct_decl.fields.clone(),
                            span: struct_decl.span,
                        });
                    } else if let Some(struct_decl) =
                        provider.program.structs.iter().find(|s| s.name == *name)
                    {
                        external_structs.push(ExternalStruct {
                            name: name.clone(),
                            fields: struct_decl
                                .fields
                                .iter()
                                .map(|field| (field.name.clone(), field.ty.clone()))
                                .collect(),
                            span: struct_decl.name_span,
                        });
                    }
                }
                DeclKind::Enum => {
                    if let Some(enum_decl) = checked_provider
                        .and_then(|program| {
                            external_enum_from_checked_module(provider, program, name)
                        })
                        .or_else(|| external_enum_from_module(provider, name))
                    {
                        external_enums.push(enum_decl);
                    }
                }
                DeclKind::TypeAlias => {}
            }
        }

        match check::check_program_with_imports(
            &module.file,
            module.program.clone(),
            &external_functions,
            &external_structs,
            &external_enums,
        ) {
            Ok(checked) => {
                checked_by_module.insert(module.name.clone(), checked);
            }
            Err(mut errs) => check_errors.append(&mut errs),
        }
    }

    if !check_errors.is_empty() {
        return Err(ProjectLoadFailure {
            diagnostics: check_errors,
            sources,
            artifact: Some(artifact_from_modules(&manifest, &modules, Some(&order))),
        });
    }

    let mut combined = CheckedProgram {
        module: manifest.name.clone(),
        enums: Vec::new(),
        structs: Vec::new(),
        c_imports: Vec::new(),
        functions: Vec::new(),
        tests: Vec::new(),
    };

    for module_index in &order {
        let module = &modules[*module_index];
        let mut checked = checked_by_module
            .remove(&module.name)
            .expect("module was checked");
        let symbols = symbol_map(module, &import_maps[*module_index]);
        rewrite_checked_program(&mut checked, &symbols);
        combined.enums.append(&mut checked.enums);
        combined.structs.append(&mut checked.structs);
        combined.c_imports.append(&mut checked.c_imports);
        combined.functions.append(&mut checked.functions);
        combined.tests.append(&mut checked.tests);
    }

    if require_entry_wrapper {
        add_entry_wrapper(
            &manifest,
            &modules,
            &by_name,
            &mut combined,
            &mut diagnostics,
        );
        if !diagnostics.is_empty() {
            return Err(ProjectLoadFailure {
                diagnostics,
                sources,
                artifact: Some(artifact_from_modules(&manifest, &modules, Some(&order))),
            });
        }
    }

    let artifact = artifact_from_modules(&manifest, &modules, Some(&order));

    Ok(CheckedProject {
        program: combined,
        sources,
        artifact,
    })
}

fn build_export_maps(
    modules: &[ModuleUnit],
    diagnostics: &mut Vec<Diagnostic>,
) -> Vec<HashMap<String, DeclKind>> {
    modules
        .iter()
        .map(|module| {
            let mut exports = HashMap::new();
            for export in &module.exports {
                let kind = module
                    .local_functions
                    .get(&export.name)
                    .or_else(|| module.local_structs.get(&export.name))
                    .or_else(|| module.local_enums.get(&export.name))
                    .map(|decl| decl.kind);
                match kind {
                    Some(kind) => {
                        exports.insert(export.name.clone(), kind);
                    }
                    None if module.local_aliases.contains_key(&export.name) => diagnostics.push(
                        Diagnostic::new(
                            &module.file,
                            "Visibility",
                            format!(
                                "type alias `{}` is module-local and cannot be exported",
                                export.name
                            ),
                        )
                        .with_span(export.span)
                        .related(
                            "type alias declaration",
                            module.local_aliases[&export.name].span,
                        )
                        .hint("export functions, structs, or enums; imported signatures see concrete target types"),
                    ),
                    None => diagnostics.push(
                        Diagnostic::new(
                            &module.file,
                            "Visibility",
                            format!(
                                "exported name `{}` is not a local function, struct, or enum",
                                export.name
                            ),
                        )
                        .with_span(export.span),
                    ),
                }
            }
            exports
        })
        .collect()
}

fn resolve_imports(
    modules: &[ModuleUnit],
    by_name: &HashMap<String, usize>,
    export_maps: &[HashMap<String, DeclKind>],
    diagnostics: &mut Vec<Diagnostic>,
) -> Vec<HashMap<String, ImportBinding>> {
    modules
        .iter()
        .map(|module| {
            let mut imports = HashMap::<String, ImportBinding>::new();
            for import in &module.imports {
                let Some(provider_index) = by_name.get(&import.module).copied() else {
                    diagnostics.push(
                        Diagnostic::new(
                            &module.file,
                            "MissingImport",
                            format!("imported module `{}` does not exist", import.module),
                        )
                        .with_span(import.module_span),
                    );
                    continue;
                };
                let provider = &modules[provider_index];
                for name in &import.names {
                    if let Some(local) = module
                        .local_functions
                        .get(&name.name)
                        .or_else(|| module.local_structs.get(&name.name))
                        .or_else(|| module.local_enums.get(&name.name))
                        .or_else(|| module.local_aliases.get(&name.name))
                    {
                        diagnostics.push(duplicate_name(
                            &module.file,
                            &name.name,
                            name.span,
                            local.span,
                        ));
                        continue;
                    }

                    if let Some(existing) = imports.get(&name.name) {
                        if existing.provider == provider.name {
                            diagnostics.push(
                                Diagnostic::new(
                                    &module.file,
                                    "DuplicateName",
                                    format!("duplicate imported name `{}`", name.name),
                                )
                                .with_span(name.span)
                                .related("original imported name", existing.import_span),
                            );
                        } else {
                            diagnostics.push(
                                Diagnostic::new(
                                    &module.file,
                                    "AmbiguousName",
                                    format!(
                                        "name `{}` is imported from multiple modules",
                                        name.name
                                    ),
                                )
                                .with_span(name.span)
                                .related("previous imported candidate", existing.import_span),
                            );
                        }
                        continue;
                    }

                    let provider_local = provider
                        .local_functions
                        .get(&name.name)
                        .or_else(|| provider.local_structs.get(&name.name))
                        .or_else(|| provider.local_enums.get(&name.name))
                        .or_else(|| provider.local_aliases.get(&name.name));
                    let exported = export_maps[provider_index].get(&name.name).copied();
                    match (provider_local, exported) {
                        (_, Some(kind)) => {
                            imports.insert(
                                name.name.clone(),
                                ImportBinding {
                                    provider: provider.name.clone(),
                                    kind,
                                    import_span: name.span,
                                },
                            );
                        }
                        (Some(decl), None) if decl.kind == DeclKind::TypeAlias => diagnostics.push(
                            Diagnostic::new(
                                &module.file,
                                "Visibility",
                                format!(
                                    "type alias `{}` is module-local and cannot be imported from module `{}`",
                                    name.name, provider.name
                                ),
                            )
                            .with_span(name.span)
                            .related_in_file(
                                provider.file.clone(),
                                "type alias declaration",
                                decl.span,
                            )
                            .hint("import functions, structs, or enums; function signatures expose the alias target type"),
                        ),
                        (Some(decl), None) => diagnostics.push(
                            Diagnostic::new(
                                &module.file,
                                "Visibility",
                                format!(
                                    "name `{}` is not exported by module `{}`",
                                    name.name, provider.name
                                ),
                            )
                            .with_span(name.span)
                            .related_in_file(
                                provider.file.clone(),
                                "private declaration",
                                decl.span,
                            ),
                        ),
                        (None, None) => diagnostics.push(
                            Diagnostic::new(
                                &module.file,
                                "MissingImport",
                                format!(
                                    "module `{}` has no exported function, struct, or enum `{}`",
                                    provider.name, name.name
                                ),
                            )
                            .with_span(name.span),
                        ),
                    }
                }
            }
            imports
        })
        .collect()
}

fn resolve_workspace_imports(
    packages: &[PackageUnit],
    module_maps: &[HashMap<String, usize>],
    export_maps: &[Vec<HashMap<String, DeclKind>>],
    diagnostics: &mut Vec<Diagnostic>,
) -> Vec<Vec<HashMap<String, WorkspaceImportBinding>>> {
    let mut package_by_name = HashMap::<String, usize>::new();
    for (index, package) in packages.iter().enumerate() {
        package_by_name.insert(package.manifest.name.clone(), index);
    }

    packages
        .iter()
        .enumerate()
        .map(|(package_index, package)| {
            package
                .modules
                .iter()
                .enumerate()
                .map(|(module_index, module)| {
                    let mut imports = HashMap::<String, WorkspaceImportBinding>::new();
                    for import in &module.imports {
                        let Some((provider_package_index, provider_module_index)) =
                            resolve_workspace_import_module(
                                packages,
                                module_maps,
                                &package_by_name,
                                package_index,
                                module,
                                import,
                                diagnostics,
                            )
                        else {
                            continue;
                        };
                        let provider =
                            &packages[provider_package_index].modules[provider_module_index];
                        for name in &import.names {
                            if let Some(local) = module
                            .local_functions
                            .get(&name.name)
                            .or_else(|| module.local_structs.get(&name.name))
                            .or_else(|| module.local_enums.get(&name.name))
                            .or_else(|| module.local_aliases.get(&name.name))
                        {
                            diagnostics.push(duplicate_name(
                                &module.file,
                                    &name.name,
                                    name.span,
                                    local.span,
                                ));
                                continue;
                            }

                            if let Some(existing) = imports.get(&name.name) {
                                if existing.provider_package == provider_package_index
                                    && existing.provider_module == provider_module_index
                                {
                                    diagnostics.push(
                                        Diagnostic::new(
                                            &module.file,
                                            "DuplicateName",
                                            format!("duplicate imported name `{}`", name.name),
                                        )
                                        .with_span(name.span)
                                        .related("original imported name", existing.import_span),
                                    );
                                } else {
                                    diagnostics.push(
                                        Diagnostic::new(
                                            &module.file,
                                            "AmbiguousName",
                                            format!(
                                                "name `{}` is imported from multiple modules",
                                                name.name
                                            ),
                                        )
                                        .with_span(name.span)
                                        .related(
                                            "previous imported candidate",
                                            existing.import_span,
                                        ),
                                    );
                                }
                                continue;
                            }

                            let provider_local = provider
                                .local_functions
                                .get(&name.name)
                                .or_else(|| provider.local_structs.get(&name.name))
                                .or_else(|| provider.local_enums.get(&name.name))
                                .or_else(|| provider.local_aliases.get(&name.name));
                            let exported = export_maps[provider_package_index]
                                [provider_module_index]
                                .get(&name.name)
                                .copied();
                            match (provider_local, exported) {
                                (_, Some(kind)) => {
                                    imports.insert(
                                        name.name.clone(),
                                        WorkspaceImportBinding {
                                            provider_package: provider_package_index,
                                            provider_module: provider_module_index,
                                            kind,
                                            import_span: name.span,
                                        },
                                    );
                                }
                                (Some(decl), None) if decl.kind == DeclKind::TypeAlias => {
                                    diagnostics.push(
                                        Diagnostic::new(
                                            &module.file,
                                            "Visibility",
                                            format!(
                                                "type alias `{}` is module-local and cannot be imported from module `{}`",
                                                name.name, import.module
                                            ),
                                        )
                                        .with_span(name.span)
                                        .related_in_file(
                                            provider.file.clone(),
                                            "type alias declaration",
                                            decl.span,
                                        )
                                        .hint("import functions, structs, or enums; function signatures expose the alias target type"),
                                    );
                                }
                                (Some(decl), None) => diagnostics.push(
                                    Diagnostic::new(
                                        &module.file,
                                        "Visibility",
                                        format!(
                                            "name `{}` is not exported by module `{}`",
                                            name.name, import.module
                                        ),
                                    )
                                    .with_span(name.span)
                                    .related_in_file(
                                        provider.file.clone(),
                                        "private declaration",
                                        decl.span,
                                    ),
                                ),
                                (None, None) => diagnostics.push(
                                    Diagnostic::new(
                                        &module.file,
                                        "MissingImport",
                                        format!(
                                            "module `{}` has no exported function, struct, or enum `{}`",
                                            import.module, name.name
                                        ),
                                    )
                                    .with_span(name.span),
                                ),
                            }
                        }
                    }
                    let _ = module_index;
                    imports
                })
                .collect()
        })
        .collect()
}

fn resolve_workspace_import_module(
    packages: &[PackageUnit],
    module_maps: &[HashMap<String, usize>],
    package_by_name: &HashMap<String, usize>,
    package_index: usize,
    module: &ModuleUnit,
    import: &ImportDecl,
    diagnostics: &mut Vec<Diagnostic>,
) -> Option<(usize, usize)> {
    if standard_module_leaf(&import.module).is_some() {
        let Some(provider_module_index) = module_maps[package_index].get(&import.module).copied()
        else {
            diagnostics.push(
                Diagnostic::new(
                    &module.file,
                    "MissingImport",
                    format!("imported module `{}` does not exist", import.module),
                )
                .with_span(import.module_span),
            );
            return None;
        };
        return Some((package_index, provider_module_index));
    }

    if let Some((package_name, module_name)) = import.module.split_once('.') {
        if module_name.contains('.') || package_name.is_empty() || module_name.is_empty() {
            diagnostics.push(
                Diagnostic::new(
                    &module.file,
                    "MissingPackageModule",
                    format!(
                        "package-qualified import `{}` must be `<package>.<module>`",
                        import.module
                    ),
                )
                .with_span(import.module_span),
            );
            return None;
        }
        let Some(provider_package_index) = package_by_name.get(package_name).copied() else {
            diagnostics.push(
                Diagnostic::new(
                    &module.file,
                    "MissingPackageDependency",
                    format!("imported package `{}` does not exist", package_name),
                )
                .with_span(import.module_span),
            );
            return None;
        };
        if !packages[package_index]
            .dependency_indices
            .contains(&provider_package_index)
        {
            diagnostics.push(
                Diagnostic::new(
                    &module.file,
                    "PackageImportNotDependency",
                    format!(
                        "package `{}` is not a direct dependency of `{}`",
                        package_name, packages[package_index].manifest.name
                    ),
                )
                .with_span(import.module_span),
            );
            return None;
        }
        let Some(provider_module_index) = module_maps[provider_package_index]
            .get(module_name)
            .copied()
        else {
            diagnostics.push(
                Diagnostic::new(
                    &module.file,
                    "MissingPackageModule",
                    format!("package `{}` has no module `{}`", package_name, module_name),
                )
                .with_span(import.module_span),
            );
            return None;
        };
        return Some((provider_package_index, provider_module_index));
    }

    let Some(provider_module_index) = module_maps[package_index].get(&import.module).copied()
    else {
        diagnostics.push(
            Diagnostic::new(
                &module.file,
                "MissingImport",
                format!("imported module `{}` does not exist", import.module),
            )
            .with_span(import.module_span),
        );
        return None;
    };
    Some((package_index, provider_module_index))
}

fn detect_import_cycle(
    modules: &[ModuleUnit],
    by_name: &HashMap<String, usize>,
    diagnostics: &mut Vec<Diagnostic>,
) {
    let mut state = vec![0_u8; modules.len()];
    let mut stack = Vec::new();
    for index in 0..modules.len() {
        if state[index] == 0 && dfs_cycle(index, modules, by_name, &mut state, &mut stack) {
            let cycle_start = stack
                .last()
                .and_then(|last| {
                    stack[..stack.len().saturating_sub(1)]
                        .iter()
                        .position(|idx| idx == last)
                })
                .unwrap_or(0);
            let cycle_nodes = &stack[cycle_start..];
            let cycle = cycle_nodes
                .iter()
                .map(|idx| modules[*idx].name.as_str())
                .collect::<Vec<_>>()
                .join(" -> ");
            let primary_edge = cycle_nodes
                .windows(2)
                .last()
                .and_then(|edge| import_edge(&modules[edge[0]], &modules[edge[1]].name));
            let primary_file = primary_edge
                .map(|(file, _)| file)
                .unwrap_or_else(|| modules[index].file.as_str());
            let primary_span = primary_edge.map(|(_, span)| span);
            let mut diagnostic = Diagnostic::new(
                primary_file,
                "ImportCycle",
                format!("project imports contain a cycle: {}", cycle),
            );
            if let Some(span) = primary_span {
                diagnostic = diagnostic.with_span(span);
            }
            for edge in cycle_nodes.windows(2) {
                if let Some((file, span)) = import_edge(&modules[edge[0]], &modules[edge[1]].name) {
                    diagnostic = diagnostic.related_in_file(
                        file.to_string(),
                        format!(
                            "module `{}` imports `{}`",
                            modules[edge[0]].name, modules[edge[1]].name
                        ),
                        span,
                    );
                }
            }
            diagnostics.push(diagnostic);
            return;
        }
    }
}

fn import_edge<'a>(from: &'a ModuleUnit, to: &str) -> Option<(&'a str, Span)> {
    from.imports
        .iter()
        .find(|import| import.module == to)
        .map(|import| (from.file.as_str(), import.module_span))
}

fn dfs_cycle(
    index: usize,
    modules: &[ModuleUnit],
    by_name: &HashMap<String, usize>,
    state: &mut [u8],
    stack: &mut Vec<usize>,
) -> bool {
    state[index] = 1;
    stack.push(index);
    for import in &modules[index].imports {
        let Some(next) = by_name.get(&import.module).copied() else {
            continue;
        };
        if state[next] == 1 {
            stack.push(next);
            return true;
        }
        if state[next] == 0 && dfs_cycle(next, modules, by_name, state, stack) {
            return true;
        }
    }
    stack.pop();
    state[index] = 2;
    false
}

fn topo_order(modules: &[ModuleUnit], by_name: &HashMap<String, usize>) -> Vec<usize> {
    let mut indegree = vec![0_usize; modules.len()];
    let mut dependents = vec![Vec::<usize>::new(); modules.len()];
    for (index, module) in modules.iter().enumerate() {
        let mut seen = HashSet::new();
        for import in &module.imports {
            if let Some(provider) = by_name.get(&import.module).copied() {
                if seen.insert(provider) {
                    indegree[index] += 1;
                    dependents[provider].push(index);
                }
            }
        }
    }
    for deps in &mut dependents {
        deps.sort_by(|left, right| modules[*left].name.cmp(&modules[*right].name));
    }

    let mut ready = modules
        .iter()
        .enumerate()
        .filter(|(index, _)| indegree[*index] == 0)
        .map(|(index, _)| index)
        .collect::<VecDeque<_>>();
    ready
        .make_contiguous()
        .sort_by(|left, right| modules[*left].name.cmp(&modules[*right].name));

    let mut order = Vec::new();
    while let Some(index) = ready.pop_front() {
        order.push(index);
        for dependent in &dependents[index] {
            indegree[*dependent] -= 1;
            if indegree[*dependent] == 0 {
                ready.push_back(*dependent);
                ready
                    .make_contiguous()
                    .sort_by(|left, right| modules[*left].name.cmp(&modules[*right].name));
            }
        }
    }
    order
}

fn package_topo_order(packages: &[PackageUnit]) -> Vec<usize> {
    let mut indegree = vec![0_usize; packages.len()];
    let mut dependents = vec![Vec::<usize>::new(); packages.len()];
    for (index, package) in packages.iter().enumerate() {
        let mut seen = HashSet::new();
        for dependency in &package.dependency_indices {
            if seen.insert(*dependency) {
                indegree[index] += 1;
                dependents[*dependency].push(index);
            }
        }
    }
    for deps in &mut dependents {
        deps.sort_by(|left, right| {
            packages[*left]
                .manifest
                .name
                .cmp(&packages[*right].manifest.name)
        });
    }

    let mut ready = packages
        .iter()
        .enumerate()
        .filter(|(index, _)| indegree[*index] == 0)
        .map(|(index, _)| index)
        .collect::<VecDeque<_>>();
    ready.make_contiguous().sort_by(|left, right| {
        packages[*left]
            .manifest
            .name
            .cmp(&packages[*right].manifest.name)
    });

    let mut order = Vec::new();
    while let Some(index) = ready.pop_front() {
        order.push(index);
        for dependent in &dependents[index] {
            indegree[*dependent] -= 1;
            if indegree[*dependent] == 0 {
                ready.push_back(*dependent);
                ready.make_contiguous().sort_by(|left, right| {
                    packages[*left]
                        .manifest
                        .name
                        .cmp(&packages[*right].manifest.name)
                });
            }
        }
    }
    order
}

fn symbol_map(
    module: &ModuleUnit,
    imports: &HashMap<String, ImportBinding>,
) -> HashMap<String, String> {
    let mut symbols = HashMap::new();
    for (name, decl) in &module.local_functions {
        if decl.kind == DeclKind::Function {
            symbols.insert(name.clone(), symbol_name(&module.name, name));
        }
    }
    for name in module.local_structs.keys() {
        symbols.insert(name.clone(), symbol_name(&module.name, name));
    }
    for name in module.local_enums.keys() {
        symbols.insert(name.clone(), symbol_name(&module.name, name));
    }
    for (name, binding) in imports {
        if matches!(
            binding.kind,
            DeclKind::Function | DeclKind::Struct | DeclKind::Enum
        ) {
            symbols.insert(name.clone(), symbol_name(&binding.provider, name));
        }
    }
    symbols
}

fn workspace_symbol_map(
    packages: &[PackageUnit],
    package_index: usize,
    module_index: usize,
    imports: &HashMap<String, WorkspaceImportBinding>,
) -> HashMap<String, String> {
    let package = &packages[package_index];
    let module = &package.modules[module_index];
    let mut symbols = HashMap::new();
    for (name, decl) in &module.local_functions {
        if decl.kind == DeclKind::Function {
            symbols.insert(
                name.clone(),
                package_symbol_name(&package.manifest.name, &module.name, name),
            );
        }
    }
    for name in module.local_structs.keys() {
        symbols.insert(
            name.clone(),
            package_symbol_name(&package.manifest.name, &module.name, name),
        );
    }
    for name in module.local_enums.keys() {
        symbols.insert(
            name.clone(),
            package_symbol_name(&package.manifest.name, &module.name, name),
        );
    }
    for (name, binding) in imports {
        if matches!(
            binding.kind,
            DeclKind::Function | DeclKind::Struct | DeclKind::Enum
        ) {
            let provider_package = &packages[binding.provider_package];
            let provider_module = &provider_package.modules[binding.provider_module];
            symbols.insert(
                name.clone(),
                package_symbol_name(&provider_package.manifest.name, &provider_module.name, name),
            );
        }
    }
    symbols
}

fn rewrite_checked_program(program: &mut CheckedProgram, symbols: &HashMap<String, String>) {
    for enum_decl in &mut program.enums {
        if let Some(name) = symbols.get(&enum_decl.name) {
            enum_decl.name = name.clone();
        }
        for variant in &mut enum_decl.variants {
            if let Some(payload_ty) = &mut variant.payload_ty {
                rewrite_type(payload_ty, symbols);
            }
        }
    }
    for struct_decl in &mut program.structs {
        if let Some(name) = symbols.get(&struct_decl.name) {
            struct_decl.name = name.clone();
        }
        for (_, ty) in &mut struct_decl.fields {
            rewrite_type(ty, symbols);
        }
    }
    for function in &mut program.functions {
        if let Some(name) = symbols.get(&function.name) {
            function.name = name.clone();
        }
        for (_, ty) in &mut function.params {
            rewrite_type(ty, symbols);
        }
        rewrite_type(&mut function.return_type, symbols);
        for expr in &mut function.body {
            rewrite_expr(expr, symbols);
        }
    }
    for test in &mut program.tests {
        for expr in &mut test.body {
            rewrite_expr(expr, symbols);
        }
    }
}

fn rewrite_expr(expr: &mut TExpr, symbols: &HashMap<String, String>) {
    rewrite_type(&mut expr.ty, symbols);
    match &mut expr.kind {
        TExprKind::StructInit { name, fields } => {
            if let Some(symbol) = symbols.get(name) {
                *name = symbol.clone();
            }
            for (_, value) in fields {
                rewrite_expr(value, symbols);
            }
        }
        TExprKind::ArrayInit { elements } => {
            for element in elements {
                rewrite_expr(element, symbols);
            }
        }
        TExprKind::OptionSome { value }
        | TExprKind::ResultOk { value }
        | TExprKind::ResultErr { value }
        | TExprKind::OptionIsSome { value }
        | TExprKind::OptionIsNone { value }
        | TExprKind::OptionUnwrapSome { value }
        | TExprKind::ResultIsOk { value, .. }
        | TExprKind::ResultIsErr { value, .. }
        | TExprKind::ResultUnwrapOk { value, .. }
        | TExprKind::ResultUnwrapErr { value, .. }
        | TExprKind::FieldAccess { value, .. } => rewrite_expr(value, symbols),
        TExprKind::Index { array, index } => {
            rewrite_expr(array, symbols);
            rewrite_expr(index, symbols);
        }
        TExprKind::Local { initializer, .. } => rewrite_expr(initializer, symbols),
        TExprKind::Set { expr, .. } => rewrite_expr(expr, symbols),
        TExprKind::Binary { left, right, .. } => {
            rewrite_expr(left, symbols);
            rewrite_expr(right, symbols);
        }
        TExprKind::If {
            condition,
            then_expr,
            else_expr,
        } => {
            rewrite_expr(condition, symbols);
            rewrite_expr(then_expr, symbols);
            rewrite_expr(else_expr, symbols);
        }
        TExprKind::Match { subject, arms } => {
            rewrite_expr(subject, symbols);
            for arm in arms {
                rewrite_match_pattern(&mut arm.pattern, symbols);
                for expr in &mut arm.body {
                    rewrite_expr(expr, symbols);
                }
            }
        }
        TExprKind::While { condition, body } => {
            rewrite_expr(condition, symbols);
            for expr in body {
                rewrite_expr(expr, symbols);
            }
        }
        TExprKind::Unsafe { body } => {
            for expr in body {
                rewrite_expr(expr, symbols);
            }
        }
        TExprKind::Call { name, args } => {
            if let Some(symbol) = symbols.get(name) {
                *name = symbol.clone();
            }
            for arg in args {
                rewrite_expr(arg, symbols);
            }
        }
        TExprKind::EnumVariant {
            enum_name, payload, ..
        } => {
            if let Some(symbol) = symbols.get(enum_name) {
                *enum_name = symbol.clone();
            }
            if let Some(payload) = payload {
                rewrite_expr(payload, symbols);
            }
        }
        TExprKind::Int(_)
        | TExprKind::Int64(_)
        | TExprKind::UInt32(_)
        | TExprKind::UInt64(_)
        | TExprKind::Float(_)
        | TExprKind::Bool(_)
        | TExprKind::String(_)
        | TExprKind::Var(_)
        | TExprKind::OptionNone => {}
    }
}

fn rewrite_match_pattern(pattern: &mut MatchPatternKind, symbols: &HashMap<String, String>) {
    if let MatchPatternKind::EnumVariant { enum_name, .. } = pattern {
        if let Some(symbol) = symbols.get(enum_name) {
            *enum_name = symbol.clone();
        }
    }
}

fn rewrite_type(ty: &mut Type, symbols: &HashMap<String, String>) {
    match ty {
        Type::Named(name) => {
            if let Some(symbol) = symbols.get(name) {
                *name = symbol.clone();
            }
        }
        Type::Array(inner, _)
        | Type::Vec(inner)
        | Type::Option(inner)
        | Type::Ptr(inner)
        | Type::Slice(inner) => rewrite_type(inner, symbols),
        Type::Result(ok, err) => {
            rewrite_type(ok, symbols);
            rewrite_type(err, symbols);
        }
        Type::I32
        | Type::I64
        | Type::U32
        | Type::U64
        | Type::F64
        | Type::Bool
        | Type::Unit
        | Type::String => {}
    }
}

fn add_entry_wrapper(
    manifest: &Manifest,
    modules: &[ModuleUnit],
    by_name: &HashMap<String, usize>,
    combined: &mut CheckedProgram,
    diagnostics: &mut Vec<Diagnostic>,
) {
    let Some(entry_index) = by_name.get(&manifest.entry).copied() else {
        return;
    };
    let entry_module = &modules[entry_index];
    let Some(entry_function) = entry_module
        .program
        .functions
        .iter()
        .find(|function| function.name == "main")
    else {
        diagnostics.push(Diagnostic::new(
            &entry_module.file,
            "ProjectEntryMainMissing",
            format!(
                "entry module `{}` has no `main` function; build/run require `(fn main () -> i32 ...)`",
                manifest.entry
            ),
        ));
        return;
    };

    if !entry_function.params.is_empty() || entry_function.return_type != Type::I32 {
        diagnostics.push(
            Diagnostic::new(
                &entry_module.file,
                "ProjectEntryMainInvalidSignature",
                format!(
                    "project entry `main` must have signature `(fn main () -> i32 ...)`; found {} parameter(s) and return `{}`",
                    entry_function.params.len(),
                    entry_function.return_type
                ),
            )
            .with_span(entry_function.span),
        );
        return;
    }

    let entry_symbol = symbol_name(&entry_module.name, "main");
    combined.functions.push(CheckedFunction {
        name: "main".to_string(),
        params: Vec::new(),
        return_type: Type::I32,
        body: vec![TExpr {
            ty: Type::I32,
            span: entry_function.span,
            kind: TExprKind::Call {
                name: entry_symbol,
                args: Vec::new(),
            },
        }],
        span: entry_function.span,
        file: entry_module.file.clone(),
    });
}

fn select_workspace_build_entry(
    workspace: &WorkspaceManifest,
    packages: &[PackageUnit],
    module_maps: &[HashMap<String, usize>],
) -> Result<usize, Diagnostic> {
    if let Some(default_package) = &workspace.default_package {
        let Some(package_index) = packages
            .iter()
            .position(|package| package.manifest.name == *default_package)
        else {
            return Err(Diagnostic::new(
                &workspace.path.display().to_string(),
                "WorkspaceDefaultPackageMissing",
                format!(
                    "workspace default_package `{}` is not a workspace package",
                    default_package
                ),
            ));
        };
        let package = &packages[package_index];
        if module_maps[package_index].contains_key(&package.manifest.entry) {
            return Ok(package_index);
        }
        return Err(Diagnostic::new(
            &package.manifest.path.display().to_string(),
            "WorkspaceDefaultPackageEntryMissing",
            format!(
                "workspace default_package `{}` has no entry module `{}`; build/run require one selected package entry module",
                default_package, package.manifest.entry
            ),
        ));
    }

    let candidates = packages
        .iter()
        .enumerate()
        .filter(|(index, package)| module_maps[*index].contains_key(&package.manifest.entry))
        .map(|(index, _)| index)
        .collect::<Vec<_>>();
    if candidates.len() == 1 {
        Ok(candidates[0])
    } else {
        Err(Diagnostic::new(
            &workspace.path.display().to_string(),
            "WorkspaceBuildAmbiguousEntryPackage",
            "workspace build requires exactly one package with its entry module or `[workspace] default_package = \"name\"`",
        ))
    }
}

fn add_workspace_entry_wrapper(
    packages: &[PackageUnit],
    package_index: usize,
    by_name: &HashMap<String, usize>,
    combined: &mut CheckedProgram,
    diagnostics: &mut Vec<Diagnostic>,
) {
    let package = &packages[package_index];
    let Some(entry_index) = by_name.get(&package.manifest.entry).copied() else {
        return;
    };
    let entry_module = &package.modules[entry_index];
    let Some(entry_function) = entry_module
        .program
        .functions
        .iter()
        .find(|function| function.name == "main")
    else {
        diagnostics.push(Diagnostic::new(
            &entry_module.file,
            "WorkspaceEntryMainMissing",
            format!(
                "entry module `{}` in package `{}` has no `main` function; build/run require `(fn main () -> i32 ...)`",
                package.manifest.entry, package.manifest.name
            ),
        ));
        return;
    };

    if !entry_function.params.is_empty() || entry_function.return_type != Type::I32 {
        diagnostics.push(
            Diagnostic::new(
                &entry_module.file,
                "WorkspaceEntryMainInvalidSignature",
                format!(
                    "workspace entry `main` must have signature `(fn main () -> i32 ...)`; found {} parameter(s) and return `{}`",
                    entry_function.params.len(),
                    entry_function.return_type
                ),
            )
            .with_span(entry_function.span),
        );
        return;
    }

    let entry_symbol = package_symbol_name(&package.manifest.name, &entry_module.name, "main");
    combined.functions.push(CheckedFunction {
        name: "main".to_string(),
        params: Vec::new(),
        return_type: Type::I32,
        body: vec![TExpr {
            ty: Type::I32,
            span: entry_function.span,
            kind: TExprKind::Call {
                name: entry_symbol,
                args: Vec::new(),
            },
        }],
        span: entry_function.span,
        file: entry_module.file.clone(),
    });
}

fn duplicate_name(file: &str, name: &str, span: Span, original: Span) -> Diagnostic {
    Diagnostic::new(file, "DuplicateName", format!("duplicate name `{}`", name))
        .with_span(span)
        .related("original declaration", original)
}

fn is_reserved_intrinsic(name: &str) -> bool {
    std_runtime::is_reserved_name(name) || crate::unsafe_ops::is_reserved_head(name)
}

fn symbol_name(module: &str, name: &str) -> String {
    format!(
        "__glagol_{}_{}",
        symbol_component(module),
        symbol_component(name)
    )
}

fn package_symbol_name(package: &str, module: &str, name: &str) -> String {
    format!(
        "__glagol_{}_{}_{}",
        symbol_component(package),
        symbol_component(module),
        symbol_component(name)
    )
}

fn symbol_component(value: &str) -> String {
    let mut out = String::new();
    for byte in value.bytes() {
        match byte {
            b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' => out.push(byte as char),
            _ => out.push_str(&format!("_{:02x}", byte)),
        }
    }
    out
}

fn head_ident(form: &SExpr) -> Option<&str> {
    let items = expect_list(form)?;
    items.first().and_then(expect_ident)
}

fn expect_list(form: &SExpr) -> Option<&[SExpr]> {
    match &form.kind {
        SExprKind::List(items) => Some(items),
        _ => None,
    }
}

fn expect_ident(form: &SExpr) -> Option<&str> {
    match &form.kind {
        SExprKind::Atom(Atom::Ident(name)) => Some(name),
        _ => None,
    }
}

fn module_name_span(form: &SExpr) -> Option<Span> {
    expect_list(form)?.get(1).map(|expr| expr.span)
}
