package net.fabricmc.fabric.impl.resource.v1;

import com.mojang.logging.LogUtils;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.fabricmc.fabric.api.resource.v1.ResourceLoader;
import net.fabricmc.fabric.api.resource.v1.reloader.ResourceReloaderKeys;
import net.fabricmc.fabric.api.util.TriState;
import net.fabricmc.fabric.impl.base.toposort.NodeSorting;
import net.fabricmc.fabric.impl.base.toposort.SortableNode;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.class_2960;
import net.minecraft.class_3264;
import net.minecraft.class_3302;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

public sealed class ResourceLoaderImpl implements ResourceLoader permits DataResourceLoaderImpl {
   private static final Logger LOGGER = LogUtils.getLogger();
   private static final Map<class_3264, ResourceLoaderImpl> IMPL_MAP = new EnumMap(class_3264.class);
   private static final boolean DEBUG_RELOADERS_IDENTITY = TriState.fromSystemProperty("fabric.resource_loader.debug.reloaders_identity").orElse(FabricLoader.getInstance().isDevelopmentEnvironment());
   public static final boolean DEBUG_PROFILE_RESOURCE_RELOADERS = Boolean.getBoolean("fabric.resource_loader.debug.profile_resource_reloaders");
   private static final boolean DEBUG_RELOADERS_ORDER = Boolean.getBoolean("fabric.resource_loader.debug.reloaders_order");
   private final Map<class_2960, class_3302> addedReloaders = new LinkedHashMap();
   private final Set<ReloaderOrder> reloadersOrdering = new LinkedHashSet();
   private final class_3264 type;

   public static ResourceLoaderImpl get(class_3264 type) {
      return (ResourceLoaderImpl)IMPL_MAP.computeIfAbsent(type, (target) -> (ResourceLoaderImpl)(target == class_3264.field_14190 ? DataResourceLoaderImpl.INSTANCE : new ResourceLoaderImpl(type)));
   }

   ResourceLoaderImpl(class_3264 type) {
      this.type = type;
   }

   protected boolean hasResourceReloader(class_2960 id) {
      return this.addedReloaders.containsKey(id);
   }

   protected final void checkUniqueResourceReloader(class_2960 id) {
      if (this.hasResourceReloader(id)) {
         throw new IllegalStateException("Tried to register resource reloader %s twice!".formatted(id));
      }
   }

   public void registerReloader(class_2960 id, class_3302 reloader) {
      Objects.requireNonNull(id, "The reloader identifier should not be null.");
      Objects.requireNonNull(reloader, "The reloader should not be null.");
      this.checkUniqueResourceReloader(id);

      for(Map.Entry<class_2960, class_3302> entry : this.addedReloaders.entrySet()) {
         if (entry.getValue() == reloader) {
            throw new IllegalStateException("Resource reloader with ID %s already in resource reloader set with ID %s!".formatted(id, entry.getKey()));
         }
      }

      this.addedReloaders.put(id, reloader);
   }

   public void addReloaderOrdering(class_2960 firstReloader, class_2960 secondReloader) {
      Objects.requireNonNull(firstReloader, "The first reloader identifier should not be null.");
      Objects.requireNonNull(secondReloader, "The second reloader identifier should not be null.");
      if (firstReloader.equals(secondReloader)) {
         throw new IllegalArgumentException("Tried to add a phase that depends on itself.");
      } else {
         this.reloadersOrdering.add(new ReloaderOrder(firstReloader, secondReloader));
      }
   }

   private class_2960 getResourceReloaderIdForSorting(class_3302 reloader) {
      if (reloader instanceof FabricResourceReloader identifiable) {
         return identifiable.fabric$getId();
      } else {
         if (DEBUG_RELOADERS_IDENTITY) {
            LOGGER.warn("The resource reloader at {} does not use identifiable registration making ordering support more difficult for other modders.", reloader.getClass().getName());
         }

         String var10001 = reloader.getClass().getName().replace(".", "/").replace("$", "_");
         return class_2960.method_60655("unknown", "private/" + var10001.toLowerCase(Locale.ROOT));
      }
   }

   public static List<class_3302> sort(class_3264 type, List<class_3302> listeners) {
      if (type == null) {
         return listeners;
      } else {
         ResourceLoaderImpl instance = get(type);
         ArrayList<class_3302> mutable = new ArrayList(listeners);
         instance.sort(mutable);
         return Collections.unmodifiableList(mutable);
      }
   }

   protected Set<Map.Entry<class_2960, class_3302>> collectReloadersToAdd(@Nullable SetupMarkerResourceReloader setupMarker) {
      return new LinkedHashSet(this.addedReloaders.entrySet());
   }

   private void sort(List<class_3302> reloaders) {
      SetupMarkerResourceReloader setupReloader = this.extractSetupMarker(reloaders);
      Set<Map.Entry<class_2960, class_3302>> reloadersToAdd = this.collectReloadersToAdd(setupReloader);
      Stream var10000 = reloadersToAdd.stream().map(Map.Entry::getValue);
      Objects.requireNonNull(reloaders);
      var10000.forEach(reloaders::remove);
      Object2ObjectOpenHashMap<class_2960, ResourceReloaderPhaseData> runtimePhases = new Object2ObjectOpenHashMap();
      Iterator<class_3302> itPhases = reloaders.iterator();
      ResourceReloaderPhaseData last = new ResourceReloaderPhaseData(ResourceReloaderKeys.BEFORE_VANILLA, (class_3302)null);
      last.setVanillaStatus(ResourceReloaderPhaseData.VanillaStatus.VANILLA);
      runtimePhases.put(last.id, last);

      while(itPhases.hasNext()) {
         class_3302 currentReloader = (class_3302)itPhases.next();
         class_2960 id = this.getResourceReloaderIdForSorting(currentReloader);
         ResourceReloaderPhaseData current = new ResourceReloaderPhaseData(id, currentReloader);
         current.setVanillaStatus(ResourceReloaderPhaseData.VanillaStatus.VANILLA);
         runtimePhases.put(id, current);
         SortableNode.link(last, current);
         last = current;
      }

      ResourceReloaderPhaseData.AfterVanilla afterVanilla = new ResourceReloaderPhaseData.AfterVanilla(ResourceReloaderKeys.AFTER_VANILLA);
      runtimePhases.put(afterVanilla.id, afterVanilla);
      SortableNode.link(last, afterVanilla);

      for(Map.Entry<class_2960, class_3302> moddedReloader : reloadersToAdd) {
         ResourceReloaderPhaseData phase = new ResourceReloaderPhaseData((class_2960)moddedReloader.getKey(), (class_3302)moddedReloader.getValue());
         runtimePhases.put(phase.id, phase);
      }

      for(ReloaderOrder order : this.reloadersOrdering) {
         ResourceReloaderPhaseData first = (ResourceReloaderPhaseData)runtimePhases.get(order.first);
         if (first != null) {
            ResourceReloaderPhaseData second = (ResourceReloaderPhaseData)runtimePhases.get(order.second);
            if (second != null) {
               SortableNode.link(first, second);
            }
         }
      }

      ObjectIterator var15 = runtimePhases.values().iterator();

      while(var15.hasNext()) {
         ResourceReloaderPhaseData putAfter = (ResourceReloaderPhaseData)var15.next();
         if (putAfter != afterVanilla && (putAfter.vanillaStatus == ResourceReloaderPhaseData.VanillaStatus.NONE || putAfter.vanillaStatus == ResourceReloaderPhaseData.VanillaStatus.AFTER)) {
            SortableNode.link(afterVanilla, putAfter);
         }
      }

      ArrayList<ResourceReloaderPhaseData> phases = new ArrayList(runtimePhases.values());
      NodeSorting.sort(phases, "resource reloaders", Comparator.comparing((data) -> data.id));
      reloaders.clear();
      if (setupReloader != null) {
         reloaders.add(setupReloader);
      }

      for(ResourceReloaderPhaseData phase : phases) {
         if (phase.resourceReloader != null) {
            reloaders.add(phase.resourceReloader);
         }
      }

      if (DEBUG_RELOADERS_ORDER) {
         LOGGER.info("Sorted reloaders: {}", phases.stream().map((data) -> {
            String str = data.id.toString();
            if (data.resourceReloader == null) {
               str = str + " (virtual)";
            }

            return str;
         }).collect(Collectors.joining(", ")));
      }

   }

   private @Nullable SetupMarkerResourceReloader extractSetupMarker(List<class_3302> reloaders) {
      if (this.type == class_3264.field_14188) {
         return null;
      } else {
         Iterator<class_3302> it = reloaders.iterator();

         while(it.hasNext()) {
            Object var4 = it.next();
            if (var4 instanceof SetupMarkerResourceReloader) {
               SetupMarkerResourceReloader marker = (SetupMarkerResourceReloader)var4;
               it.remove();
               return marker;
            }
         }

         throw new IllegalStateException("No SetupMarkerResourceReloader found in reloaders!");
      }
   }

   private static record ReloaderOrder(class_2960 first, class_2960 second) {
   }
}
