package p.studio.compiler.workspaces;

import org.junit.jupiter.api.Test;
import p.studio.compiler.models.AnalysisSnapshot;
import p.studio.compiler.models.BuildResult;
import p.studio.compiler.models.CompileResult;

import java.lang.reflect.RecordComponent;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;

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

class BuilderPipelinePublicSurfaceTest {

    @Test
    void publicServiceSurfaceMustExposeExplicitEntrypointsAndNoPublicRun() {
        final var publicMethodNames = Arrays.stream(BuilderPipelineService.class.getDeclaredMethods())
                .filter(method -> java.lang.reflect.Modifier.isPublic(method.getModifiers()))
                .map(method -> method.getName())
                .collect(Collectors.toSet());

        assertTrue(publicMethodNames.contains("analyze"), "public surface must expose analyze");
        assertTrue(publicMethodNames.contains("compile"), "public surface must expose compile");
        assertTrue(publicMethodNames.contains("build"), "public surface must expose build");
        assertFalse(publicMethodNames.contains("run"), "public surface must not expose legacy run");
    }

    @Test
    void analysisSnapshotMustExposeMinimumSharedContract() {
        assertEquals(
                Set.of("frontendSpec", "diagnostics", "resolvedWorkspace", "fileTable", "irBackend"),
                recordComponentNames(AnalysisSnapshot.class),
                "analysis snapshot must expose the minimum shared contract");
    }

    @Test
    void compileResultMustExposeValidatedInMemoryArtifacts() {
        final var componentNames = recordComponentNames(CompileResult.class);

        assertTrue(componentNames.contains("analysisSnapshot"), "compile result must retain analysis snapshot");
        assertTrue(componentNames.contains("diagnostics"), "compile result must expose diagnostics");
        assertTrue(componentNames.contains("bytecodeModule"), "compile result must expose bytecode module");
        assertTrue(componentNames.contains("bytecodeBytes"), "compile result must expose serialized bytecode bytes");
    }

    @Test
    void buildResultMustExposeArtifactPathAndCompilePayload() {
        final var componentNames = recordComponentNames(BuildResult.class);

        assertTrue(componentNames.contains("compileResult"), "build result must retain compile payload");
        assertTrue(componentNames.contains("diagnostics"), "build result must expose diagnostics");
        assertTrue(componentNames.contains("bytecodeArtifactPath"), "build result must expose artifact path");
    }

    private Set<String> recordComponentNames(final Class<?> type) {
        assertTrue(type.isRecord(), () -> type.getSimpleName() + " must remain a record contract");
        return Arrays.stream(type.getRecordComponents())
                .map(RecordComponent::getName)
                .collect(Collectors.toSet());
    }
}
