package p.studio.compiler.pbs.stdlib;

import p.studio.compiler.models.SourceHandle;
import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.pbs.lexer.PbsLexer;
import p.studio.compiler.pbs.linking.PbsModuleVisibilityValidator;
import p.studio.compiler.pbs.parser.PbsBarrelParser;
import p.studio.compiler.pbs.parser.PbsParser;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.identifiers.ProjectId;
import p.studio.compiler.source.tables.FileTable;
import p.studio.utilities.structures.ReadOnlyList;

import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;

public final class InterfaceModuleLoader {
    public InterfaceModuleLoader() {
    }

    public InterfaceModuleLoader(final int ignoredSyntheticSeed) {
        this();
    }

    public PbsModuleVisibilityValidator.ModuleUnit load(
            final StdlibModuleSource moduleSource,
            final ProjectId syntheticOwnerProjectId,
            final FileTable fileTable,
            final DiagnosticSink diagnostics) {
        final var sources = new ArrayList<PbsModuleVisibilityValidator.SourceFile>(moduleSource.sourceFiles().size());
        for (int i = 0; i < moduleSource.sourceFiles().size(); i++) {
            final var sourceFile = moduleSource.sourceFiles().get(i);
            final var fileId = registerSyntheticFile(
                    syntheticOwnerProjectId,
                    moduleSource,
                    sourceFile.logicalPath(),
                    sourceFile.source(),
                    fileTable);
            final var sourceAst = parseSource(sourceFile.source(), fileId, diagnostics);
            sources.add(new PbsModuleVisibilityValidator.SourceFile(fileId, sourceAst));
        }

        final var barrels = new ArrayList<PbsModuleVisibilityValidator.BarrelFile>(1);
        final var barrelFileId = registerSyntheticFile(
                syntheticOwnerProjectId,
                moduleSource,
                "mod.barrel",
                moduleSource.barrelSource(),
                fileTable);
        final var barrelAst = parseBarrel(moduleSource.barrelSource(), barrelFileId, diagnostics);
        barrels.add(new PbsModuleVisibilityValidator.BarrelFile(barrelFileId, barrelAst));

        return new PbsModuleVisibilityValidator.ModuleUnit(
                new PbsModuleVisibilityValidator.ModuleCoordinates(moduleSource.project(), moduleSource.pathSegments()),
                ReadOnlyList.wrap(sources),
                ReadOnlyList.wrap(barrels));
    }

    private FileId registerSyntheticFile(
            final ProjectId syntheticOwnerProjectId,
            final StdlibModuleSource moduleSource,
            final String logicalPath,
            final String source,
            final FileTable fileTable) {
        final var normalizedLogicalPath = (logicalPath == null || logicalPath.isBlank())
                ? "source.pbs"
                : logicalPath;
        final var relativePath = Path.of("@stdlib")
                .resolve(moduleSource.project())
                .resolve(String.join("/", moduleSource.pathSegments().asList()))
                .resolve(normalizedLogicalPath);
        final var canonPath = Path.of("/virtual/stdlib")
                .resolve(moduleSource.project())
                .resolve(String.join("/", moduleSource.pathSegments().asList()))
                .resolve(normalizedLogicalPath);
        final var utf8 = source == null ? new byte[0] : source.getBytes(StandardCharsets.UTF_8);
        final var sourceHandle = new SourceHandle(
                syntheticOwnerProjectId,
                relativePath,
                canonPath,
                utf8.length,
                0,
                ignored -> () -> utf8);
        return fileTable.register(sourceHandle);
    }

    private PbsAst.File parseSource(
            final String source,
            final FileId fileId,
            final DiagnosticSink diagnostics) {
        final var tokens = PbsLexer.lex(source, fileId, diagnostics);
        return PbsParser.parse(tokens, fileId, diagnostics, PbsParser.ParseMode.INTERFACE_MODULE);
    }

    private PbsAst.BarrelFile parseBarrel(
            final String source,
            final FileId fileId,
            final DiagnosticSink diagnostics) {
        final var tokens = PbsLexer.lex(source, fileId, diagnostics);
        return PbsBarrelParser.parse(tokens, fileId, diagnostics);
    }
}
