package p.studio.compiler.pbs.semantics;

import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.source.identifiers.ModuleId;

import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

public final class PbsGlobalDependencySupport {
    private PbsGlobalDependencySupport() {
    }

    public record GlobalRef(
            ModuleId moduleId,
            String globalName) {
        public GlobalRef {
            moduleId = moduleId == null ? ModuleId.none() : moduleId;
        }
    }

    public static LinkedHashSet<GlobalRef> collectDependencies(
            final PbsAst.Expression expression,
            final ModuleId currentModuleId,
            final Set<String> localGlobals,
            final Map<String, GlobalRef> importedGlobalsByLocalName) {
        final var dependencies = new LinkedHashSet<GlobalRef>();
        collectRecursive(expression, currentModuleId, localGlobals, importedGlobalsByLocalName, dependencies);
        return dependencies;
    }

    private static void collectRecursive(
            final PbsAst.Expression expression,
            final ModuleId currentModuleId,
            final Set<String> localGlobals,
            final Map<String, GlobalRef> importedGlobalsByLocalName,
            final LinkedHashSet<GlobalRef> dependencies) {
        if (expression == null) {
            return;
        }
        if (expression instanceof PbsAst.IdentifierExpr identifierExpr) {
            if (localGlobals.contains(identifierExpr.name())) {
                dependencies.add(new GlobalRef(currentModuleId, identifierExpr.name()));
                return;
            }
            final var importedGlobal = importedGlobalsByLocalName.get(identifierExpr.name());
            if (importedGlobal != null) {
                dependencies.add(importedGlobal);
            }
            return;
        }
        if (expression instanceof PbsAst.GroupExpr groupExpr) {
            collectRecursive(groupExpr.expression(), currentModuleId, localGlobals, importedGlobalsByLocalName, dependencies);
            return;
        }
        if (expression instanceof PbsAst.UnaryExpr unaryExpr) {
            collectRecursive(unaryExpr.expression(), currentModuleId, localGlobals, importedGlobalsByLocalName, dependencies);
            return;
        }
        if (expression instanceof PbsAst.BinaryExpr binaryExpr) {
            collectRecursive(binaryExpr.left(), currentModuleId, localGlobals, importedGlobalsByLocalName, dependencies);
            collectRecursive(binaryExpr.right(), currentModuleId, localGlobals, importedGlobalsByLocalName, dependencies);
            return;
        }
        if (expression instanceof PbsAst.MemberExpr memberExpr) {
            collectRecursive(memberExpr.receiver(), currentModuleId, localGlobals, importedGlobalsByLocalName, dependencies);
            return;
        }
        if (expression instanceof PbsAst.NewExpr newExpr) {
            for (final var argument : newExpr.arguments()) {
                collectRecursive(argument, currentModuleId, localGlobals, importedGlobalsByLocalName, dependencies);
            }
        }
    }
}

