package p.studio.compiler.workspaces.phases;

import lombok.experimental.UtilityClass;
import org.apache.commons.collections4.CollectionUtils;
import p.studio.compiler.dtos.PrometeuManifestDTO;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.workspaces.DependencyReference;
import p.studio.registry.models.ProjectRef;
import p.studio.registry.models.Registry;
import p.studio.registry.utilities.RegistryStore;

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

@UtilityClass
public final class DependencyResolver {
    public static List<DependencyReference> resolveDependencies(
            final Path mainProjectRootPathCanon,
            final List<PrometeuManifestDTO.DependencyDeclaration> dependencies,
            final BuildingIssueSink issues) {
        if (CollectionUtils.isEmpty(dependencies)) {
            return List.of();
        }

        final var registryMaybe = RegistryStore.INSTANCE.load(mainProjectRootPathCanon);
        if (registryMaybe.isEmpty()) {
            issues.report(builder -> builder
                    .error(true)
                    .message("[DEPS]: failed to load registry from " + mainProjectRootPathCanon));
            return List.of();
        }
        final var registry = registryMaybe.get();

        final var deps = new ArrayList<DependencyReference>(dependencies.size());
        for (var dependency : dependencies) {

            // Resolves each dependency based on its declaration type
            switch (dependency) {
                case PrometeuManifestDTO.DependencyDeclaration.Local loc -> resolveLocal(loc, mainProjectRootPathCanon, issues, deps);
                case PrometeuManifestDTO.DependencyDeclaration.Registry reg -> resolveRegistry(reg, registry, issues, deps);
                default -> issues.report(builder -> builder
                        .error(true)
                        .message("[DEPS]: unknown dependency declaration type: " + dependency.getClass().getSimpleName()));
            }
        }

        return deps;
    }

    // Resolves local dependency path; reports canonicalization errors
    private static void resolveLocal(
            final PrometeuManifestDTO.DependencyDeclaration.Local loc,
            final Path rootProjectCanonPath,
            final BuildingIssueSink issues,
            final List<DependencyReference> deps) {
        final var pathResolve = rootProjectCanonPath.resolve(loc.path());
        try {
            final Path pathCanon = pathResolve.toRealPath();
            deps.add(new DependencyReference(pathCanon));
        } catch (IOException e) {
            issues.report(builder -> builder
                    .error(true)
                    .message("[DEPS]: failed to canonicalize dependency path: " + pathResolve)
                    .exception(e));
        }
    }

    // Looks up the registry for a dependency path; reports missing path errors
    private static void resolveRegistry(
            final PrometeuManifestDTO.DependencyDeclaration.Registry reg,
            final Registry registry,
            final BuildingIssueSink issues,
            final List<DependencyReference> deps) {
        final var ref = new ProjectRef(reg.name(), reg.version());
        final var canonPath = registry.optional(ref);
        if (canonPath.isEmpty()) {
            issues.report(builder -> builder
                    .error(true)
                    .message("[DEPS]: registry dependency not found: " + ref));
            return;
        }
        deps.add(new DependencyReference(canonPath.get()));
    }
}
