package p.studio.compiler.workspaces.stages;

import lombok.extern.slf4j.Slf4j;
import p.studio.compiler.FrontendRegistryService;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.messages.FrontendPhaseContext;
import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.compiler.workspaces.AssetSurfaceContextLoader;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.workspaces.PipelineStage;
import p.studio.utilities.logs.LogAggregator;

@Slf4j
public class FrontendPhasePipelineStage implements PipelineStage {
    private final AssetSurfaceContextLoader assetSurfaceContextLoader = new AssetSurfaceContextLoader();

    @Override
    public BuildingIssueSink run(final BuilderPipelineContext ctx, final LogAggregator logs) {
        final var frontedSpec = ctx.resolvedWorkspace.frontendSpec();
        final var service = FrontendRegistryService.getFrontendPhaseService(frontedSpec.getLanguageId());
        if (service.isEmpty()) {
            return BuildingIssueSink.empty()
                    .report(builder -> builder
                            .error(true)
                            .message("[BUILD]: unable to find a service for frontend phase: " + frontedSpec.getLanguageId()));
        }
        final var projectTable = ctx.resolvedWorkspace.graph().projectTable();
        final var fileTable = ctx.fileTable;
        final var frontendPhaseContext = new FrontendPhaseContext(
                projectTable,
                fileTable,
                ctx.resolvedWorkspace.stack(),
                ctx.resolvedWorkspace.stdlib(),
                p.studio.compiler.messages.HostAdmissionContext.permissiveDefault(),
                assetSurfaceContextLoader.load(ctx.resolvedWorkspace.mainProject().getRootPath()));
        final var diagnostics = DiagnosticSink.empty();
        final var issues = BuildingIssueSink.empty();
        ctx.irBackend = service.get().compile(frontendPhaseContext, diagnostics, logs, issues);
        logs.using(log).debug("Frontend phase completed successfully!");
        adaptDiagnostics(diagnostics, issues);
        return issues;
    }

    private void adaptDiagnostics(
            final DiagnosticSink diagnostics,
            final BuildingIssueSink issues) {
        for (final var diagnostic : diagnostics) {
            final var span = diagnostic.getSpan();
            issues.report(builder -> builder
                    .error(diagnostic.getSeverity().isError())
                    .phase(diagnostic.getPhase().id())
                    .code(diagnostic.getCode())
                    .fileId(span.getFileId().isNone() ? null : span.getFileId().getId())
                    .start(safeToInt(span.getStart()))
                    .end(safeToInt(span.getEnd()))
                    .message("[BUILD] %s :: %s @ %s:[%d,%d)".formatted(
                            diagnostic.getCode(),
                            diagnostic.getMessage(),
                            span.getFileId(),
                            span.getStart(),
                            span.getEnd())));
        }
    }

    private int safeToInt(final long value) {
        return value > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) Math.max(0L, value);
    }
}
