package p.studio.compiler.services;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import p.studio.compiler.messages.Addressable;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.messages.FESurfaceContext;
import p.studio.compiler.messages.FrontendPhaseContext;
import p.studio.compiler.models.IRGlobalVisibility;
import p.studio.compiler.models.BuildStack;
import p.studio.compiler.models.IRHiddenGlobalKind;
import p.studio.compiler.models.ProjectDescriptor;
import p.studio.compiler.models.SourceHandle;
import p.studio.compiler.models.SourceKind;
import p.studio.compiler.pbs.PbsHostAdmissionErrors;
import p.studio.compiler.pbs.linking.PbsLinkErrors;
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
import p.studio.compiler.pbs.stdlib.InterfaceModuleLoader;
import p.studio.compiler.pbs.stdlib.StdlibEnvironment;
import p.studio.compiler.pbs.stdlib.StdlibEnvironmentResolver;
import p.studio.compiler.pbs.stdlib.StdlibModuleSource;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.identifiers.ProjectId;
import p.studio.compiler.source.tables.FileTable;
import p.studio.compiler.source.tables.ProjectTable;
import p.studio.compiler.utilities.SourceProviderFactory;
import p.studio.utilities.logs.LogAggregator;
import p.studio.utilities.structures.ReadOnlyList;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;

class PBSFrontendPhaseServiceTest {

    @TempDir
    Path tempDir;

    @Test
    void shouldPropagateBackendOwnedAssetSurfaceIntoPbsFrontendCompilation() throws IOException {
        final var projectRoot = tempDir.resolve("project-assets");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("main");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                [Frame]
                fn frame() -> void {
                  let asset: Addressable = assets.ui.panel;
                  return;
                }
                """);
        Files.writeString(modBarrel, "pub fn frame() -> void;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("core")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                1,
                p.studio.compiler.messages.HostAdmissionContext.permissiveDefault(),
                new FESurfaceContext(ReadOnlyList.from(new Addressable("assets.ui.panel", 91))));
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertEquals(0, diagnostics.errorCount(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
        assertEquals(0, diagnostics.warningCount());
        final var frameFn = irBackend.getExecutableFunctions().stream()
                .filter(function -> "frame".equals(function.callableName()))
                .findFirst()
                .orElseThrow();
        assertTrue(frameFn.instructions().stream().anyMatch(instruction ->
                instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.PUSH_I32
                        && "91".equals(instruction.label())));
    }

    @Test
    void shouldKeepWarningsNonBlockingWhileLoweringExplicitDiscard() throws IOException {
        final var projectRoot = tempDir.resolve("project-ignored-values");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("main");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                fn pure() -> int { return 1; }

                [Frame]
                fn frame() -> void {
                  pure();
                  return;
                }
                """);
        Files.writeString(modBarrel, "pub fn frame() -> void;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("core")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertEquals(1, diagnostics.warningCount());
        assertEquals(0, diagnostics.errorCount());
        assertTrue(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.W_SEM_IGNORED_VALUE.name())));
        final var frameFn = irBackend.getExecutableFunctions().stream()
                .filter(function -> "frame".equals(function.callableName()))
                .findFirst()
                .orElseThrow();
        assertTrue(frameFn.instructions().stream().anyMatch(instruction ->
                instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.POP));
    }

    @Test
    void shouldReportInvalidBarrelFilename() throws IOException {
        final var projectRoot = tempDir.resolve("project");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("math");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        final var invalidBarrel = modulePath.resolve("extra.barrel");
        Files.writeString(sourceFile, "fn run() -> int { return 1; }");
        Files.writeString(modBarrel, "pub fn run() -> int;");
        Files.writeString(invalidBarrel, "pub fn run() -> int;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("core")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);
        registerFile(projectId, projectRoot, invalidBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(PbsLinkErrors.E_LINK_INVALID_BARREL_FILENAME.name())));
        assertEquals(0, irBackend.getFunctions().size());
    }

    @Test
    void shouldLowerOnlyModulesThatPassAdmissionGates() throws IOException {
        final var projectRoot = tempDir.resolve("project-mixed");
        final var sourceRoot = projectRoot.resolve("src");
        final var validModulePath = sourceRoot.resolve("valid");
        final var invalidModulePath = sourceRoot.resolve("invalid");
        Files.createDirectories(validModulePath);
        Files.createDirectories(invalidModulePath);

        final var validSource = validModulePath.resolve("source.pbs");
        final var validBarrel = validModulePath.resolve("mod.barrel");
        final var invalidSource = invalidModulePath.resolve("source.pbs");
        Files.writeString(validSource, """
                fn good(v: int) -> int {
                  return v;
                }
                """);
        Files.writeString(validBarrel, "pub fn good(v: int) -> int;");
        Files.writeString(invalidSource, """
                fn bad(v: int) -> int {
                  return v;
                }
                """);

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("core")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, validSource, fileTable);
        registerFile(projectId, projectRoot, validBarrel, fileTable);
        registerFile(projectId, projectRoot, invalidSource, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_MISSING_BARREL.name())));
        assertEquals(1, irBackend.getFunctions().size());
        assertEquals("good", irBackend.getFunctions().getFirst().name());
    }

    @Test
    void shouldEmitCallFuncWithCalleeModuleIdentityFromResolvedTarget() throws IOException {
        final var projectRoot = tempDir.resolve("project-callsite-module-identity");
        final var sourceRoot = projectRoot.resolve("src");
        final var moduleAPath = sourceRoot.resolve("a");
        final var moduleBPath = sourceRoot.resolve("b");
        Files.createDirectories(moduleAPath);
        Files.createDirectories(moduleBPath);

        final var sourceA = moduleAPath.resolve("source.pbs");
        final var barrelA = moduleAPath.resolve("mod.barrel");
        Files.writeString(sourceA, """
                fn target() -> int {
                  return 1;
                }
                """);
        Files.writeString(barrelA, "pub fn target() -> int;");

        final var sourceB = moduleBPath.resolve("source.pbs");
        final var barrelB = moduleBPath.resolve("mod.barrel");
        Files.writeString(sourceB, """
                import { target } from @app:a;

                fn caller() -> int {
                  return target();
                }

                [Frame]
                fn frame() -> void {
                  caller();
                  return;
                }
                """);
        Files.writeString(barrelB, """
                pub fn caller() -> int;
                pub fn frame() -> void;
                """);

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceA, fileTable);
        registerFile(projectId, projectRoot, barrelA, fileTable);
        registerFile(projectId, projectRoot, sourceB, fileTable);
        registerFile(projectId, projectRoot, barrelB, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertEquals(0, diagnostics.errorCount(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
        assertEquals(1, diagnostics.warningCount());

        final var targetFn = irBackend.getExecutableFunctions().stream()
                .filter(function -> "target".equals(function.callableName()))
                .findFirst()
                .orElseThrow();
        final var callerFn = irBackend.getExecutableFunctions().stream()
                .filter(function -> "caller".equals(function.callableName()))
                .findFirst()
                .orElseThrow();
        final var callInstruction = callerFn.instructions().stream()
                .filter(instruction -> instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_FUNC)
                .findFirst()
                .orElseThrow();

        assertNotNull(callInstruction.calleeModuleId());
        assertEquals(targetFn.moduleId().getIndex(), callInstruction.calleeModuleId().getIndex());
        assertTrue(callerFn.moduleId().getIndex() != callInstruction.calleeModuleId().getIndex());
    }

    @Test
    void shouldLowerAllSourcesWhenNoAdmissionErrorsExist() throws IOException {
        final var projectRoot = tempDir.resolve("project-valid");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("math");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                [Frame]
                fn frame() -> void { return; }
                fn sum(a: int, b: int) -> int { return a + b; }
                """);
        Files.writeString(modBarrel, """
                pub fn frame() -> void;
                pub fn sum(a: int, b: int) -> int;
                """);

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("core")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.isEmpty());
        assertEquals(2, irBackend.getFunctions().size());
        assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function -> "frame".equals(function.callableName())));
        assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function ->
                function.callableName().startsWith("__pbs.frame_wrapper$m")));
    }

    @Test
    void shouldRequireExplicitFrameMarkerForExecutableProjects() throws IOException {
        final var projectRoot = tempDir.resolve("project-missing-frame-marker");
        final var sourceRoot = projectRoot.resolve("src");
        Files.createDirectories(sourceRoot);

        final var sourceFile = sourceRoot.resolve("main.pbs");
        final var modBarrel = sourceRoot.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                fn frame() -> void { return; }
                """);
        Files.writeString(modBarrel, "pub fn frame() -> void;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("main")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().anyMatch(d ->
                        d.getCode().equals(PbsSemanticsErrors.E_SEM_MISSING_PROJECT_FRAME.name())),
                diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
    }

    @Test
    void shouldPublishSyntheticFrameWrapperAsEntrypointWhenLifecycleMarkersExist() throws IOException {
        final var projectRoot = tempDir.resolve("project-lifecycle-wrapper");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("game");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                declare global SCORE: int = 0;

                [Init]
                fn boot() -> void { return; }

                [Frame]
                fn frame() -> void { return; }
                """);
        Files.writeString(modBarrel, """
                pub fn boot() -> void;
                pub fn frame() -> void;
                pub global SCORE;
                """);

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("core")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
        assertTrue(irBackend.getEntryPointCallableName().startsWith("__pbs.frame_wrapper$m"));
        final var scoreGlobal = irBackend.getGlobals().stream()
                .filter(global -> "SCORE".equals(global.name()))
                .findFirst()
                .orElseThrow();
        assertEquals(IRGlobalVisibility.PUBLIC, scoreGlobal.visibility());
        final var bootGuard = irBackend.getGlobals().stream()
                .filter(global -> global.hiddenKind() == IRHiddenGlobalKind.BOOT_GUARD)
                .findFirst()
                .orElseThrow();
        assertTrue(bootGuard.isHidden());
        assertEquals(IRGlobalVisibility.MODULE, bootGuard.visibility());
        assertTrue(bootGuard.name().startsWith("__pbs.boot_guard$m"));
        assertEquals(1, bootGuard.slot());
        final var wrapper = irBackend.getExecutableFunctions().stream()
                .filter(function -> irBackend.getEntryPointCallableName().equals(function.callableName()))
                .findFirst()
                .orElseThrow();
        assertEquals(11, wrapper.instructions().size());
        assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.GET_GLOBAL, wrapper.instructions().get(0).kind());
        assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.PUSH_BOOL, wrapper.instructions().get(1).kind());
        assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.EQ, wrapper.instructions().get(2).kind());
        assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.JMP_IF_TRUE, wrapper.instructions().get(3).kind());
        assertTrue(wrapper.instructions().get(4).calleeCallableName().startsWith("__pbs.module_init$m"));
        assertTrue(wrapper.instructions().get(5).calleeCallableName().startsWith("__pbs.project_init$m"));
        assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.PUSH_BOOL, wrapper.instructions().get(6).kind());
        assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.SET_GLOBAL, wrapper.instructions().get(7).kind());
        assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.LABEL, wrapper.instructions().get(8).kind());
        assertEquals("frame", wrapper.instructions().get(9).calleeCallableName());
        assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.RET, wrapper.instructions().get(10).kind());
    }

    @Test
    void shouldSynthesizePositiveTopic19FixtureAcrossFileInitProjectInitAndFrame() throws IOException {
        final var projectRoot = tempDir.resolve("project-topic-19-positive");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("game");
        Files.createDirectories(modulePath);

        final var globalsSource = modulePath.resolve("globals.pbs");
        final var mainSource = modulePath.resolve("main.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(globalsSource, """
                declare global SCORE: int = 0;

                [Init]
                fn warmup() -> void { return; }
                """);
        Files.writeString(mainSource, """
                [Init]
                fn boot() -> void { return; }

                [Frame]
                fn frame() -> void { return; }
                """);
        Files.writeString(modBarrel, """
                pub fn warmup() -> void;
                pub fn boot() -> void;
                pub fn frame() -> void;
                pub global SCORE;
                """);

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("core")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, globalsSource, fileTable);
        registerFile(projectId, projectRoot, mainSource, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
        assertTrue(irBackend.getEntryPointCallableName().startsWith("__pbs.frame_wrapper$m"));
        assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function ->
                function.callableName().startsWith("__pbs.file_init$f")));
        assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function ->
                function.callableName().startsWith("__pbs.module_init$m")));
        assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function ->
                function.callableName().startsWith("__pbs.project_init$m")));
        assertTrue(irBackend.getGlobals().stream().anyMatch(global ->
                "SCORE".equals(global.name()) && global.visibility() == IRGlobalVisibility.PUBLIC));
        assertTrue(irBackend.getGlobals().stream().anyMatch(global -> global.hiddenKind() == IRHiddenGlobalKind.BOOT_GUARD));
    }

    @Test
    void shouldSkipIrLoweringForSourcesMarkedAsSdkInterface() throws IOException {
        final var projectRoot = tempDir.resolve("project-sdk-interface");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("gfx");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, "fn draw() -> int { return 1; }");
        Files.writeString(modBarrel, "pub fn draw() -> int;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("gfx-sdk")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .sourceKind(SourceKind.SDK_INTERFACE)
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_INTERFACE_NON_DECLARATIVE_DECLARATION.name())));
        assertEquals(0, irBackend.getFunctions().size());
    }

    @Test
    void shouldParseReservedDeclarationsWhenSourceKindIsSdkInterface() throws IOException {
        final var projectRoot = tempDir.resolve("project-sdk-interface-reserved");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("gfx");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                [BuiltinType(name = "Color", version = 1)]
                declare builtin type Color(
                  pub r: int,
                  pub g: int,
                  pub b: int
                ) {
                  [IntrinsicCall(name = "color.pack", version = 1)]
                  fn pack() -> int;
                }
                declare host Gfx {
                  [Host(module = "gfx", name = "draw_pixel", version = 1)]
                  [Capability(name = "gfx")]
                  fn draw(x: int, y: int, color: Color) -> void;
                }
                """);
        Files.writeString(modBarrel, "");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("gfx-sdk")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .sourceKind(SourceKind.SDK_INTERFACE)
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name())));
        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_MISSING_BARREL.name())));
        assertEquals(0, irBackend.getFunctions().size());
    }

    @Test
    void shouldResolveReservedImportsThroughStdlibResolverForSelectedMajor() throws IOException {
        final var projectRoot = tempDir.resolve("project-stdlib");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("app");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                import { Gfx } from @sdk:gfx;
                fn use() -> int { return 1; }
                """);
        Files.writeString(modBarrel, "pub fn use() -> int;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var stdlibModule = new StdlibModuleSource(
                "sdk",
                ReadOnlyList.wrap(List.of("gfx")),
                ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile(
                        "gfx.pbs",
                        """
                        declare host Gfx {
                          [Host(module = "gfx", name = "draw_pixel", version = 1)]
                          [Capability(name = "gfx")]
                          fn draw(x: int, y: int) -> void;
                        }
                        """))),
                "pub host Gfx;");
        final var frontendService = new PBSFrontendPhaseService(
                resolverForMajor(7, stdlibModule),
                new InterfaceModuleLoader(2_000_000));

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                7);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = frontendService.compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name())));
        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_UNRESOLVED.name())));
        assertEquals(1, irBackend.getFunctions().size());
        assertEquals("use", irBackend.getFunctions().getFirst().name());
    }

    @Test
    void shouldReportReservedImportModuleNotFoundWhenStdlibResolverMissesModule() throws IOException {
        final var projectRoot = tempDir.resolve("project-stdlib-missing");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("app");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                import { draw } from @sdk:gfx;
                fn use() -> int { return 1; }
                """);
        Files.writeString(modBarrel, "pub fn use() -> int;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var frontendService = new PBSFrontendPhaseService(
                resolverForMajor(7),
                new InterfaceModuleLoader(2_100_000));
        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                7);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = frontendService.compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name())));
        assertEquals(0, irBackend.getFunctions().size());
    }

    @Test
    void shouldResolveCoreColorImportFromBootstrapStdlib() throws IOException {
        final var projectRoot = tempDir.resolve("project-bootstrap-core-color");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("app");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                import { Color } from @core:color;
                declare contract Palette {
                  fn pick(base: Color) -> Color;
                }
                """);
        Files.writeString(modBarrel, "pub contract Palette;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                1);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name())));
        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_UNRESOLVED.name())));
        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name())));
        assertEquals(0, irBackend.getFunctions().size());
        assertTrue(irBackend.getReservedMetadata().builtinTypeSurfaces().stream()
                .anyMatch(t -> t.sourceTypeName().equals("Color") && t.fields().size() == 1));
    }

    @Test
    void shouldResolveSdkGfxImportFromBootstrapStdlib() throws IOException {
        final var projectRoot = tempDir.resolve("project-bootstrap-sdk-gfx");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("app");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                import { Gfx } from @sdk:gfx;
                declare contract Renderer {
                  fn render(gfx: Gfx) -> void;
                }
                """);
        Files.writeString(modBarrel, "pub contract Renderer;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                1);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name())));
        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_UNRESOLVED.name())));
        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name())));
        assertEquals(0, irBackend.getFunctions().size());
        assertTrue(irBackend.getReservedMetadata().hostMethodBindings().stream()
                .anyMatch(h -> h.ownerName().equals("LowGfx") && h.sourceMethodName().equals("clear")));
    }

    @Test
    void shouldResolveSdkAssetImportFromBootstrapStdlib() throws IOException {
        final var projectRoot = tempDir.resolve("project-bootstrap-sdk-asset");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("app");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                import { Assets } from @sdk:asset;
                declare contract Loader {
                  fn load(assets: Assets) -> void;
                }
                """);
        Files.writeString(modBarrel, "pub contract Loader;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                1);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name())));
        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_UNRESOLVED.name())));
        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name())));
        assertEquals(0, irBackend.getFunctions().size());
        assertTrue(irBackend.getReservedMetadata().hostMethodBindings().stream()
                .anyMatch(h -> h.ownerName().equals("LowAssets")
                        && h.sourceMethodName().equals("load")
                        && h.abiModule().equals("asset")
                        && java.util.Objects.equals(h.assetLoweringParam(), 0)));
    }

    @Test
    void shouldLowerSdkGfxServiceFacadeCallsWithoutHostCallableAmbiguity() throws IOException {
        final var projectRoot = tempDir.resolve("project-bootstrap-sdk-gfx-facade-call");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("app");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                import { Gfx } from @sdk:gfx;

                [Frame]
                fn frame() -> void
                {
                    Gfx.clear_565(6577);
                }
                """);
        Files.writeString(modBarrel, "pub fn frame() -> void;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                1);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name())));
        assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function -> "frame".equals(function.callableName())));
    }

    @Test
    void shouldLowerSdkComposerEmitSpriteFacadeUsingCanonicalComposerContract() throws IOException {
        final var projectRoot = tempDir.resolve("project-bootstrap-sdk-composer-emit-sprite");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("app");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                import { Composer } from @sdk:composer;

                fn render() -> int
                {
                    return Composer.emit_sprite(7, 3, 12, 18, 0, 2, false, true, 1);
                }

                [Frame]
                fn frame() -> void
                {
                    render();
                    return;
                }
                """);
        Files.writeString(modBarrel, """
                pub fn render() -> int;
                pub fn frame() -> void;
                """);

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                1);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name())));
        assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function -> "frame".equals(function.callableName())));
        assertTrue(irBackend.getReservedMetadata().hostMethodBindings().stream()
                .anyMatch(h -> h.ownerName().equals("LowComposer")
                        && h.sourceMethodName().equals("emit_sprite")
                        && h.abiModule().equals("composer")
                        && h.abiMethod().equals("emit_sprite")));
    }

    @Test
    void shouldLowerSdkComposerSceneBindingFacadesUsingCanonicalComposerContract() throws IOException {
        final var projectRoot = tempDir.resolve("project-bootstrap-sdk-composer-scene-binding");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("app");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                import { Composer } from @sdk:composer;

                fn bind_scene_now() -> int
                {
                    return Composer.bind_scene(4);
                }

                fn unbind_scene_now() -> int
                {
                    return Composer.unbind_scene();
                }

                [Frame]
                fn frame() -> void
                {
                    bind_scene_now();
                    unbind_scene_now();
                    return;
                }
                """);
        Files.writeString(modBarrel, """
                pub fn bind_scene_now() -> int;
                pub fn unbind_scene_now() -> int;
                pub fn frame() -> void;
                """);

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                1);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertFalse(diagnostics.hasErrors(), diagnostics.stream()
                .map(d -> d.getCode() + ":" + d.getMessage())
                .toList()
                .toString());
        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name())));
        assertTrue(irBackend.getReservedMetadata().hostMethodBindings().stream()
                .anyMatch(h -> h.ownerName().equals("LowComposer")
                        && h.sourceMethodName().equals("bind_scene")
                        && h.abiModule().equals("composer")
                        && h.abiMethod().equals("bind_scene")));
        assertTrue(irBackend.getReservedMetadata().hostMethodBindings().stream()
                .anyMatch(h -> h.ownerName().equals("LowComposer")
                        && h.sourceMethodName().equals("unbind_scene")
                        && h.abiModule().equals("composer")
                        && h.abiMethod().equals("unbind_scene")));
    }

    @Test
    void shouldRejectLegacyGfxSetSpriteAfterComposerMigration() throws IOException {
        final var projectRoot = tempDir.resolve("project-bootstrap-sdk-gfx-set-sprite-removed");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("app");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                import { Gfx } from @sdk:gfx;

                fn render() -> int
                {
                    return Gfx.set_sprite(2, 0, 12, 18, 7, 3, true, false, true, 1);
                }

                [Frame]
                fn frame() -> void
                {
                    render();
                    return;
                }
                """);
        Files.writeString(modBarrel, """
                pub fn render() -> int;
                pub fn frame() -> void;
                """);

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                1);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name())
                        || d.getCode().equals(PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name())));
        assertFalse(irBackend.getReservedMetadata().hostMethodBindings().stream()
                .anyMatch(h -> h.ownerName().equals("LowGfx") && h.sourceMethodName().equals("set_sprite")));
    }

    @Test
    void shouldReportInconsistentBarrelInsideSdkBootstrapFixture() throws IOException {
        final var projectRoot = tempDir.resolve("project-stdlib-barrel-inconsistent");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("app");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                import { Gfx } from @sdk:gfx;
                fn run() -> int { return 1; }
                """);
        Files.writeString(modBarrel, "pub fn run() -> int;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var inconsistentGfxModule = new StdlibModuleSource(
                "sdk",
                ReadOnlyList.wrap(List.of("gfx")),
                ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile(
                        "main.pbs",
                        """
                        declare host Gfx {
                          [Host(module = "gfx", name = "draw_pixel", version = 1)]
                          [Capability(name = "gfx")]
                          fn draw_pixel(x: int, y: int) -> void;
                        }
                        """))),
                "pub host Missing;");
        final var frontendService = new PBSFrontendPhaseService(
                resolverForMajor(1, inconsistentGfxModule),
                new InterfaceModuleLoader(2_500_000));
        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                1);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = frontendService.compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name())));
        assertTrue(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name())));
        assertEquals(0, irBackend.getFunctions().size());
    }

    @Test
    void shouldEmitStaticSemanticsErrorForInvalidStdlibBuiltinLayoutShape() throws IOException {
        final var projectRoot = tempDir.resolve("project-stdlib-invalid-metadata");
        final var sourceRoot = projectRoot.resolve("src");
        final var modulePath = sourceRoot.resolve("app");
        Files.createDirectories(modulePath);

        final var sourceFile = modulePath.resolve("source.pbs");
        final var modBarrel = modulePath.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                import { Color } from @core:color;
                fn run() -> int { return 1; }
                """);
        Files.writeString(modBarrel, "pub fn run() -> int;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var invalidColorModule = new StdlibModuleSource(
                "core",
                ReadOnlyList.wrap(List.of("color")),
                ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile(
                        "main.pbs",
                        """
                        declare struct RGBA(raw: int);
                        [BuiltinType(name = "Color", version = 1)]
                        declare builtin type Color(
                          pub raw: RGBA
                        ) {
                        }
                        """))),
                "pub struct Color;");
        final var frontendService = new PBSFrontendPhaseService(
                resolverForMajor(1, invalidColorModule),
                new InterfaceModuleLoader(2_700_000));
        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                1);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = frontendService.compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        final var semanticsDiagnostic = diagnostics.stream()
                .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_BUILTIN_LAYOUT_FIELD_TYPE_NOT_ADMISSIBLE.name()))
                .findFirst()
                .orElseThrow();
        assertEquals(p.studio.compiler.source.diagnostics.DiagnosticPhase.STATIC_SEMANTICS, semanticsDiagnostic.getPhase());
        assertEquals(semanticsDiagnostic.getCode(), semanticsDiagnostic.getTemplateId());
        assertEquals(0, irBackend.getFunctions().size());
    }

    @Test
    void shouldBlockDependentModulesTransitivelyWhenUpstreamModuleFailsLinking() throws IOException {
        final var projectRoot = tempDir.resolve("project-fail-fast-linking");
        final var sourceRoot = projectRoot.resolve("src");
        final var moduleAPath = sourceRoot.resolve("a");
        final var moduleBPath = sourceRoot.resolve("b");
        final var moduleCPath = sourceRoot.resolve("c");
        final var moduleDPath = sourceRoot.resolve("d");
        Files.createDirectories(moduleAPath);
        Files.createDirectories(moduleBPath);
        Files.createDirectories(moduleCPath);
        Files.createDirectories(moduleDPath);

        final var aSource = moduleAPath.resolve("source.pbs");
        final var aBarrel = moduleAPath.resolve("mod.barrel");
        final var aInvalidBarrel = moduleAPath.resolve("extra.barrel");
        Files.writeString(aSource, "fn a_entry() -> int { return 1; }");
        Files.writeString(aBarrel, "pub fn a_entry() -> int;");
        Files.writeString(aInvalidBarrel, "pub fn a_entry() -> int;");

        final var bSource = moduleBPath.resolve("source.pbs");
        final var bBarrel = moduleBPath.resolve("mod.barrel");
        Files.writeString(bSource, """
                import { a_entry } from @app:a;
                fn b_entry() -> int { return 2; }
                """);
        Files.writeString(bBarrel, "pub fn b_entry() -> int;");

        final var cSource = moduleCPath.resolve("source.pbs");
        final var cBarrel = moduleCPath.resolve("mod.barrel");
        Files.writeString(cSource, """
                import { b_entry } from @app:b;
                fn c_entry() -> int { return 3; }
                """);
        Files.writeString(cBarrel, "pub fn c_entry() -> int;");

        final var dSource = moduleDPath.resolve("source.pbs");
        final var dBarrel = moduleDPath.resolve("mod.barrel");
        Files.writeString(dSource, "fn d_entry() -> int { return 4; }");
        Files.writeString(dBarrel, "pub fn d_entry() -> int;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, aSource, fileTable);
        registerFile(projectId, projectRoot, aBarrel, fileTable);
        registerFile(projectId, projectRoot, aInvalidBarrel, fileTable);
        registerFile(projectId, projectRoot, bSource, fileTable);
        registerFile(projectId, projectRoot, bBarrel, fileTable);
        registerFile(projectId, projectRoot, cSource, fileTable);
        registerFile(projectId, projectRoot, cBarrel, fileTable);
        registerFile(projectId, projectRoot, dSource, fileTable);
        registerFile(projectId, projectRoot, dBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsLinkErrors.E_LINK_INVALID_BARREL_FILENAME.name())));
        assertEquals(1, irBackend.getFunctions().size());
        assertEquals("d_entry", irBackend.getFunctions().getFirst().name());
    }

    @Test
    void shouldBlockDependentModulesWhenUpstreamModuleFailsHostAdmission() throws IOException {
        final var projectRoot = tempDir.resolve("project-fail-fast-host-admission");
        final var sourceRoot = projectRoot.resolve("src");
        final var dependentModulePath = sourceRoot.resolve("dependent");
        final var independentModulePath = sourceRoot.resolve("independent");
        Files.createDirectories(dependentModulePath);
        Files.createDirectories(independentModulePath);

        final var dependentSource = dependentModulePath.resolve("source.pbs");
        final var dependentBarrel = dependentModulePath.resolve("mod.barrel");
        Files.writeString(dependentSource, """
                import { Gfx } from @sdk:gfx;
                fn dependent() -> int { return 2; }
                """);
        Files.writeString(dependentBarrel, "pub fn dependent() -> int;");

        final var independentSource = independentModulePath.resolve("source.pbs");
        final var independentBarrel = independentModulePath.resolve("mod.barrel");
        Files.writeString(independentSource, "fn independent() -> int { return 3; }");
        Files.writeString(independentBarrel, "pub fn independent() -> int;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, dependentSource, fileTable);
        registerFile(projectId, projectRoot, dependentBarrel, fileTable);
        registerFile(projectId, projectRoot, independentSource, fileTable);
        registerFile(projectId, projectRoot, independentBarrel, fileTable);

        final var badSdkModule = new StdlibModuleSource(
                "sdk",
                ReadOnlyList.wrap(List.of("gfx")),
                ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile(
                        "main.pbs",
                        """
                        declare host Gfx {
                          [Host(module = "gfx", name = "draw_pixel", version = 1)]
                          [Capability(name = "unknown_capability")]
                          fn draw_pixel(x: int, y: int) -> void;
                        }
                        """))),
                "pub host Gfx;");
        final var frontendService = new PBSFrontendPhaseService(
                resolverForMajor(9, badSdkModule),
                new InterfaceModuleLoader(3_000_000));

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                9);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = frontendService.compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsHostAdmissionErrors.E_HOST_UNKNOWN_CAPABILITY.name())));
        assertEquals(1, irBackend.getFunctions().size());
        assertEquals("independent", irBackend.getFunctions().getFirst().name());
    }

    @Test
    void shouldResolveImportedBuiltinTypeChainsInExpressionFlowSemantics() throws IOException {
        final var projectRoot = tempDir.resolve("project-builtin-chain");
        final var sourceRoot = projectRoot.resolve("src");
        Files.createDirectories(sourceRoot);

        final var sourceFile = sourceRoot.resolve("main.pbs");
        final var modBarrel = sourceRoot.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                import { Log } from @sdk:log;
                import { Input } from @sdk:input;

                [Frame]
                fn frame() -> void
                {
                    if (Input.pad().a().pressed())
                    {
                        Log.debug("Hello World!");
                    }
                }
                """);
        Files.writeString(modBarrel, "pub fn frame() -> void;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("main")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                1);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name())));
        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_APPLY_NON_CALLABLE_TARGET.name())));
        final var frameExecutableOptional = irBackend.getExecutableFunctions().stream()
                .filter(function -> "frame".equals(function.callableName()))
                .findFirst();
        final var executableNames = irBackend.getExecutableFunctions().stream()
                .map(function -> function.callableName() + "@"
                        + (function.moduleId().isNone() ? "-" : function.moduleId().getIndex()))
                .toList();
        assertTrue(
                frameExecutableOptional.isPresent(),
                "diagnostics="
                        + diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList()
                        + " executableNames="
                        + executableNames);
        final var frameExecutable = frameExecutableOptional.orElseThrow();
        final var callableCalls = frameExecutable.instructions().stream()
                .filter(instruction ->
                        instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_FUNC)
                .map(instruction -> instruction.calleeCallableName())
                .toList();
        final var intrinsicCalls = frameExecutable.instructions().stream()
                .filter(instruction ->
                        instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC)
                .toList();
        final var inputPadExecutable = irBackend.getExecutableFunctions().stream()
                .filter(function -> "Input.pad".equals(function.callableName()))
                .findFirst()
                .orElseThrow();
        final var inputPadIntrinsics = inputPadExecutable.instructions().stream()
                .filter(instruction ->
                        instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC
                                && instruction.intrinsicCall() != null)
                .map(instruction -> instruction.intrinsicCall().canonicalName())
                .toList();
        assertTrue(callableCalls.contains("Input.pad"));
        assertTrue(intrinsicCalls.stream().anyMatch(instruction -> Integer.valueOf(1).equals(instruction.expectedArgSlots())));
        assertTrue(inputPadIntrinsics.contains("input.pad"));
        assertTrue(intrinsicCalls.stream().anyMatch(instruction ->
                instruction.intrinsicCall() != null && "input.pad.a".equals(instruction.intrinsicCall().canonicalName())));
        assertTrue(intrinsicCalls.stream().anyMatch(instruction ->
                instruction.intrinsicCall() != null && "input.button.pressed".equals(instruction.intrinsicCall().canonicalName())));
        assertTrue(
                irBackend.getExecutableFunctions().stream().anyMatch(function -> "frame".equals(function.callableName())),
                "diagnostics="
                        + diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList()
                        + " executableNames="
                        + executableNames);
    }

    @Test
    void shouldResolveIntrinsicChainsWithMethodNameCollisionsByOwnerIdentity() throws IOException {
        final var projectRoot = tempDir.resolve("project-intrinsic-owner-collision");
        final var sourceRoot = projectRoot.resolve("src");
        Files.createDirectories(sourceRoot);

        final var sourceFile = sourceRoot.resolve("main.pbs");
        final var modBarrel = sourceRoot.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                import { Input } from @sdk:input;

                [Frame]
                fn frame() -> void
                {
                    Input.pad().x().pressed();
                    Input.touch().x();
                }
                """);
        Files.writeString(modBarrel, "pub fn frame() -> void;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("main")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                1);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name())),
                diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
        final var frameExecutable = irBackend.getExecutableFunctions().stream()
                .filter(function -> "frame".equals(function.callableName()))
                .findFirst()
                .orElseThrow();
        final var callableCalls = frameExecutable.instructions().stream()
                .filter(instruction ->
                        instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_FUNC)
                .map(instruction -> instruction.calleeCallableName())
                .toList();
        final var intrinsicCalls = frameExecutable.instructions().stream()
                .filter(instruction ->
                        instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC
                                && instruction.intrinsicCall() != null)
                .map(instruction -> instruction.intrinsicCall().canonicalName())
                .toList();
        final var inputPadExecutable = irBackend.getExecutableFunctions().stream()
                .filter(function -> "Input.pad".equals(function.callableName()))
                .findFirst()
                .orElseThrow();
        final var inputTouchExecutable = irBackend.getExecutableFunctions().stream()
                .filter(function -> "Input.touch".equals(function.callableName()))
                .findFirst()
                .orElseThrow();
        final var inputPadIntrinsics = inputPadExecutable.instructions().stream()
                .filter(instruction ->
                        instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC
                                && instruction.intrinsicCall() != null)
                .map(instruction -> instruction.intrinsicCall().canonicalName())
                .toList();
        final var inputTouchIntrinsics = inputTouchExecutable.instructions().stream()
                .filter(instruction ->
                        instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC
                                && instruction.intrinsicCall() != null)
                .map(instruction -> instruction.intrinsicCall().canonicalName())
                .toList();
        assertTrue(callableCalls.contains("Input.pad"));
        assertTrue(callableCalls.contains("Input.touch"));
        assertTrue(inputPadIntrinsics.contains("input.pad"));
        assertTrue(intrinsicCalls.contains("input.pad.x"));
        assertTrue(intrinsicCalls.contains("input.button.pressed"));
        assertTrue(inputTouchIntrinsics.contains("input.touch"));
        assertTrue(intrinsicCalls.contains("input.touch.x"));
    }

    @Test
    void shouldResolveIntrinsicCallsOnBuiltinValuesStoredInLocals() throws IOException {
        final var projectRoot = tempDir.resolve("project-intrinsic-local-builtin-receiver");
        final var sourceRoot = projectRoot.resolve("src");
        Files.createDirectories(sourceRoot);

        final var sourceFile = sourceRoot.resolve("main.pbs");
        final var modBarrel = sourceRoot.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                import { Input } from @sdk:input;

                [Frame]
                fn frame() -> void
                {
                    let touch = Input.touch();
                    touch.x();
                    touch.y();
                }
                """);
        Files.writeString(modBarrel, "pub fn frame() -> void;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("main")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                1);
        final var diagnostics = DiagnosticSink.empty();

        final var irBackend = new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(diagnostics.stream().noneMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name())),
                diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
        final var frameExecutable = irBackend.getExecutableFunctions().stream()
                .filter(function -> "frame".equals(function.callableName()))
                .findFirst()
                .orElseThrow();
        final var callableCalls = frameExecutable.instructions().stream()
                .filter(instruction ->
                        instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_FUNC)
                .map(instruction -> instruction.calleeCallableName())
                .toList();
        final var intrinsicCalls = frameExecutable.instructions().stream()
                .filter(instruction ->
                        instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC
                                && instruction.intrinsicCall() != null)
                .map(instruction -> instruction.intrinsicCall().canonicalName())
                .toList();
        final var inputTouchExecutable = irBackend.getExecutableFunctions().stream()
                .filter(function -> "Input.touch".equals(function.callableName()))
                .findFirst()
                .orElseThrow();
        final var inputTouchIntrinsics = inputTouchExecutable.instructions().stream()
                .filter(instruction ->
                        instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC
                                && instruction.intrinsicCall() != null)
                .map(instruction -> instruction.intrinsicCall().canonicalName())
                .toList();
        assertTrue(callableCalls.contains("Input.touch"));
        assertTrue(inputTouchIntrinsics.contains("input.touch"));
        assertTrue(intrinsicCalls.contains("input.touch.x"));
        assertTrue(intrinsicCalls.contains("input.touch.y"));
    }

    @Test
    void shouldReportInterModuleGlobalCycleThroughImportedAlias() throws IOException {
        final var projectRoot = tempDir.resolve("project-global-cycle-alias");
        final var sourceRoot = projectRoot.resolve("src");
        final var moduleAPath = sourceRoot.resolve("a");
        final var moduleBPath = sourceRoot.resolve("b");
        Files.createDirectories(moduleAPath);
        Files.createDirectories(moduleBPath);

        final var sourceA = moduleAPath.resolve("source.pbs");
        final var barrelA = moduleAPath.resolve("mod.barrel");
        Files.writeString(sourceA, """
                import { B as ImportedB } from @app:b;

                declare global A: int = ImportedB + 1;
                """);
        Files.writeString(barrelA, "pub global A;");

        final var sourceB = moduleBPath.resolve("source.pbs");
        final var barrelB = moduleBPath.resolve("mod.barrel");
        Files.writeString(sourceB, """
                import { A } from @app:a;

                declare global B: int = A + 1;
                """);
        Files.writeString(barrelB, "pub global B;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceA, fileTable);
        registerFile(projectId, projectRoot, barrelA, fileTable);
        registerFile(projectId, projectRoot, sourceB, fileTable);
        registerFile(projectId, projectRoot, barrelB, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        final var cycleCount = diagnostics.stream()
                .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_GLOBAL_CYCLIC_DEPENDENCY.name()))
                .count();
        assertEquals(2, cycleCount, diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
    }

    @Test
    void shouldRejectDuplicateFrameMarkersAcrossExecutableProject() throws IOException {
        final var projectRoot = tempDir.resolve("project-duplicate-frame-markers");
        final var sourceRoot = projectRoot.resolve("src");
        final var moduleAPath = sourceRoot.resolve("a");
        final var moduleBPath = sourceRoot.resolve("b");
        Files.createDirectories(moduleAPath);
        Files.createDirectories(moduleBPath);

        final var sourceA = moduleAPath.resolve("source.pbs");
        final var barrelA = moduleAPath.resolve("mod.barrel");
        Files.writeString(sourceA, """
                [Frame]
                fn frame_a() -> void { return; }
                """);
        Files.writeString(barrelA, "pub fn frame_a() -> void;");

        final var sourceB = moduleBPath.resolve("source.pbs");
        final var barrelB = moduleBPath.resolve("mod.barrel");
        Files.writeString(sourceB, """
                [Frame]
                fn frame_b() -> void { return; }
                """);
        Files.writeString(barrelB, "pub fn frame_b() -> void;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, sourceA, fileTable);
        registerFile(projectId, projectRoot, barrelA, fileTable);
        registerFile(projectId, projectRoot, sourceB, fileTable);
        registerFile(projectId, projectRoot, barrelB, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        new PBSFrontendPhaseService().compile(
                ctx,
                diagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        final var duplicateFrameCount = diagnostics.stream()
                .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_DUPLICATE_PROJECT_FRAME.name()))
                .count();
        assertEquals(1, duplicateFrameCount, diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
    }

    @Test
    void shouldRejectHostCallsInInitWithoutInitAllowedAndAcceptWhenPresent() throws IOException {
        final var projectRoot = tempDir.resolve("project-init-host-calls");
        final var sourceRoot = projectRoot.resolve("src");
        Files.createDirectories(sourceRoot);

        final var deniedSource = sourceRoot.resolve("denied.pbs");
        final var deniedBarrel = sourceRoot.resolve("mod.barrel");
        Files.writeString(deniedSource, """
                import { Gfx } from @sdk:gfx;

                [Init]
                fn boot() -> void {
                  Gfx.clear();
                }

                [Frame]
                fn frame() -> void { return; }
                """);
        Files.writeString(deniedBarrel, """
                pub fn boot() -> void;
                pub fn frame() -> void;
                """);

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        registerFile(projectId, projectRoot, deniedSource, fileTable);
        registerFile(projectId, projectRoot, deniedBarrel, fileTable);

        final var sdkModuleDenied = new StdlibModuleSource(
                "sdk",
                ReadOnlyList.wrap(List.of("gfx")),
                ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile(
                        "gfx.pbs",
                        """
                        declare host Gfx {
                          [Host(module = "gfx", name = "clear", version = 1)]
                          fn clear() -> void;
                        }
                        """))),
                "pub host Gfx;");
        final var deniedFrontendService = new PBSFrontendPhaseService(
                resolverForMajor(11, sdkModuleDenied),
                new InterfaceModuleLoader(3_200_000));

        final var deniedCtx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
                11);
        final var deniedDiagnostics = DiagnosticSink.empty();

        deniedFrontendService.compile(
                deniedCtx,
                deniedDiagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(deniedDiagnostics.stream().anyMatch(d ->
                        d.getCode().equals(PbsSemanticsErrors.E_SEM_INIT_HOST_CALL_NOT_ALLOWED.name())),
                deniedDiagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());

        final var allowedProjectRoot = tempDir.resolve("project-init-host-calls-allowed");
        final var allowedSourceRoot = allowedProjectRoot.resolve("src");
        Files.createDirectories(allowedSourceRoot);
        final var allowedSource = allowedSourceRoot.resolve("main.pbs");
        final var allowedBarrel = allowedSourceRoot.resolve("mod.barrel");
        Files.writeString(allowedSource, """
                import { Gfx } from @sdk:gfx;

                [Init]
                fn boot() -> void {
                  Gfx.clear();
                }

                [Frame]
                fn frame() -> void { return; }
                """);
        Files.writeString(allowedBarrel, """
                pub fn boot() -> void;
                pub fn frame() -> void;
                """);

        final var allowedProjectTable = new ProjectTable();
        final var allowedFileTable = new FileTable(1);
        final var allowedProjectId = allowedProjectTable.register(ProjectDescriptor.builder()
                .rootPath(allowedProjectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(allowedSourceRoot)))
                .build());
        registerFile(allowedProjectId, allowedProjectRoot, allowedSource, allowedFileTable);
        registerFile(allowedProjectId, allowedProjectRoot, allowedBarrel, allowedFileTable);

        final var sdkModuleAllowed = new StdlibModuleSource(
                "sdk",
                ReadOnlyList.wrap(List.of("gfx")),
                ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile(
                        "gfx.pbs",
                        """
                        declare host Gfx {
                          [Host(module = "gfx", name = "clear", version = 1)]
                          [InitAllowed]
                          fn clear() -> void;
                        }
                        """))),
                "pub host Gfx;");
        final var allowedFrontendService = new PBSFrontendPhaseService(
                resolverForMajor(12, sdkModuleAllowed),
                new InterfaceModuleLoader(3_300_000));

        final var allowedCtx = new FrontendPhaseContext(
                allowedProjectTable,
                allowedFileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(allowedProjectId))),
                12);
        final var allowedDiagnostics = DiagnosticSink.empty();

        allowedFrontendService.compile(
                allowedCtx,
                allowedDiagnostics,
                LogAggregator.empty(),
                BuildingIssueSink.empty());

        assertTrue(allowedDiagnostics.stream().noneMatch(d ->
                        d.getCode().equals(PbsSemanticsErrors.E_SEM_INIT_HOST_CALL_NOT_ALLOWED.name())),
                allowedDiagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
    }

    @Test
    void shouldProduceInlineHintsForEligibleInferredLetBindings() throws IOException {
        final var projectRoot = tempDir.resolve("project-inline-hints");
        final var sourceRoot = projectRoot.resolve("src");
        Files.createDirectories(sourceRoot);

        final var sourceFile = sourceRoot.resolve("main.pbs");
        final var modBarrel = sourceRoot.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                declare struct Player(score: int);
                declare error Failure {
                  Boom;
                }

                fn fetch_pair() -> result<Failure> (left: int, right: int) {
                  return ok((left: 1, right: 2));
                }

                fn main() -> void {
                  let scalar = 1;
                  let player = new Player(1);
                  let maybe = some(1);
                  let pair = (left: 1, right: 2);
                  let outcome = fetch_pair();
                  return;
                }
                """);
        Files.writeString(modBarrel, "pub fn main() -> void;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        final var sourceFileId = registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        final var inlineHintsByFile = PBSFrontendPhaseService.inlineHintsByFile(
                ctx,
                diagnostics,
                BuildingIssueSink.empty());
        final var sourceHints = inlineHintsByFile.get(sourceFileId);
        assertNotNull(sourceHints, () -> "diagnostics="
                + diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList()
                + ", keys=" + inlineHintsByFile.keySet());
        final var labels = sourceHints.asList().stream()
                .map(PbsInlineHintSurface::label)
                .toList();

        assertEquals(List.of(
                        "int",
                        "Player",
                        "optional int",
                        "(left: int, right: int)",
                        "result<Failure> (left: int, right: int)"),
                labels);
        assertEquals(0, diagnostics.errorCount(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
    }

    @Test
    void shouldSuppressInlineHintsForExplicitlyTypedLetBindings() throws IOException {
        final var projectRoot = tempDir.resolve("project-inline-hints-explicit");
        final var sourceRoot = projectRoot.resolve("src");
        Files.createDirectories(sourceRoot);

        final var sourceFile = sourceRoot.resolve("main.pbs");
        final var modBarrel = sourceRoot.resolve("mod.barrel");
        Files.writeString(sourceFile, """
                fn main() -> void {
                  let inferred = 1;
                  let explicit: int = 2;
                  return;
                }
                """);
        Files.writeString(modBarrel, "pub fn main() -> void;");

        final var projectTable = new ProjectTable();
        final var fileTable = new FileTable(1);
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .rootPath(projectRoot)
                .name("app")
                .version("1.0.0")
                .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
                .build());

        final var sourceFileId = registerFile(projectId, projectRoot, sourceFile, fileTable);
        registerFile(projectId, projectRoot, modBarrel, fileTable);

        final var ctx = new FrontendPhaseContext(
                projectTable,
                fileTable,
                new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
        final var diagnostics = DiagnosticSink.empty();

        final var inlineHints = PBSFrontendPhaseService.inlineHintsByFile(
                ctx,
                diagnostics,
                BuildingIssueSink.empty()).get(sourceFileId);

        assertEquals(1, inlineHints.size());
        assertEquals("int", inlineHints.getFirst().label());
        assertEquals("type", inlineHints.getFirst().category());
    }

    private FileId registerFile(
            final ProjectId projectId,
            final Path projectRoot,
            final Path file,
            final FileTable fileTable) throws IOException {
        final BasicFileAttributes attributes = Files.readAttributes(file, BasicFileAttributes.class);
        return fileTable.register(new SourceHandle(
                projectId,
                projectRoot.relativize(file),
                file,
                attributes.size(),
                attributes.lastModifiedTime().toMillis(),
                SourceProviderFactory.filesystem()));
    }

    private StdlibEnvironmentResolver resolverForMajor(
            final int acceptedStdlib,
            final StdlibModuleSource... modules) {
        final var byModuleKey = new HashMap<String, StdlibModuleSource>();
        for (final var module : modules) {
            byModuleKey.put(moduleKey(module.project(), module.pathSegments()), module);
        }
        return stdlib -> {
            if (stdlib != acceptedStdlib) {
                return (project, pathSegments) -> Optional.empty();
            }
            return new StdlibEnvironment() {
                @Override
                public Optional<StdlibModuleSource> resolveModule(
                        final String project,
                        final ReadOnlyList<String> pathSegments) {
                    return Optional.ofNullable(byModuleKey.get(moduleKey(project, pathSegments)));
                }
            };
        };
    }

    private String moduleKey(
            final String project,
            final ReadOnlyList<String> pathSegments) {
        return project + ":" + String.join("/", pathSegments.asList());
    }

}
