package p.studio.compiler.pbs.semantics;

import p.studio.compiler.source.Span;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.diagnostics.RelatedSpan;
import p.studio.compiler.source.diagnostics.Severity;
import p.studio.compiler.source.identifiers.NameId;
import p.studio.compiler.source.tables.NameTable;

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

final class PbsNamespaceBinder {
    private final NameTable nameTable;
    private final DiagnosticSink diagnostics;

    private final Map<NameId, Span> typeNamespace = new HashMap<>();
    private final Map<NameId, Span> valueNamespace = new HashMap<>();
    private final Map<NameId, Span> hostOwnerNamespace = new HashMap<>();
    private final Map<CallableIdentity, Span> callableNamespace = new HashMap<>();
    private final Map<NameId, VisibleTopLevelSymbol> visibleTopLevelNamespace = new HashMap<>();

    PbsNamespaceBinder(final NameTable nameTable, final DiagnosticSink diagnostics) {
        this.nameTable = nameTable;
        this.diagnostics = diagnostics;
    }

    void registerType(final String name, final Span span, final String kind) {
        registerByNamespace(typeNamespace, name, span, "type namespace", kind);
    }

    void registerValue(final String name, final Span span, final String kind) {
        registerByNamespace(valueNamespace, name, span, "value namespace", kind);
    }

    void registerVisibleTopLevelSurface(final String name, final Span span, final String kind) {
        final var nameId = nameTable.register(name);
        final var first = visibleTopLevelNamespace.putIfAbsent(nameId, new VisibleTopLevelSymbol(kind, span));
        if (first == null) {
            return;
        }
        if ("function".equals(kind) && "function".equals(first.kind())) {
            return;
        }

        p.studio.compiler.source.diagnostics.Diagnostics.report(diagnostics,
                Severity.Error,
                PbsSemanticsErrors.E_SEM_DUPLICATE_DECLARATION.name(),
                "Visible top-level symbol '%s' collides between %s and %s".formatted(name, kind, first.kind()),
                span,
                List.of(new RelatedSpan("First visible top-level symbol is here", first.span())));
    }

    @SuppressWarnings("unused")
    void registerHostOwner(final String name, final Span span, final String kind) {
        registerByNamespace(hostOwnerNamespace, name, span, "host-owner namespace", kind);
    }

    void registerCallable(
            final PbsCallableScope scope,
            final String callableName,
            final String shape,
            final Span span) {
        final var callableNameId = nameTable.register(callableName);
        final var identity = new CallableIdentity(scope, callableNameId, shape);
        final var first = callableNamespace.putIfAbsent(identity, span);
        if (first == null) {
            return;
        }

        p.studio.compiler.source.diagnostics.Diagnostics.report(diagnostics, 
                Severity.Error,
                PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name(),
                "Duplicate callable declaration '%s' with shape %s in %s".formatted(callableName, shape, scope.label()),
                span,
                List.of(new RelatedSpan("First callable declaration is here", first)));
    }

    void registerCtor(
            final PbsCallableScope scope,
            final String ctorName,
            final String shape,
            final Span span) {
        final var callableNameId = nameTable.register(ctorName);
        final var identity = new CallableIdentity(scope, callableNameId, shape);
        final var first = callableNamespace.putIfAbsent(identity, span);
        if (first == null) {
            return;
        }

        p.studio.compiler.source.diagnostics.Diagnostics.report(diagnostics, 
                Severity.Error,
                PbsSemanticsErrors.E_SEM_DUPLICATE_CALLABLE_SHAPE.name(),
                "Duplicate constructor declaration '%s' with shape %s in %s".formatted(ctorName, shape, scope.label()),
                span,
                List.of(new RelatedSpan("First constructor declaration is here", first)));
    }

    private void registerByNamespace(
            final Map<NameId, Span> namespace,
            final String declarationName,
            final Span span,
            final String namespaceName,
            final String declarationKind) {
        final var nameId = nameTable.register(declarationName);
        final var first = namespace.putIfAbsent(nameId, span);
        if (first == null) {
            return;
        }

        p.studio.compiler.source.diagnostics.Diagnostics.report(diagnostics, 
                Severity.Error,
                PbsSemanticsErrors.E_SEM_DUPLICATE_DECLARATION.name(),
                "Duplicate %s declaration '%s' in %s".formatted(declarationKind, declarationName, namespaceName),
                span,
                List.of(new RelatedSpan("First declaration is here", first)));
    }

    private record CallableIdentity(
            PbsCallableScope scope,
            NameId callableNameId,
            String shape) {
    }

    private record VisibleTopLevelSymbol(
            String kind,
            Span span) {
    }
}
