package p.studio.compiler.source.diagnostics;

import org.junit.jupiter.api.Test;
import p.studio.compiler.source.Span;

import java.util.List;
import java.util.Map;

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

class DiagnosticSinkTest {

    @Test
    void shouldReportAndCountDiagnostics() {
        final var sink = DiagnosticSink.empty();

        sink.error("E001", "E001", Map.of(), "Unknown symbol", Span.none());
        sink.warning("W001", "W001", Map.of(), "Unused declaration", Span.none());

        assertEquals(2, sink.size());
        assertTrue(sink.hasErrors());
        assertTrue(sink.hasWarnings());
        assertEquals(1, sink.errorCount());
        assertEquals(1, sink.warningCount());
    }

    @Test
    void shouldMergeDiagnostics() {
        final var a = DiagnosticSink.empty()
                .warning("W010", "W010", Map.of(), "Unused import", Span.none());
        final var b = DiagnosticSink.empty()
                .error("E999", "E999", Map.of(), "Invalid type", Span.none());

        a.merge(b);

        assertEquals(2, a.size());
        assertTrue(a.hasErrors());
        assertTrue(a.hasWarnings());
        assertEquals(1, a.errorCount());
        assertEquals(1, a.warningCount());
        assertEquals(1, b.errorCount());
        assertEquals(0, b.warningCount());
    }

    @Test
    void shouldPopulateContractFieldsForLegacyEmission() {
        final var sink = DiagnosticSink.empty();

        sink.error("E_PARSE_EXPECTED_TOKEN", "E_PARSE_EXPECTED_TOKEN", Map.of(), "Expected token", Span.none());

        final var diagnostic = sink.stream().findFirst().orElseThrow();
        assertEquals("E_PARSE_EXPECTED_TOKEN", diagnostic.getCode());
        assertEquals(DiagnosticPhase.SYNTAX, diagnostic.getPhase());
        assertEquals("E_PARSE_EXPECTED_TOKEN", diagnostic.getTemplateId());
        assertTrue(diagnostic.getPlaceholders().isEmpty());
        assertEquals("Expected token", diagnostic.getMessage());
        assertTrue(diagnostic.getSpan().isNone());
    }

    @Test
    void shouldKeepExplicitPhaseTemplateAndPlaceholdersStable() {
        final var sink = DiagnosticSink.empty();

        sink.error(
                DiagnosticPhase.LINKING,
                "E_LINK_UNRESOLVED_BARREL_ENTRY",
                "pbs.link.unresolved-barrel-entry",
                Map.of("module", "@app:core", "symbol", "foo"),
                "Barrel entry 'foo' does not resolve",
                Span.none());

        final var diagnostic = sink.stream().findFirst().orElseThrow();
        assertEquals(DiagnosticPhase.LINKING, diagnostic.getPhase());
        assertEquals("pbs.link.unresolved-barrel-entry", diagnostic.getTemplateId());
        assertEquals(Map.of("module", "@app:core", "symbol", "foo"), diagnostic.getPlaceholders());
    }

    @Test
    void shouldReportRelatedSpansAndRenderPhaseInToString() {
        final var sink = DiagnosticSink.empty();
        final var related = new RelatedSpan("First declaration is here", Span.none());

        sink.error(
                "E_SEM_DUPLICATE_DECLARATION",
                "E_SEM_DUPLICATE_DECLARATION",
                Map.of(),
                "Duplicate declaration",
                Span.none(),
                List.of(related));

        final var diagnostic = sink.stream().findFirst().orElseThrow();
        assertEquals(1, diagnostic.getRelated().size());
        assertEquals("First declaration is here", diagnostic.getRelated().getFirst().getMessage());
        assertTrue(diagnostic.toString().contains("static-semantics"));
        assertTrue(diagnostic.toString().contains("E_SEM_DUPLICATE_DECLARATION"));
    }
}
