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.*;

class PbsSemanticsControlFlowTest {

    @Test
    void shouldValidateIfSwitchAndProjectionRules() {
        final var source = """
                declare enum Mode(Idle, Run);

                declare struct State(v: int) {
                  fn value() -> int { return this.v; }
                }

                fn flow(m: Mode, s: State) -> int {
                  let a: int = if 1 { 1 } else { 2 };
                  let b: int = switch m { Mode.Idle: { 1 } };
                  let c: int = s.missing;
                  let d = s.value;
                  return a + b + c + d;
                }
                """;
        final var diagnostics = DiagnosticSink.empty();

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

        assertTrue(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_IF_NON_BOOL_CONDITION.name())));
        assertTrue(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_SWITCH_NON_EXHAUSTIVE.name())));
        assertTrue(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name())));
        assertTrue(diagnostics.stream().anyMatch(d ->
                d.getCode().equals(PbsSemanticsErrors.E_SEM_BARE_METHOD_EXTRACTION.name())));
    }

    @Test
    void shouldRejectDuplicateSwitchWildcardArms() {
        final var source = """
                fn flow(state: int) -> int {
                  let value: int = switch state {
                    default: { 1 },
                    default: { 2 },
                  };
                  return value;
                }
                """;
        final var diagnostics = DiagnosticSink.empty();

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

        final var duplicateWildcardCount = diagnostics.stream()
                .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_SWITCH_DUPLICATE_PATTERN.name()))
                .count();
        assertEquals(1, duplicateWildcardCount);
    }

    @Test
    void shouldRejectMixedSwitchWildcardSpellings() {
        final var source = """
                fn flow(state: int) -> int {
                  let value: int = switch state {
                    default: { 1 },
                    _: { 2 },
                  };
                  return value;
                }
                """;
        final var diagnostics = DiagnosticSink.empty();

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

        final var duplicateWildcardCount = diagnostics.stream()
                .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_SWITCH_DUPLICATE_PATTERN.name()))
                .count();
        assertEquals(1, duplicateWildcardCount);
    }

    @Test
    void shouldAllowThisInsideStructServiceAndCtorBodies() {
        final var source = """
                declare struct Box(v: int) {
                  fn read() -> int { return this.v; }
                  ctor make(seed: int) { this.v = seed; }
                }

                declare service Game {
                  fn ping() -> int { this; return 0; }
                }

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

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

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

    @Test
    void shouldRejectThisOutsideReceiverContexts() {
        final var source = """
                fn bad() -> int {
                  this;
                  return 0;
                }
                """;
        final var diagnostics = DiagnosticSink.empty();

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

        final var invalidThisCount = diagnostics.stream()
                .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_INVALID_THIS_CONTEXT.name()))
                .count();
        assertEquals(1, invalidThisCount);
    }
}
