package p.studio.compiler.workspaces.phases;

import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.DependencyContext;
import p.studio.compiler.models.ProjectDescriptor;
import p.studio.compiler.models.ProjectInfo;
import p.studio.compiler.models.ProjectInfoId;
import p.studio.compiler.source.identifiers.ProjectId;
import p.studio.compiler.workspaces.DependencyPhase;
import p.studio.utilities.structures.ReadOnlyList;

import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

public final class WireProjectsPhase implements DependencyPhase {
    @Override
    public BuildingIssueSink run(final DependencyContext ctx) {
        // to start all over again, we will re-populate the project nodes and dependenciesByProjectId based on the project infos
        ctx.rootProjectId = null;
        ctx.projectIds.clear();
        ctx.projectTable.clear();
        ctx.dependenciesByProject.clear();

        final BuildingIssueSink issues = BuildingIssueSink.empty();

        for (final var projectInfo : ctx.projectInfoTable.values()) {
            final var projectDescriptor = buildProjectDescriptor(ctx, projectInfo, issues);
            final var projectId = ctx.projectTable.register(projectDescriptor);
            ctx.projectIds.add(projectId);
        }

        final var rootProjectId = ctx.projectTable.optionalId(ctx.mainProjectRootPathCanon);
        if (rootProjectId.isEmpty()) {
            return issues.report(builder -> builder
                    .message("[DEPS]: rootProjectId project dir " + ctx.mainProjectRootPathCanon + " was not discovered/materialized"));
        }


        // since it is not ordered, we have to iterate it over again, but now associating dependencies with projectIds
        for (int i = 0; i < ctx.projectInfoTable.size(); i++) {
            final var projectInfo = ctx.projectInfoTable.get(new ProjectInfoId(i));
            final var dependencies = new ArrayList<ProjectId>();
            for (final var dependency : projectInfo.manifest.dependencies()) {
                final var dependencyCanonPath = dependency.canonPath();
                final var projectId = ctx.projectTable.optionalId(dependencyCanonPath);
                if (projectId.isEmpty()) {
                    issues.report(builder -> builder
                            .error(true)
                            .message("[DEPS]: dependency not found: " + dependencyCanonPath));
                    continue;
                }
                dependencies.add(projectId.get());
            }
            ctx.dependenciesByProject.add(dependencies);
        }

        ctx.rootProjectId = rootProjectId.get();

        return issues;
    }

    private static ProjectDescriptor buildProjectDescriptor(
            final DependencyContext ctx,
            final ProjectInfo projectInfo,
            final BuildingIssueSink issues) {
        final BuildingIssueSink sourceRootIssues = BuildingIssueSink.empty();
        final List<Path> sourceRoots = new ArrayList<>();
        for (final var sourceRoot : ctx.frontendSpec.getSourceRoots()) {
            final var sourceRootPath = projectInfo.rootDirectory.resolve(sourceRoot);
            try {
                final var sourceRootPathCanon = sourceRootPath.toRealPath();
                sourceRoots.add(sourceRootPathCanon);
            } catch (IOException e) {
                sourceRootIssues.report(builder -> builder
                        .error(true)
                        .message("[DEPS]: source project canonPath does not exist: " + sourceRootPath + " (from " + projectInfo.rootDirectory + ")")
                        .exception(e));
            }
        }
        if (sourceRootIssues.size() == ctx.frontendSpec.getSourceRoots().size()) {
            // no source roots were found at all
            issues.merge(sourceRootIssues);
        }

        return ProjectDescriptor
                .builder()
                .rootPath(projectInfo.rootDirectory)
                .name(projectInfo.manifest.name())
                .version(projectInfo.manifest.version())
                .sourceRoots(ReadOnlyList.wrap(sourceRoots))
                .build();
    }
}
