package com.airlock.iam.plugin.framework.misc.config;

import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.plugin.api.annotation.property.GlobalPluginProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.LogTypes;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import com.airlock.iam.plugin.api.exception.InitializeException;
import com.airlock.iam.plugin.api.exception.PropertyTypeMismatchException;
import com.airlock.iam.plugin.api.property.PropertyConfiguration;
import com.airlock.iam.plugin.api.util.PropertyAnnotationException;
import com.airlock.iam.plugin.framework.application.configuration.DefaultPluginConfiguration;
import com.airlock.iam.plugin.framework.application.configuration.MutablePluginConfiguration;
import com.airlock.iam.plugin.framework.misc.AppId;
import com.airlock.iam.plugin.framework.misc.config.modification.ConfigModificationItem;
import com.airlock.iam.plugin.framework.misc.config.properties.PluginListPropertyConfiguration;
import com.airlock.iam.plugin.framework.misc.config.properties.PluginMapPropertyConfiguration;
import com.airlock.iam.plugin.framework.misc.config.properties.PluginPropertyConfiguration;
import com.airlock.iam.plugin.framework.misc.config.properties.SimplePropertyConfiguration;
import com.airlock.iam.plugin.framework.misc.config.search.PluginConfigurationSearch;
import com.airlock.iam.plugin.framework.misc.config.search.SearchScope;
import com.airlock.iam.plugin.framework.misc.dictionary.CompositionalParents;
import com.airlock.iam.plugin.framework.misc.frompersistence.TranslatedMedusaConfiguration;
import com.airlock.iam.plugin.framework.misc.ioc.PluginLoader;
import com.airlock.iam.plugin.framework.misc.processor.FieldProcessors;
import com.airlock.iam.plugin.framework.misc.processor.PluginPropertyProcessor;
import com.airlock.iam.plugin.impl.cache.PluginClassCache;
import com.airlock.iam.sensitivevalues.application.service.SensitiveValuesProvider;
import com.airlock.iam.system.version.ConfigSchemaVersion;
import com.airlock.iam.system.version.IamVersion;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;

public final class PluginConfigurationDictionary implements GlobalConfigurationAccessor, PluginConfigurationOwner {
   private static final ELogger LOG = ELoggerFactory.getLogger(PluginConfigurationDictionary.class);
   private final List<MutablePluginConfiguration> configTreeRoots;
   private final Set<MutablePluginConfiguration> pool;
   private final List<ConfigHistoryItem> history;
   private final List<ConfigModificationItem> modifications;
   private final boolean isDocumentTypeSnippet;
   private final boolean noSyntheticPlugins;
   private IamVersion configVersion;
   private ConfigSchemaVersion schemaVersion = ConfigSchemaVersion.UNDEFINED_SCHEMA;
   private final Set<ConfigurationContext> addedConfigContexts;
   private final Set<ConfigurationContext> referencedConfigContexts;
   private final Optional<SensitiveValuesProvider> sensitiveValuesProvider;
   private final Supplier<ConfigurationEnvironment> environment;

   public static PluginConfigurationDictionary fromTranslatedConfiguration(
      TranslatedMedusaConfiguration config, Supplier<Optional<SensitiveValuesProvider>> sensitiveValuesProvider, Supplier<ConfigurationEnvironment> env
   ) {
      return fromTranslatedConfiguration(config, sensitiveValuesProvider, env, false);
   }

   public static PluginConfigurationDictionary fromTranslatedConfiguration(
      TranslatedMedusaConfiguration config,
      Supplier<Optional<SensitiveValuesProvider>> sensitiveValuesProvider,
      Supplier<ConfigurationEnvironment> env,
      boolean noSyntheticPlugins
   ) {
      PluginConfigurationDictionary dict = new PluginConfigurationDictionary(
         config.getConfigTreeRoots(), config.isDocumentTypeSnippet(), sensitiveValuesProvider, env, noSyntheticPlugins, config.getModifications()
      );
      dict.setSchemaVersion(config.getSchemaVersion());
      dict.setConfigVersion(config.getIamVersion());
      dict.loadSensitiveValues();
      config.getHistory().forEach(dict::addHistoryItem);
      return dict;
   }

   public static PluginConfigurationDictionary loadIamApplication(
      TranslatedMedusaConfiguration config,
      Supplier<Optional<SensitiveValuesProvider>> sensitiveValuesProvider,
      Supplier<ConfigurationEnvironment> env,
      AppId selectedApplication
   ) throws InitializeException {
      if (config.isDocumentTypeSnippet()) {
         throw new InitializeException("Configuration to load IAM application from must not be a snippet.");
      }

      List<MutablePluginConfiguration> selectedConfigRoots = config.getConfigTreeRoots()
         .stream()
         .filter(pc -> pc.getPluginDisplayName() != null)
         .filter(
            pc -> pc.getPluginDisplayName().equals(selectedApplication.id())
               || pc.getPluginDisplayName().equals(GlobalConfigurationSpec.GLOBAL_CONFIG_IDENTIFIER.id())
         )
         .collect(Collectors.toList());
      PluginConfigurationDictionary dict = new PluginConfigurationDictionary(
         selectedConfigRoots, false, sensitiveValuesProvider, env, false, Collections.emptyList()
      );
      dict.setSchemaVersion(config.getSchemaVersion());
      dict.setConfigVersion(config.getIamVersion());
      dict.loadSensitiveValues();
      return dict;
   }

   public static PluginConfigurationDictionary pluginConfigurationDictionary(
      List<PluginConfiguration> configTreeRoots,
      boolean isDocumentTypeSnippet,
      Supplier<Optional<SensitiveValuesProvider>> sensitiveValuesProvider,
      Supplier<ConfigurationEnvironment> env,
      List<ConfigModificationItem> modifications
   ) {
      List<MutablePluginConfiguration> mutableRoots = configTreeRoots.stream().map(MutablePluginConfiguration.class::cast).collect(Collectors.toList());
      return new PluginConfigurationDictionary(mutableRoots, isDocumentTypeSnippet, sensitiveValuesProvider, env, false, modifications);
   }

   public static PluginConfigurationDictionary pluginConfigurationDictionary(
      List<PluginConfiguration> configTreeRoots, Supplier<Optional<SensitiveValuesProvider>> sensitiveValuesProvider, Supplier<ConfigurationEnvironment> env
   ) {
      return pluginConfigurationDictionary(configTreeRoots, false, sensitiveValuesProvider, env, Collections.emptyList());
   }

   public static PluginConfigurationDictionary pluginConfigurationDictionary(List<PluginConfiguration> configTreeRoots) {
      return pluginConfigurationDictionary(configTreeRoots, false, Optional::empty, ConfigurationEnvironment::common, Collections.emptyList());
   }

   public static PluginConfigurationDictionary pluginConfigurationDictionary(PluginConfiguration... configTreeRoots) {
      return pluginConfigurationDictionary(Arrays.asList(configTreeRoots), false, Optional::empty, ConfigurationEnvironment::common, Collections.emptyList());
   }

   private PluginConfigurationDictionary(
      @NonNull Collection<MutablePluginConfiguration> configTreeRoots,
      boolean isDocumentTypeSnippet,
      Supplier<Optional<SensitiveValuesProvider>> sensitiveValuesProvider,
      Supplier<ConfigurationEnvironment> env,
      boolean noSyntheticPlugins,
      List<ConfigModificationItem> modifications
   ) {
      if (configTreeRoots == null) {
         throw new NullPointerException("configTreeRoots is marked non-null but is null");
      }

      this.sensitiveValuesProvider = sensitiveValuesProvider.get();
      this.addedConfigContexts = new HashSet<>();
      this.referencedConfigContexts = new HashSet<>();
      this.configTreeRoots = new ArrayList<>();
      this.configTreeRoots.addAll(configTreeRoots);
      this.isDocumentTypeSnippet = isDocumentTypeSnippet;
      this.noSyntheticPlugins = noSyntheticPlugins;
      this.modifications = modifications;
      this.pool = Sets.newIdentityHashSet();
      configTreeRoots.forEach(this::addPluginWithContainedAndAncestorsToPool);
      this.history = new ArrayList<>();
      this.environment = env;
   }

   private void loadSensitiveValues() {
      if (!this.sensitiveValuesProvider.isEmpty()) {
         ConfigurationEnvironment instanceEnvironment = this.environment.get();

         for (PluginConfiguration pc : this.getAllPluginConfigurations()) {
            new PluginConfigurationPropertiesView(pc, instanceEnvironment)
               .inheritFromParent()
               .inheritFromCommonEnvironment()
               .getProperties()
               .allProperties()
               .filter(SimplePropertyConfiguration.class::isInstance)
               .forEach(property -> this.loadSensitiveValue((SimplePropertyConfiguration)property));
         }
      }
   }

   private void loadSensitiveValue(SimplePropertyConfiguration property) {
      Optional<String> externalId = property.getExternalStorageId();
      if (!externalId.isEmpty()) {
         Optional<String> value = this.sensitiveValuesProvider.orElseThrow().getSensitiveValue(externalId.get());
         property.setValue(value.orElse(null));
      }
   }

   public TranslatedMedusaConfiguration toTranslatedConfiguration() {
      return new TranslatedMedusaConfiguration(
         this.getConfigTreeRoots(), this.isDocumentTypeSnippet, this.schemaVersion, this.configVersion, this.history, this.modifications
      );
   }

   @Override
   public Optional<PluginConfigurationDictionary.GlobalDescription> getGlobalPluginDescription(String identifier, ConfigurationEnvironment env) {
      PluginConfigurationDictionary.GlobalPluginSearchPredicate globalIdentifierMatches = (globalId, var2x) -> identifier.equals(globalId);
      return this.getGlobalPlugin(globalIdentifierMatches, env);
   }

   private Optional<PluginConfigurationDictionary.GlobalDescription> getGlobalPlugin(
      PluginConfigurationDictionary.GlobalPluginSearchPredicate searchPredicate, ConfigurationEnvironment env
   ) {
      MutablePluginConfiguration globalConfig = this.getByDisplayName(GlobalConfigurationSpec.GLOBAL_CONFIG_IDENTIFIER.id());
      if (globalConfig == null) {
         return Optional.empty();
      }

      Class<?> globalConfigClass;
      try {
         globalConfigClass = globalConfig.getPluginClass();
      } catch (InitializeException e) {
         LOG.error(LogTypes.CONFIGURATION_ERROR, "Error loading global configuration class: " + e.getMessage(), e);
         return Optional.empty();
      }

      if (!GlobalConfigurationSpec.class.isAssignableFrom(globalConfigClass)) {
         LOG.error(
            LogTypes.CONFIGURATION_ERROR,
            "Error initializing global configuration; identifier '"
               + GlobalConfigurationSpec.GLOBAL_CONFIG_IDENTIFIER
               + "' is reserved for a global configuration plugin but is used instead for a plugin of type: "
               + PluginLoader.getDisplayName(globalConfigClass)
         );
         return Optional.empty();
      } else {
         return this.findGlobalPluginConfiguration(globalConfig, searchPredicate, env);
      }
   }

   private Optional<PluginConfigurationDictionary.GlobalDescription> findGlobalPluginConfiguration(
      MutablePluginConfiguration currentConfig, PluginConfigurationDictionary.GlobalPluginSearchPredicate searchPredicate, ConfigurationEnvironment env
   ) {
      Class<?> globalConfigClass;
      Collection<Field> fields;
      try {
         globalConfigClass = currentConfig.getPluginClass();
         fields = PluginClassCache.collectAllAnnotatedFields(globalConfigClass, PluginProperty.class);
      } catch (InitializeException e) {
         LOG.error(LogTypes.CONFIGURATION_ERROR, "Error loading global configuration class: " + e.getMessage(), e);
         return Optional.empty();
      }

      for (Field pluginField : fields) {
         String propertyName = pluginField.getName();
         GlobalPluginProperty globalAnnotation = pluginField.getAnnotation(GlobalPluginProperty.class);
         PluginProperty pluginPropertyAnnotation = pluginField.getAnnotation(PluginProperty.class);
         String globalId = null;
         if (globalAnnotation != null) {
            globalId = globalAnnotation.id();
            if (StringUtils.isBlank(globalId)) {
               LOG.error(
                  LogTypes.CONFIGURATION_ERROR,
                  "Global Plugin " + globalConfigClass.getName() + " has an empty global ID on field '" + propertyName + "' - ignoring."
               );
               continue;
            }
         } else if (pluginPropertyAnnotation.supportsContexts()) {
            LOG.error(
               LogTypes.CONFIGURATION_ERROR,
               "Plugin property '"
                  + propertyName
                  + "' in global config must either be annotated with @"
                  + GlobalPluginProperty.class.getSimpleName()
                  + " or have the 'supportsContexts' attribute set to false."
            );
            continue;
         }

         Collection<ConfigurationContext> contextsForProperty = new PluginConfigurationPropertiesView(currentConfig, env)
            .inheritFromParent()
            .getProperties()
            .getContextsForProperty(propertyName);
         if (contextsForProperty.stream().anyMatch(c -> !c.isDefaultContext())) {
            LOG.error(
               LogTypes.CONFIGURATION_ERROR,
               "Plugin property '"
                  + propertyName
                  + "' in plugin '"
                  + globalConfigClass.getName()
                  + "' does not support contexts but is defined in "
                  + contextsForProperty.stream()
                     .filter(c -> !c.isDefaultContext())
                     .<CharSequence>map(ConfigurationContext::getIdentifier)
                     .collect(Collectors.joining())
            );
         } else {
            MutablePluginConfiguration pluginConfiguration;
            try {
               pluginConfiguration = currentConfig.getPluginPropertyConfigurationValue(propertyName, env, ConfigurationContext.defaultContext());
            } catch (PropertyTypeMismatchException e) {
               LOG.error(
                  LogTypes.CONFIGURATION_ERROR,
                  "Global Plugin " + globalConfigClass.getName() + " has an invalidly typed field '" + propertyName + "': " + e.getMessage(),
                  e
               );
               continue;
            }

            if (globalAnnotation == null) {
               if (pluginConfiguration != null) {
                  Optional<PluginConfigurationDictionary.GlobalDescription> findGlobalPluginWithinSubTree = this.findGlobalPluginConfiguration(
                     pluginConfiguration, searchPredicate, env
                  );
                  if (findGlobalPluginWithinSubTree.isPresent()) {
                     return findGlobalPluginWithinSubTree;
                  }
               }
            } else if (searchPredicate.matches(globalId, pluginConfiguration)) {
               Class<?> defaultType = pluginPropertyAnnotation.defaultValue();
               return Optional.of(
                  new PluginConfigurationDictionary.GlobalDescription(
                     currentConfig, propertyName, pluginConfiguration, !Object.class.equals(defaultType) ? this.createSyntheticPlugin(defaultType) : null
                  )
               );
            }
         }
      }

      return Optional.empty();
   }

   public void addPluginConfiguration(PluginConfiguration pc) {
      this.addPluginWithContainedAndAncestorsToPool(pc);
   }

   private boolean isRegisteredAsRoot(PluginConfiguration pc) {
      if (pc == null) {
         return false;
      }

      for (PluginConfiguration root : this.configTreeRoots) {
         if (root == pc) {
            return true;
         }
      }

      return false;
   }

   public void addConfigTreeRootPluginConfiguration(PluginConfiguration pc) {
      this.addPluginConfiguration(pc);
      this.addConfigToRoots((MutablePluginConfiguration)pc);
   }

   private void createDefaultPluginConfigurations(@NonNull MutablePluginConfiguration pc) {
      if (pc == null) {
         throw new NullPointerException("pc is marked non-null but is null");
      }

      if (!this.noSyntheticPlugins) {
         try {
            for (Field field : PluginClassCache.collectAllFields(pc.getPluginClass())) {
               field.setAccessible(true);
               if (FieldProcessors.getPropertyProcessorForField(field) instanceof PluginPropertyProcessor ppp) {
                  PluginProperty annotation = ppp.getAnnotation(field);
                  if (!Object.class.equals(annotation.defaultValue())) {
                     MutablePluginConfiguration defaultPc = this.createSyntheticPlugin(annotation.defaultValue());
                     this.pool.add(defaultPc);
                     this.createDefaultPluginConfigurations(defaultPc);
                     this.setDefaultValueForDefaultContextAndCommonEnvironment(pc, field, ppp, defaultPc);
                     this.setDefaultValuesForLocallyUsedContextsAndEnvironments(pc, field, ppp, defaultPc);
                  }
               }
            }
         } catch (PropertyAnnotationException | InitializeException | NoClassDefFoundError var8) {
         }
      }
   }

   private void setDefaultValueForDefaultContextAndCommonEnvironment(
      MutablePluginConfiguration pc, Field field, PluginPropertyProcessor ppp, MutablePluginConfiguration defaultPc
   ) throws PropertyTypeMismatchException {
      if (pc.getPluginPropertyConfigurationValue(field.getName(), ConfigurationEnvironment.common(), ConfigurationContext.defaultContext()) == null) {
         ppp.setValueInConfiguration(pc, field, ConfigurationEnvironment.common(), ConfigurationContext.defaultContext(), Optional.empty(), defaultPc);
      }
   }

   private void setDefaultValuesForLocallyUsedContextsAndEnvironments(
      MutablePluginConfiguration pc, Field field, PluginPropertyProcessor ppp, MutablePluginConfiguration defaultPc
   ) throws PropertyTypeMismatchException {
      for (ConfigurationEnvironment env : pc.getLocallyUsedConfigurationEnvironments()) {
         for (ConfigurationContext ctx : pc.getLocallyUsedConfigurationContexts(env)) {
            if (pc.isPropertyDefinedLocallyInEnvironmentAndContext(field.getName(), env, ctx)
               && pc.getPluginPropertyConfigurationValue(field.getName(), env, ctx) == null) {
               ppp.setValueInConfiguration(pc, field, env, ctx, Optional.empty(), defaultPc);
            }
         }
      }
   }

   @Override
   public MutablePluginConfiguration createSyntheticPlugin(Class<?> pluginClass) {
      MutablePluginConfiguration defaultPc = new DefaultPluginConfiguration(null, pluginClass.getName());
      defaultPc.setSynthetic(true);
      defaultPc.setGlobalConfiguration(this);
      defaultPc.setOwner(this);
      return defaultPc;
   }

   private void addPluginWithContainedAndAncestorsToPool(PluginConfiguration newPluginConfig) {
      if (newPluginConfig != null) {
         if (this.hasCompositionalParents(newPluginConfig)) {
            this.removeConfigFromRoots(newPluginConfig);
         }

         CombiningWalkerScope scope = CombiningWalkerScope.with().scope(new ParentWalkerScope()).scope(new AllPropertiesWalkerScope()).combine();
         Iterator<MutablePluginConfiguration> iter = new SubtreeWalker(newPluginConfig, scope);

         for (boolean first = true; iter.hasNext(); first = false) {
            MutablePluginConfiguration pluginConfigToExamine = iter.next();
            pluginConfigToExamine.setGlobalConfiguration(this);
            if (pluginConfigToExamine.getPluginDisplayName() != null) {
               PluginConfiguration existingWithSameDisplayName = this.getByDisplayName(pluginConfigToExamine.getPluginDisplayName());
               if (existingWithSameDisplayName != null && pluginConfigToExamine != existingWithSameDisplayName) {
                  throw new IllegalStateException("Display name not unique: \"" + pluginConfigToExamine.getPluginDisplayName() + "\"!");
               }
            }

            if (this.pool.add(pluginConfigToExamine)) {
               pluginConfigToExamine.setGlobalConfiguration(this);
               pluginConfigToExamine.setOwner(this);
               pluginConfigToExamine.addLocallyUsedConfigurationContextsInAllEnvironments(this.referencedConfigContexts);
               this.createDefaultPluginConfigurations(pluginConfigToExamine);
            }

            if (!first) {
               this.removeConfigFromRoots(pluginConfigToExamine);
            }
         }
      }
   }

   public Set<MutablePluginConfiguration> getAssignablePluginConfiguration(Class<?> pluginClass) {
      Objects.requireNonNull(pluginClass);
      Set<MutablePluginConfiguration> res = Sets.newIdentityHashSet();

      for (MutablePluginConfiguration pc : this.pool) {
         try {
            Class<?> currentPluginClass = pc.getPluginClass();
            if (currentPluginClass != null && pluginClass.isAssignableFrom(currentPluginClass)) {
               res.add(pc);
            }
         } catch (InitializeException var6) {
         }
      }

      return res;
   }

   public List<MutablePluginConfiguration> getConfigTreeRoots() {
      return Collections.unmodifiableList(this.configTreeRoots);
   }

   public Set<MutablePluginConfiguration> getParentHierarchyRoots() {
      Set<MutablePluginConfiguration> res = new HashSet<>();

      for (MutablePluginConfiguration pc : this.pool) {
         if (!pc.hasParentConfiguration()) {
            res.add(pc);
         }
      }

      return res;
   }

   public Set<MutablePluginConfiguration> getChildren(PluginConfiguration parent) {
      Objects.requireNonNull(parent);
      Set<MutablePluginConfiguration> res = Sets.newIdentityHashSet();

      for (MutablePluginConfiguration pc : this.pool) {
         if (parent.isParentOf(pc)) {
            res.add(pc);
         }
      }

      return res;
   }

   private Set<MutablePluginConfiguration> getDescendants(PluginConfiguration ancestor) {
      Objects.requireNonNull(ancestor);
      Set<MutablePluginConfiguration> res = new HashSet<>();
      this.fillWithChildren(res, ancestor);
      return res;
   }

   private void fillWithChildren(Set<MutablePluginConfiguration> res, PluginConfiguration ancestor) {
      Objects.requireNonNull(ancestor);
      Objects.requireNonNull(res);

      for (MutablePluginConfiguration pc : this.pool) {
         if (ancestor.isParentOf(pc)) {
            res.add(pc);
            this.fillWithChildren(res, pc);
         }
      }
   }

   public Set<MutablePluginConfiguration> getAllPluginConfigurations() {
      Set<MutablePluginConfiguration> set = Sets.newIdentityHashSet();
      set.addAll(this.pool);
      return set;
   }

   private Set<MutablePluginConfiguration> getContainedPluginConfigurationObjects(MutablePluginConfiguration pc) {
      Objects.requireNonNull(pc);
      Set<MutablePluginConfiguration> res = Sets.newIdentityHashSet();
      this.fillWithContainedPluginConfigurationObjects(res, pc);
      res.remove(pc);
      return res;
   }

   private Set<MutablePluginConfiguration> getDirectlyReferencedPluginConfigurationObjects(MutablePluginConfiguration pc) {
      Set<MutablePluginConfiguration> res = new HashSet<>();
      this.doForAllDirectlyReferencedPluginConfigurationObjects(pc, res::add);
      return res;
   }

   public boolean hasCompositionalParents(PluginConfiguration orphan) {
      return CompositionalParents.builder().pool(this.pool).pluginConfiguration(orphan).build().hasCompositionalParents();
   }

   private void fillWithContainedPluginConfigurationObjects(Set<MutablePluginConfiguration> res, MutablePluginConfiguration pc) {
      Objects.requireNonNull(res);
      if (pc != null) {
         res.add(pc);
         this.doForAllDirectlyReferencedPluginConfigurationObjects(pc, p -> this.fillWithContainedPluginConfigurationObjects(res, p));
      }
   }

   private void doForAllDirectlyReferencedPluginConfigurationObjects(MutablePluginConfiguration config, Consumer<MutablePluginConfiguration> pluginConsumer) {
      Objects.requireNonNull(config);

      for (ConfigurationEnvironment env : config.getLocallyUsedConfigurationEnvironments()) {
         for (PropertyConfiguration propertyConfiguration : config.getEnvironmentSpecificProperties(env).getAllProperties()) {
            if (propertyConfiguration instanceof PluginPropertyConfiguration) {
               pluginConsumer.accept((MutablePluginConfiguration)propertyConfiguration.getValue());
            } else if (propertyConfiguration instanceof PluginListPropertyConfiguration pluginListPropertyConfiguration) {
               List<PluginConfiguration> containedPlugins = pluginListPropertyConfiguration.getValue();
               if (containedPlugins != null) {
                  for (PluginConfiguration containedPlugin : containedPlugins) {
                     pluginConsumer.accept((MutablePluginConfiguration)containedPlugin);
                  }
               }
            } else if (propertyConfiguration instanceof PluginMapPropertyConfiguration pluginMapPropertyConfiguration) {
               Map<String, PluginConfiguration> containedPlugins = pluginMapPropertyConfiguration.getValue();
               if (containedPlugins != null) {
                  for (PluginConfiguration containedPlugin : containedPlugins.values()) {
                     pluginConsumer.accept((MutablePluginConfiguration)containedPlugin);
                  }
               }
            }
         }
      }
   }

   public MutablePluginConfiguration getByDisplayName(String displayName) {
      Objects.requireNonNull(displayName, "display name must not be null");
      return this.pool.stream().filter(pc -> displayName.equals(pc.getPluginDisplayName())).findFirst().orElse(null);
   }

   @Deprecated(since = "8.5")
   public MutablePluginConfiguration getByIdentifier(String displayName) {
      return this.getByDisplayName(displayName);
   }

   @Nullable
   public MutablePluginConfiguration getByUuid(String uuid) {
      Objects.requireNonNull(uuid, "UUID must not be null");
      return this.pool.stream().filter(pc -> pc.getUuid().equals(uuid)).findFirst().orElse(null);
   }

   public MutablePluginConfiguration getByIdentityHashCode(int identityHashCode) {
      return this.pool.stream().filter(pc -> System.identityHashCode(pc) == identityHashCode).findFirst().orElse(null);
   }

   public boolean displayNameExists(String displayName) {
      return this.getByDisplayName(displayName) != null;
   }

   @Deprecated(since = "8.5")
   public boolean identifierExists(String displayName) {
      return this.displayNameExists(displayName);
   }

   @Override
   public void removePluginConfiguration(PluginConfiguration pluginConfigurationToDelete, boolean deleteDescendant, boolean deleteContainedPlugins) throws IllegalStateException {
      if (pluginConfigurationToDelete != null) {
         this.followAllReferencesAndUpdatePool();
         Set<MutablePluginConfiguration> listOfConfigsToDelete = Sets.newIdentityHashSet();
         synchronized (this) {
            MutablePluginConfiguration mutableToDelete = (MutablePluginConfiguration)pluginConfigurationToDelete;
            listOfConfigsToDelete.add(mutableToDelete);
            Set<MutablePluginConfiguration> descendants = this.getDescendants(pluginConfigurationToDelete);
            if (deleteDescendant) {
               listOfConfigsToDelete.addAll(descendants);
            } else if (!descendants.isEmpty()) {
               throw new IllegalStateException("Cannot remove plugin configuration: descendants (parent-child) still refer to it.");
            }

            if (deleteContainedPlugins) {
               Set<MutablePluginConfiguration> containedPlugins = this.getContainedPluginConfigurationObjects(mutableToDelete);
               listOfConfigsToDelete.addAll(containedPlugins);
            } else {
               this.detachPlugins(pluginConfigurationToDelete);
               if (!this.getContainedPluginConfigurationObjects(mutableToDelete).isEmpty()) {
                  throw new IllegalStateException("Detach plugins failed. Plugin configuration should have no referenced plugins, but it does.");
               }
            }

            for (PluginConfiguration configToDelete : listOfConfigsToDelete) {
               for (MutablePluginConfiguration pc : this.pool) {
                  if (pc.getParentConfiguration() == configToDelete) {
                     pc.removeParent();
                  }
               }
            }

            for (MutablePluginConfiguration configToDelete : listOfConfigsToDelete) {
               this.removeConfigFromRoots(configToDelete);
            }

            for (MutablePluginConfiguration configToDelete : listOfConfigsToDelete) {
               this.pool.remove(configToDelete);
            }

            this.refreshAllConfigurationContexts();

            for (PluginConfiguration configToDelete : listOfConfigsToDelete) {
               for (PluginConfiguration pc : this.pool) {
                  for (ConfigurationEnvironment env : pc.getLocallyUsedConfigurationEnvironments()) {
                     for (PropertyConfiguration propertyConfiguration : pc.getEnvironmentSpecificProperties(env).getAllProperties()) {
                        if (propertyConfiguration instanceof PluginPropertyConfiguration
                           && ((PluginPropertyConfiguration)propertyConfiguration).getValue() == configToDelete) {
                           ((PluginPropertyConfiguration)propertyConfiguration).setValue(null);
                        } else if (propertyConfiguration instanceof PluginListPropertyConfiguration) {
                           List<PluginConfiguration> listElems = ((PluginListPropertyConfiguration)propertyConfiguration).getValue();
                           if (listElems != null) {
                              listElems.removeIf(plugin -> plugin == configToDelete);
                           }
                        } else if (propertyConfiguration instanceof PluginMapPropertyConfiguration) {
                           Map<String, PluginConfiguration> map = ((PluginMapPropertyConfiguration)propertyConfiguration).getValue();
                           if (map != null) {
                              String key = null;
                              Iterator var18 = map.entrySet().iterator();

                              while (true) {
                                 if (var18.hasNext()) {
                                    Entry<String, PluginConfiguration> mapEntry = (Entry<String, PluginConfiguration>)var18.next();
                                    if (mapEntry.getValue() != configToDelete) {
                                       continue;
                                    }

                                    key = mapEntry.getKey();
                                 }

                                 if (key != null) {
                                    map.remove(key);
                                 }
                                 break;
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
      }
   }

   private void followAllReferencesAndUpdatePool() {
      synchronized (this) {
         for (MutablePluginConfiguration pc : Set.copyOf(this.pool)) {
            this.addPluginWithContainedAndAncestorsToPool(pc);
         }

         for (MutablePluginConfiguration pc : this.configTreeRoots) {
            this.addPluginWithContainedAndAncestorsToPool(pc);
         }
      }
   }

   public void addConfigurationContext(ConfigurationContext context) {
      Objects.requireNonNull(context);
      synchronized (this) {
         this.addedConfigContexts.add(context);
      }
   }

   public void removeConfigurationContext(ConfigurationContext context) {
      Objects.requireNonNull(context);
      Validate.isTrue(!context.isDefaultContext(), "Cannot remove default context!", new Object[0]);
      synchronized (this) {
         this.addedConfigContexts.remove(context);
         this.referencedConfigContexts.remove(context);

         for (MutablePluginConfiguration pc : this.pool) {
            pc.removeAllPropertiesForContext(context);
         }
      }
   }

   public SortedSet<ConfigurationContext> getConfigurationContexts() {
      SortedSet<ConfigurationContext> set = new TreeSet<>(this.referencedConfigContexts);
      set.addAll(this.addedConfigContexts);
      set.add(ConfigurationContext.defaultContext());
      return set;
   }

   public SortedSet<ConfigurationEnvironment> getConfigurationEnvironments() {
      SortedSet<ConfigurationEnvironment> set = new TreeSet<>();
      set.add(ConfigurationEnvironment.common());

      for (PluginConfiguration pc : this.pool) {
         set.addAll(pc.getLocallyUsedConfigurationEnvironments());
      }

      return set;
   }

   private void refreshAllConfigurationContexts() {
      this.referencedConfigContexts.clear();

      for (MutablePluginConfiguration pc : this.pool) {
         pc.addLocallyUsedConfigurationContextsInAllEnvironments(this.referencedConfigContexts);
      }
   }

   public Set<ConfigurationContext> getDeletableContexts() {
      Set<ConfigurationContext> res = this.getConfigurationContexts();
      res.remove(ConfigurationContext.defaultContext());
      return res;
   }

   public Set<PluginConfiguration> getMatchingPluginConfigurations(SearchScope scope, String... searchTerms) {
      Objects.requireNonNull(scope, "scope must not be null");
      if (searchTerms != null && searchTerms.length != 0) {
         Set<PluginConfiguration> remainingConfigs = new HashSet<>(this.pool);

         for (String searchTerm : searchTerms) {
            if (StringUtils.isNotBlank(searchTerm)) {
               remainingConfigs = PluginConfigurationSearch.search(remainingConfigs, scope, searchTerm);
            }
         }

         return remainingConfigs;
      } else {
         return new HashSet<>();
      }
   }

   public Set<MutablePluginConfiguration> getUnconnectedItems(Set<String> treeRootIdentifiers) {
      return this.configTreeRoots.stream().filter(pc -> !treeRootIdentifiers.contains(pc.getPluginDisplayName())).collect(Collectors.toSet());
   }

   public void addHistoryItem(ConfigHistoryItem configMetaData) {
      Objects.requireNonNull(configMetaData, "config meta data must not be null");
      this.history.add(configMetaData);
   }

   public void replacePluginConfigurationInTree(MutablePluginConfiguration configToReplace, MutablePluginConfiguration newConfig) throws ConfigurationException {
      if (configToReplace != null) {
         Set<MutablePluginConfiguration> set = Sets.newIdentityHashSet();
         set.addAll(this.pool);

         for (MutablePluginConfiguration pc : set) {
            if (pc != configToReplace) {
               for (ConfigurationEnvironment env : pc.getLocallyUsedConfigurationEnvironments()) {
                  for (PropertyConfiguration propertyConfiguration : pc.getEnvironmentSpecificProperties(env).getAllProperties()) {
                     if (propertyConfiguration instanceof PluginPropertyConfiguration
                        && ((PluginPropertyConfiguration)propertyConfiguration).getValue() == configToReplace) {
                        ((PluginPropertyConfiguration)propertyConfiguration).setValue(newConfig);
                     } else if (propertyConfiguration instanceof PluginListPropertyConfiguration) {
                        List<PluginConfiguration> listElems = ((PluginListPropertyConfiguration)propertyConfiguration).getValue();
                        if (listElems != null) {
                           for (int i = 0; i < listElems.size(); i++) {
                              if (listElems.get(i) == configToReplace) {
                                 listElems.set(i, newConfig);
                              }
                           }
                        }
                     } else if (propertyConfiguration instanceof PluginMapPropertyConfiguration) {
                        Map<String, PluginConfiguration> map = ((PluginMapPropertyConfiguration)propertyConfiguration).getValue();
                        if (map != null) {
                           for (Entry<String, PluginConfiguration> mapEntry : map.entrySet()) {
                              if (mapEntry != null && mapEntry.getValue() == configToReplace) {
                                 mapEntry.setValue(newConfig);
                              }
                           }
                        }
                     }
                  }
               }

               if (pc.getParentConfiguration() == configToReplace && pc != newConfig) {
                  pc.setParentConfiguration(newConfig);
               }
            }
         }

         if (this.isRegisteredAsRoot(configToReplace)) {
            int index = this.configTreeRoots.indexOf(configToReplace);
            Preconditions.checkState(index >= 0, "index must be >= 0 if registered as roots");
            this.removeConfigFromRoots(configToReplace);
            if (!this.hasCompositionalParents(newConfig)) {
               if (!this.isRegisteredAsRoot(newConfig)) {
                  this.addConfigToRoots(newConfig, index);
               }
            } else {
               this.removeConfigFromRoots(newConfig);
            }
         } else {
            this.removeConfigFromRoots(newConfig);
         }
      }
   }

   public void detachPlugins(PluginConfiguration pc) {
      for (ConfigurationEnvironment env : pc.getLocallyUsedConfigurationEnvironments()) {
         for (PropertyConfiguration propertyConfiguration : pc.getEnvironmentSpecificProperties(env).getAllProperties()) {
            Collection<PluginConfiguration> pluginConfigurationList = propertyConfiguration.getDirectlyReferenced();
            propertyConfiguration.disconnectDirectlyReferenced();
            this.addPluginsToRootsOrRemoveFromPoolIfNecessary(pluginConfigurationList);
         }
      }
   }

   public void cleanUpPlugins(Set<String> rootPluginIdentifiers) {
      for (PluginConfiguration unconnectedPluginConfiguration : this.getUnconnectedItems(rootPluginIdentifiers)) {
         if (unconnectedPluginConfiguration != null) {
            this.detachPlugins(unconnectedPluginConfiguration);
            this.removePluginConfiguration(unconnectedPluginConfiguration, true, false);
         }
      }

      if (!this.getUnconnectedItems(rootPluginIdentifiers).isEmpty()) {
         this.cleanUpPlugins(rootPluginIdentifiers);
      }
   }

   @Override
   public void removePluginConfigurationAndUnusedContainedPlugins(
      Set<String> rootPluginIdentifiers, Set<PluginConfiguration> pluginsToDelete, boolean deleteDescendant
   ) {
      Set<PluginConfiguration> directlyReferencedPlugins = this.removePluginsAndGetDirectlyReferenced(pluginsToDelete, deleteDescendant);

      while (!directlyReferencedPlugins.isEmpty()) {
         pluginsToDelete = this.filterPluginsToRemove(rootPluginIdentifiers, directlyReferencedPlugins);
         directlyReferencedPlugins = this.removePluginsAndGetDirectlyReferenced(pluginsToDelete, deleteDescendant);
      }
   }

   private Set<PluginConfiguration> removePluginsAndGetDirectlyReferenced(Set<PluginConfiguration> pluginsToDelete, boolean deleteDescendant) {
      Set<PluginConfiguration> nextDirectlyReferencedPlugins = new HashSet<>();

      for (PluginConfiguration pc : pluginsToDelete) {
         nextDirectlyReferencedPlugins.addAll(this.getDirectlyReferencedPluginConfigurationObjects((MutablePluginConfiguration)pc));
         this.removePluginConfiguration(pc, deleteDescendant, false);
      }

      return nextDirectlyReferencedPlugins;
   }

   private Set<PluginConfiguration> filterPluginsToRemove(Set<String> rootPluginIdentifiers, Set<PluginConfiguration> containedPlugins) {
      Set<PluginConfiguration> nextPluginsToDelete = new HashSet<>(containedPlugins);
      Set<MutablePluginConfiguration> unconnectedItems = this.getUnconnectedItems(rootPluginIdentifiers);
      nextPluginsToDelete.removeIf(o -> o == null || unconnectedItems.stream().noneMatch(x -> x.getUuid().equals(o.getUuid())));
      return nextPluginsToDelete;
   }

   @Override
   public boolean isGlobalConfig(PluginConfiguration pluginConfiguration, ConfigurationEnvironment env) {
      PluginConfigurationDictionary.GlobalPluginSearchPredicate pluginMatches = (var1x, pluginConfig) -> pluginConfiguration.equals(pluginConfig);
      return this.getGlobalPlugin(pluginMatches, env).isPresent();
   }

   @Override
   public void handlePluginPropertyConfigurationChanged(PluginConfigurationPropertyChangedEvent event) {
      PropertyConfiguration newValue = event.getNewValue();
      PropertyConfiguration oldValue = event.getOldValue();
      if (oldValue != null) {
         this.addPluginsToRootsOrRemoveFromPoolIfNecessary(oldValue.getDirectlyReferenced());
         if (newValue == null) {
            this.refreshAllConfigurationContexts();
         }
      }

      if (newValue != null) {
         this.processNewPluginConfigurationInTree(newValue);
         this.referencedConfigContexts.add(event.getContext());
      }

      MutablePluginConfiguration pluginConfiguration = event.getPluginConfiguration();
      this.propagateSyntheticChangeToCompositionalParents(pluginConfiguration);
   }

   private void propagateSyntheticChangeToCompositionalParents(MutablePluginConfiguration pluginConfiguration) {
      for (PluginConfiguration parent : CompositionalParents.builder()
         .pool(this.pool)
         .pluginConfiguration(pluginConfiguration)
         .build()
         .getAllCompositionalParents()) {
         MutablePluginConfiguration compositionalParent = (MutablePluginConfiguration)parent;
         if (compositionalParent != null && compositionalParent.isSynthetic() && !pluginConfiguration.isSynthetic()) {
            compositionalParent.setSynthetic(false);
            this.propagateSyntheticChangeToCompositionalParents(compositionalParent);
         }
      }
   }

   @Override
   public void handlePluginConfigurationParentChanged(PluginConfigurationParentChangedEvent event) {
      this.handlePluginConfigurationParentChanged(event.getOldParent(), event.getNewParent());
      this.propagateSyntheticChangeToCompositionalParents(event.getPluginConfiguration());
   }

   private void handlePluginConfigurationParentChanged(MutablePluginConfiguration oldParent, MutablePluginConfiguration newParent) {
      if (oldParent != null) {
         this.addPluginToRootsIfNecessary(oldParent);
      }

      if (newParent != null) {
         this.processNewPluginConfigurationInTree(newParent);
      }
   }

   private void processNewPluginConfigurationInTree(PropertyConfiguration newPluginConfig) {
      for (PluginConfiguration pc : newPluginConfig.getDirectlyReferenced()) {
         this.processNewPluginConfigurationInTree((MutablePluginConfiguration)pc);
      }
   }

   private void processNewPluginConfigurationInTree(MutablePluginConfiguration newPluginConfiguration) {
      this.addPluginWithContainedAndAncestorsToPool(newPluginConfiguration);
      this.removeConfigFromRoots(newPluginConfiguration);
   }

   private void addPluginsToRootsOrRemoveFromPoolIfNecessary(Collection<PluginConfiguration> pcCollection) {
      for (PluginConfiguration pc : pcCollection) {
         if (pc != null && !this.hasCompositionalParents(pc)) {
            if (pc.isSynthetic()) {
               this.pool.remove((MutablePluginConfiguration)pc);
            } else {
               this.addConfigToRoots((MutablePluginConfiguration)pc);
            }
         }
      }
   }

   private void addPluginToRootsIfNecessary(MutablePluginConfiguration pc) {
      if (pc != null && !this.hasCompositionalParents(pc)) {
         this.addConfigToRoots(pc);
      }
   }

   private void addConfigToRoots(MutablePluginConfiguration pc) {
      this.addConfigToRoots(pc, this.configTreeRoots.size());
   }

   private void addConfigToRoots(MutablePluginConfiguration pc, int index) {
      Objects.requireNonNull(pc, "plugin configuration must not be null");
      if (!this.configTreeRoots.contains(pc)) {
         this.configTreeRoots.add(index, pc);
         LOG.debug("Added unconnected PluginConfiguration ''{0}'' to configRoots", new String[]{pc.toString()});
      }
   }

   private void removeConfigFromRoots(PluginConfiguration pc) {
      Objects.requireNonNull(pc, "plugin configuration must not be null");
      if (this.removeFromConfigTreeIfExists(pc)) {
         LOG.debug("Removed existing PluginConfiguration ''{0}'' from configRoots", new String[]{pc.toString()});
      }
   }

   private boolean removeFromConfigTreeIfExists(PluginConfiguration pc) {
      Iterator<MutablePluginConfiguration> rootIterator = this.configTreeRoots.listIterator();

      while (rootIterator.hasNext()) {
         PluginConfiguration candidate = rootIterator.next();
         if (candidate == pc) {
            rootIterator.remove();
            return true;
         }
      }

      return false;
   }

   public Stream<PropertyConfigurationWithPluginConfiguration> getEnvironmentSpecificProperties(ConfigurationEnvironment env) {
      return this.getAllPluginConfigurations().stream().flatMap(pc -> this.mapToPropertyConfigWithPluginConfig(pc, env));
   }

   private Stream<PropertyConfigurationWithPluginConfiguration> mapToPropertyConfigWithPluginConfig(PluginConfiguration pc, ConfigurationEnvironment env) {
      return pc.getEnvironmentSpecificProperties(env)
         .getAllPropertiesWithContext()
         .stream()
         .map(
            propertyWithContext -> new PropertyConfigurationWithPluginConfiguration(
               propertyWithContext.getPropertyConfiguration(), pc, propertyWithContext.getContext()
            )
         );
   }

   @SuppressFBWarnings(justification = "generated code")
   public List<ConfigModificationItem> getModifications() {
      return this.modifications;
   }

   @SuppressFBWarnings(justification = "generated code")
   public boolean isDocumentTypeSnippet() {
      return this.isDocumentTypeSnippet;
   }

   @SuppressFBWarnings(justification = "generated code")
   public void setConfigVersion(IamVersion configVersion) {
      this.configVersion = configVersion;
   }

   @SuppressFBWarnings(justification = "generated code")
   public IamVersion getConfigVersion() {
      return this.configVersion;
   }

   @SuppressFBWarnings(justification = "generated code")
   public void setSchemaVersion(ConfigSchemaVersion schemaVersion) {
      this.schemaVersion = schemaVersion;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<SensitiveValuesProvider> getSensitiveValuesProvider() {
      return this.sensitiveValuesProvider;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Supplier<ConfigurationEnvironment> getEnvironment() {
      return this.environment;
   }

   public record GlobalDescription(
      MutablePluginConfiguration parent, String propertyName, @Nullable MutablePluginConfiguration plugin, @Nullable MutablePluginConfiguration defaultPlugin
   ) {
   }

   @FunctionalInterface
   private interface GlobalPluginSearchPredicate {
      boolean matches(String var1, @Nullable PluginConfiguration var2);
   }
}
