package p.studio.compiler.pbs.semantics;

import org.junit.jupiter.api.Test;
import p.studio.compiler.pbs.PbsFrontendCompiler;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.identifiers.FileId;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

class PbsSemanticsConstTest {

    @Test
    void shouldResolveConstDependenciesIndependentOfSourceOrder() {
        final var source = """
                declare enum Mode(Idle, Run);
                declare const C: int = B + 1;
                declare const B: int = A + 1;
                declare const A: int = 1;
                declare const M: Mode = Mode.Run;

                fn use() -> int {
                  return C;
                }
                """;
        final var diagnostics = DiagnosticSink.empty();

        new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);

        assertFalse(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_CONST_UNRESOLVED_REFERENCE.name())));
        assertFalse(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_CONST_CYCLIC_DEPENDENCY.name())));
        assertFalse(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_CONST_NON_CONSTANT_INITIALIZER.name())));
    }

    @Test
    void shouldRejectNonConstantConstInitializers() {
        final var source = """
                fn inc(v: int) -> int { return v + 1; }
                declare struct Box(pub mut value: int);

                declare const A: int = inc(1);
                declare const B: int = new Box(1);
                declare const C: int = if true { 1 } else { 2 };
                declare const D: int = some(1);
                """;
        final var diagnostics = DiagnosticSink.empty();

        new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);

        final var nonConstCount = diagnostics.stream()
                .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_CONST_NON_CONSTANT_INITIALIZER.name()))
                .count();
        assertEquals(4, nonConstCount);
    }

    @Test
    void shouldRejectConstInitializerTypeMismatchAndUnresolvedReference() {
        final var source = """
                declare const A: int = "wrong";
                declare const B: int = Missing + 1;
                """;
        final var diagnostics = DiagnosticSink.empty();

        new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);

        final var mismatchCount = diagnostics.stream()
                .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_CONST_INITIALIZER_TYPE_MISMATCH.name()))
                .count();
        final var unresolvedCount = diagnostics.stream()
                .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_CONST_UNRESOLVED_REFERENCE.name()))
                .count();

        assertEquals(1, mismatchCount);
        assertEquals(1, unresolvedCount);
    }

    @Test
    void shouldRejectCyclicConstDependencies() {
        final var source = """
                declare const A: int = B + 1;
                declare const B: int = C + 1;
                declare const C: int = A + 1;
                """;
        final var diagnostics = DiagnosticSink.empty();

        new PbsFrontendCompiler().compileFile(new FileId(0), source, diagnostics);

        final var cycleCount = diagnostics.stream()
                .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_CONST_CYCLIC_DEPENDENCY.name()))
                .count();
        assertEquals(3, cycleCount);
    }
}
