/*
 * Copyright 2017 Red Hat, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.smallrye.config;

import static io.smallrye.config.ConfigMappingLoader.configMappingProperties;
import static io.smallrye.config.ConfigMappingLoader.getConfigMappingClass;
import static io.smallrye.config.ConfigSourceInterceptor.EMPTY;
import static io.smallrye.config.Converters.newCollectionConverter;
import static io.smallrye.config.Converters.newMapConverter;
import static io.smallrye.config.Converters.newOptionalConverter;
import static io.smallrye.config.ProfileConfigSourceInterceptor.activeName;
import static io.smallrye.config.common.utils.StringUtil.unindexed;
import static io.smallrye.config.common.utils.StringUtil.unquoted;
import static java.util.stream.Collectors.toList;

import java.io.ObjectStreamException;
import java.io.Serial;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.inject.ConfigProperties;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
import org.eclipse.microprofile.config.spi.Converter;

import io.smallrye.config.SmallRyeConfigBuilder.InterceptorWithPriority;
import io.smallrye.config._private.ConfigLogging;
import io.smallrye.config._private.ConfigMessages;
import io.smallrye.config.common.utils.StringUtil;

/**
 * {@link SmallRyeConfig} provides a way to retrieve configuration values from a configuration name.
 * <p>
 * A {@link SmallRyeConfig} instance is obtained via the {@link SmallRyeConfigBuilder#build()}, which details how
 * {@link SmallRyeConfig} will behave. Generally, a {@link SmallRyeConfig} instance is composed of:
 * <ul>
 * <li>{@linkplain ConfigSource Configuration Sources} to lookup the configuration values</li>
 * <li>{@linkplain Converter Converters} to convert values to specific types</li>
 * <li>{@linkplain ConfigSourceInterceptor Interceptors} to enhance the configuration lookup process</li>
 * <li>{@linkplain ConfigMapping} Config Mappings classes to group multiple configuration values in a common prefix</li>
 * </ul>
 *
 * @author Jeff Mesnil
 * @author David M. Lloyd
 * @author Roberto Cortez
 */
public class SmallRyeConfig implements Config, Serializable {
    /**
     * Configuration name to set the main Profiles to activate. The configuration supports multiple profiles separated
     * by a comma.
     */
    public static final String SMALLRYE_CONFIG_PROFILE = "smallrye.config.profile";
    /**
     * Configuration name to set the parent Profile to activate.
     */
    public static final String SMALLRYE_CONFIG_PROFILE_PARENT = "smallrye.config.profile.parent";
    /**
     * Configuration name to set additional config locations to be loaded with the Config. The configuration supports
     * multiple locations separated by a comma and each must represent a valid {@code java.net.URI}.
     */
    public static final String SMALLRYE_CONFIG_LOCATIONS = "smallrye.config.locations";
    /**
     * Configuration name to validate that a {@link ConfigMapping} maps every available configuration name contained
     * in the mapping prefix. The configuration value must be a {@code boolean}.
     */
    public static final String SMALLRYE_CONFIG_MAPPING_VALIDATE_UNKNOWN = "smallrye.config.mapping.validate-unknown";
    /**
     * Configuration name to set the names of the secret handlers to be loaded. A value of {@code all} loads all
     * available secret handlers and a value of none {@code skips} the load. The configuration supports multiple secret
     * handlers separated by a comma.
     */
    public static final String SMALLRYE_CONFIG_SECRET_HANDLERS = "smallrye.config.secret-handlers";
    /**
     * Configuration name to enable logging of configuration values lookup in {@code DEBUG} log level. The configuration
     * value must be a {@code boolean}.
     */
    public static final String SMALLRYE_CONFIG_LOG_VALUES = "smallrye.config.log.values";

    @Serial
    private static final long serialVersionUID = 8138651532357898263L;

    private final ConfigSources configSources;
    private final Map<Type, Converter<?>> converters;
    private final Map<Type, Converter<Optional<?>>> optionalConverters = new ConcurrentHashMap<>();

    private final ConfigValidator configValidator;
    private final Map<Class<?>, Map<String, Object>> mappings;

    SmallRyeConfig(SmallRyeConfigBuilder builder) {
        this.configSources = new ConfigSources(builder);
        this.converters = buildConverters(builder);
        this.configValidator = builder.getValidator();
        this.mappings = new ConcurrentHashMap<>(buildMappings(builder));
    }

    private Map<Type, Converter<?>> buildConverters(final SmallRyeConfigBuilder builder) {
        final Map<Type, SmallRyeConfigBuilder.ConverterWithPriority> convertersToBuild = new HashMap<>(builder.getConverters());

        if (builder.isAddDiscoveredConverters()) {
            for (Converter<?> converter : builder.discoverConverters()) {
                Type type = Converters.getConverterType(converter.getClass());
                if (type == null) {
                    throw ConfigMessages.msg.unableToAddConverter(converter);
                }
                SmallRyeConfigBuilder.addConverter(type, converter, convertersToBuild);
            }
        }

        final ConcurrentHashMap<Type, Converter<?>> converters = new ConcurrentHashMap<>(Converters.ALL_CONVERTERS);
        for (Entry<Type, SmallRyeConfigBuilder.ConverterWithPriority> entry : convertersToBuild.entrySet()) {
            converters.put(entry.getKey(), entry.getValue().getConverter());
        }
        converters.put(ConfigValue.class, Converters.CONFIG_VALUE_CONVERTER);

        return converters;
    }

    Map<Class<?>, Map<String, Object>> buildMappings(final SmallRyeConfigBuilder builder)
            throws ConfigValidationException {
        SmallRyeConfigBuilder.MappingBuilder mappingsBuilder = builder.getMappingsBuilder();
        if (mappingsBuilder.isEmpty()) {
            return Collections.emptyMap();
        }

        // Perform the config mapping
        ConfigMappingContext context = SecretKeys.doUnlocked(new Supplier<ConfigMappingContext>() {
            @Override
            public ConfigMappingContext get() {
                return new ConfigMappingContext(SmallRyeConfig.this, mappingsBuilder);
            }
        });

        if (getOptionalValue(SMALLRYE_CONFIG_MAPPING_VALIDATE_UNKNOWN, boolean.class).orElse(true)) {
            context.reportUnknown(mappingsBuilder.getIgnoredPaths());
        }

        List<ConfigValidationException.Problem> problems = context.getProblems();
        if (!problems.isEmpty()) {
            throw new ConfigValidationException(problems.toArray(ConfigValidationException.Problem.NO_PROBLEMS));
        }

        return context.getMappings();
    }

    /**
     * Returns the values for the specified configuration name from the underlying
     * {@linkplain ConfigSource configuration sources}.
     * <p>
     * The lookup to the configuration will first query {@link SmallRyeConfig#getPropertyNames()} for indexed
     * properties. An indexed property uses the original property name with square brackets and an index in between, as
     * {@code my.property[0]}. All indexed properties are queried for their value, which represents a single
     * element in the returning {@code List} converted to their specified property type. The following
     * configuration:
     * <ul>
     * <li>my.property[0]=dog</li>
     * <li>my.property[1]=cat</li>
     * <li>my.property[2]=turtle</li>
     * </ul>
     * <p>
     * Results in a {@code List} with the elements {@code dog}, {@code cat}, and {@code turtle},
     * considering the configuration name as {@code my.property} and the property type as a {@code String}.
     * <p>
     * Otherwise, the configuration value is a single element that a comma-separated string ({@code ,}) can represent,
     * and split into multiple elements with the backslash ({@code \}) as the escape character.
     * A configuration of {@code my.property=dog,cat,turtle} results in a {@code List} with the elements
     * {@code dog}, {@code cat}, and {@code turtle}, considering the configuration name as
     * {@code my.property} and the property type as a {@code String}.
     * <p>
     * The indexed property format has priority when both styles are found in the same configuration source. When
     * available in multiple sources, the higher ordinal source wins, like any other configuration lookup.
     *
     * @param name The configuration property name to look for in the configuration
     * @param propertyType The type into which the resolved property values are converted
     * @return the resolved property values as a {@code List} of instances of the property type
     * @param <T> the item type
     * @throws java.lang.IllegalArgumentException if the property values cannot be converted to the specified type
     * @throws java.util.NoSuchElementException if the property isn't present in the configuration or is defined as
     *         an empty string, or the converter returns {@code null}
     *
     * @see SmallRyeConfig#getValues(String, Class, IntFunction)
     * @see SmallRyeConfig#getValues(String, Converter, IntFunction)
     * @see SmallRyeConfig#getOptionalValues(String, Class)
     * @see SmallRyeConfig#getOptionalValues(String, Class, IntFunction)
     * @see SmallRyeConfig#getOptionalValues(String, Converter, IntFunction)
     */
    @Override
    public <T> List<T> getValues(final String name, final Class<T> propertyType) {
        return getValues(name, propertyType, ArrayList::new);
    }

    /**
     * Returns the values for the specified configuration name from the underlying
     * {@linkplain ConfigSource configuration sources}.
     * <p>
     * The lookup to the configuration will first query {@link SmallRyeConfig#getPropertyNames()} for indexed
     * properties. An indexed property uses the original property name with square brackets and an index in between, as
     * {@code my.property[0]}. All indexed properties are queried for their value, which represents a single
     * element in the returning {@code Collection} converted to their specified property type. The following
     * configuration:
     * <ul>
     * <li>my.property[0]=dog</li>
     * <li>my.property[1]=cat</li>
     * <li>my.property[2]=turtle</li>
     * </ul>
     * <p>
     * Results in a {@code Collection} with the elements {@code dog}, {@code cat}, and {@code turtle},
     * considering the configuration name as {@code my.property} and the property type as a {@code String}.
     * <p>
     * Otherwise, the configuration value is a single element that a comma-separated string ({@code ,}) can represent,
     * and split into multiple elements with the backslash ({@code \}) as the escape character.
     * A configuration of {@code my.property=dog,cat,turtle} results in a {@code Collection} with the elements
     * {@code dog}, {@code cat}, and {@code turtle}, considering the configuration name as
     * {@code my.property} and the property type as a {@code String}.
     * <p>
     * The indexed property format has priority when both styles are found in the same configuration source. When
     * available in multiple sources, the higher ordinal source wins, like any other configuration lookup.
     *
     * @param name The configuration property name to look for in the configuration
     * @param itemClass The type into which the resolved property values are converted
     * @param collectionFactory the resulting instance of a {@code Collection} to return the property values
     * @return the resolved property values as a {@code Collection} of instances of the property type
     * @param <T> the item type
     * @param <C> the collection type
     * @throws java.lang.IllegalArgumentException if the property values cannot be converted to the specified type
     * @throws java.util.NoSuchElementException if the property isn't present in the configuration or is defined as
     *         an empty string, or the converter returns {@code null}
     *
     * @see SmallRyeConfig#getValues(String, Class)
     * @see SmallRyeConfig#getValues(String, Converter, IntFunction)
     * @see SmallRyeConfig#getOptionalValues(String, Class)
     * @see SmallRyeConfig#getOptionalValues(String, Class, IntFunction)
     * @see SmallRyeConfig#getOptionalValues(String, Converter, IntFunction)
     */
    public <T, C extends Collection<T>> C getValues(final String name, final Class<T> itemClass,
            final IntFunction<C> collectionFactory) {
        return getValues(name, requireConverter(itemClass), collectionFactory);
    }

    /**
     * Returns the values for the specified configuration name from the underlying
     * {@linkplain ConfigSource configuration sources}.
     * <p>
     * The lookup to the configuration will first query {@link SmallRyeConfig#getPropertyNames()} for indexed
     * properties. An indexed property uses the original property name with square brackets and an index in between, as
     * {@code my.property[0]}. All indexed properties are queried for their value, which represents a single
     * element in the returning {@code Collection} converted by the specified {@link Converter}. The following
     * configuration:
     * <ul>
     * <li>my.property[0]=dog</li>
     * <li>my.property[1]=cat</li>
     * <li>my.property[2]=turtle</li>
     * </ul>
     * <p>
     * Results in a {@code Collection} with the elements {@code dog}, {@code cat}, and {@code turtle},
     * considering the configuration name as {@code my.property} and the property type as a {@code String}.
     * <p>
     * Otherwise, the configuration value is a single element that a comma-separated string ({@code ,}) can represent,
     * and split into multiple elements with the backslash ({@code \}) as the escape character.
     * A configuration of {@code my.property=dog,cat,turtle} results in a {@code Collection} with the elements
     * {@code dog}, {@code cat}, and {@code turtle}, considering the configuration name as
     * {@code my.property} and the {@link Converter} to convert the property type as a {@code String}.
     * <p>
     * The indexed property format has priority when both styles are found in the same configuration source. When
     * available in multiple sources, the higher ordinal source wins, like any other configuration lookup.
     *
     * @param name The configuration property name to look for in the configuration
     * @param converter The {@link Converter} to use to convert the resolved property values
     * @param collectionFactory the resulting instance of a {@code Collection} to return the property values
     * @return the resolved property values as a {@code Collection} of instances of the property type
     * @param <T> the item type
     * @param <C> the collection type
     * @throws java.lang.IllegalArgumentException if the property values cannot be converted to the specified type
     * @throws java.util.NoSuchElementException if the property isn't present in the configuration or is defined as
     *         an empty string, or the converter returns {@code null}
     *
     * @see SmallRyeConfig#getValues(String, Class)
     * @see SmallRyeConfig#getValues(String, Class IntFunction)
     * @see SmallRyeConfig#getOptionalValues(String, Class)
     * @see SmallRyeConfig#getOptionalValues(String, Class, IntFunction)
     * @see SmallRyeConfig#getOptionalValues(String, Converter, IntFunction)
     */
    public <T, C extends Collection<T>> C getValues(final String name, final Converter<T> converter,
            final IntFunction<C> collectionFactory) {
        List<String> indexedProperties = getIndexedProperties(name);
        if (indexedProperties.isEmpty()) {
            // Try legacy / MP comma separated values
            return getValue(name, newCollectionConverter(converter, collectionFactory));
        }

        // Check ordinality of indexed
        int indexedOrdinality = Integer.MIN_VALUE;
        C collection = collectionFactory.apply(indexedProperties.size());
        for (String indexedProperty : indexedProperties) {
            ConfigValue indexed = getConfigValue(indexedProperty);
            if (indexed.getConfigSourceOrdinal() >= indexedOrdinality) {
                indexedOrdinality = indexed.getConfigSourceOrdinal();
            }
            collection.add(convertValue(indexed, converter));
        }

        // Use indexed if comma separated empty or higher in ordinality
        ConfigValue commaSeparated = getConfigValue(name);
        if (commaSeparated.getValue() == null || indexedOrdinality >= commaSeparated.getConfigSourceOrdinal()) {
            return collection;
        } else {
            return getValue(name, newCollectionConverter(converter, collectionFactory));
        }
    }

    @Deprecated(forRemoval = true)
    public <T, C extends Collection<T>> C getIndexedValues(String name, Converter<T> converter,
            IntFunction<C> collectionFactory) {
        List<String> indexedProperties = getIndexedProperties(name);
        if (indexedProperties.isEmpty()) {
            throw new NoSuchElementException(ConfigMessages.msg.propertyNotFound(name));
        }
        return getIndexedValues(indexedProperties, converter, collectionFactory);
    }

    @Deprecated(forRemoval = true)
    private <T, C extends Collection<T>> C getIndexedValues(List<String> indexedProperties, Converter<T> converter,
            IntFunction<C> collectionFactory) {
        C collection = collectionFactory.apply(indexedProperties.size());
        for (String indexedProperty : indexedProperties) {
            collection.add(getValue(indexedProperty, converter));
        }
        return collection;
    }

    public List<String> getIndexedProperties(final String property) {
        Map<Integer, String> indexedProperties = configSources.getPropertyNames().indexed().get(property);
        return indexedProperties == null ? Collections.emptyList() : indexedProperties.values().stream().toList();
    }

    public List<Integer> getIndexedPropertiesIndexes(final String property) {
        Map<Integer, String> indexedProperties = configSources.getPropertyNames().indexed().get(property);
        return indexedProperties == null ? Collections.emptyList() : indexedProperties.keySet().stream().toList();
    }

    /**
     * Returns the values for the specified configuration name from the underlying
     * {@linkplain ConfigSource configuration sources}.
     * <p>
     * The lookup to the configuration will first query {@link SmallRyeConfig#getPropertyNames()} for keyed
     * properties. A keyed property uses the original property name plus an additional dotted segment to represent
     * a {@code Map} key, as {@code my.property.key}, where {@code my.property} is the property name and {@code key}
     * is the {@code Map} key. All keyed properties are queried for their values, which represent a single entry in the
     * returning {@code Map} converting both the key and value to their specified types.
     * The following configuration:
     * <ul>
     * <li>server.reasons.200=OK</li>
     * <li>server.reasons.201=CREATED</li>
     * <li>server.reasons.404=NOT_FOUND</li>
     * </ul>
     * <p>
     * Results in a {@code Map} with the entries {@code 200=OK}, {@code 201=CREATED}, and {@code 404=NOT_FOUND},
     * considering the configuration name as {@code server.reasons}, the key type as {@code Integer} and the
     * property type as a {@code String}.
     * <p>
     * Otherwise, the configuration value is a single element represented by key value pairs as
     * {@code <key1>=<value1>;<key2>=<value2>...} separated by a semicolon {@code ;} with the backslash ({@code \}) as
     * the escape character.
     * A configuration of {@code server.reasons=200=OK;201=CREATED;404=NOT_FOUND} results in a {@code Map}
     * with the entries {@code 200=OK}, {@code 201=CREATED}, and {@code 404=NOT_FOUND}, considering the configuration
     * name as {@code server.reasons}, the key type as {@code Integer} and the property type as a {@code String}.
     * <p>
     * The keyed property format has priority when both styles are found in the same configuration source. When
     * available in multiple sources, the higher ordinal source wins, like any other configuration lookup.
     *
     * @param name The configuration property name to look for in the configuration
     * @param keyClass The type into which the resolved property keys are converted
     * @param valueClass The type into which the resolved property values are converted
     * @return the resolved property values as a {@code Map} of keys of the property name and values of the property
     *         type
     * @param <K> the key type
     * @param <V> the value type
     * @throws java.lang.IllegalArgumentException if the property keys or values cannot be converted to the specified
     *         type
     * @throws java.util.NoSuchElementException if the property isn't present in the configuration or is defined as
     *         an empty string, or the converter returns {@code null}
     *
     * @see SmallRyeConfig#getValues(String, Converter, Converter)
     * @see SmallRyeConfig#getValues(String, Converter, Converter, IntFunction)
     * @see SmallRyeConfig#getOptionalValues(String, Class)
     * @see SmallRyeConfig#getOptionalValues(String, Class, IntFunction)
     * @see SmallRyeConfig#getOptionalValues(String, Converter, IntFunction)
     */
    public <K, V> Map<K, V> getValues(final String name, final Class<K> keyClass, final Class<V> valueClass) {
        return getValues(name, requireConverter(keyClass), requireConverter(valueClass));
    }

    /**
     * Returns the values for the specified configuration name from the underlying
     * {@linkplain ConfigSource configuration sources}.
     * <p>
     * The lookup to the configuration will first query {@link SmallRyeConfig#getPropertyNames()} for keyed
     * properties. A keyed property uses the original property name plus an additional dotted segment to represent
     * a {@code Map} key, as {@code my.property.key}, where {@code my.property} is the property name and {@code key}
     * is the {@code Map} key. All keyed properties are queried for their values, which represent a single entry in the
     * returning {@code Map} converting both the key and value using the specified {@linkplain Converters Converters}.
     * The following configuration:
     * <ul>
     * <li>server.reasons.200=OK</li>
     * <li>server.reasons.201=CREATED</li>
     * <li>server.reasons.404=NOT_FOUND</li>
     * </ul>
     * <p>
     * Results in a {@code Map} with the entries {@code 200=OK}, {@code 201=CREATED}, and {@code 404=NOT_FOUND},
     * considering the configuration name as {@code server.reasons} and {@linkplain Converters Converters} to
     * convert the key type as an {@code Integer} and the property type as a {@code String}.
     * <p>
     * Otherwise, the configuration value is a single element represented by key value pairs as
     * {@code <key1>=<value1>;<key2>=<value2>...} separated by a semicolon {@code ;} with the backslash ({@code \}) as
     * the escape character.
     * A configuration of {@code server.reasons=200=OK;201=CREATED;404=NOT_FOUND} results in a {@code Map}
     * with the entries {@code 200=OK}, {@code 201=CREATED}, and {@code 404=NOT_FOUND}, considering the configuration
     * name as {@code server.reasons} and {@linkplain Converters Converters} to convert the key type as an
     * {@code Integer} and the property type as a {@code String}.
     * <p>
     * The keyed property format has priority when both styles are found in the same configuration source. When
     * available in multiple sources, the higher ordinal source wins, like any other configuration lookup.
     *
     * @param name The configuration property name to look for in the configuration
     * @param keyConverter The {@link Converter} to use to convert the resolved property keys
     * @param valueConverter The {@link Converter} to use to convert the resolved property values
     * @return the resolved property values as a {@code Map} of keys of the property name and values of the property
     *         type
     * @param <K> the key type
     * @param <V> the value type
     * @throws java.lang.IllegalArgumentException if the property keys or values cannot be converted to the specified
     *         type
     * @throws java.util.NoSuchElementException if the property isn't present in the configuration or is defined as
     *         an empty string, or the converter returns {@code null}
     *
     * @see SmallRyeConfig#getValues(String, Class, Class)
     * @see SmallRyeConfig#getValues(String, Converter, Converter, IntFunction)
     * @see SmallRyeConfig#getOptionalValues(String, Class)
     * @see SmallRyeConfig#getOptionalValues(String, Class, IntFunction)
     * @see SmallRyeConfig#getOptionalValues(String, Converter, IntFunction)
     */
    public <K, V> Map<K, V> getValues(
            final String name,
            final Converter<K> keyConverter,
            final Converter<V> valueConverter) {
        return getValues(name, keyConverter, valueConverter, HashMap::new);
    }

    /**
     * Returns the values for the specified configuration name from the underlying
     * {@linkplain ConfigSource configuration sources}.
     * <p>
     * The lookup to the configuration will first query {@link SmallRyeConfig#getPropertyNames()} for keyed
     * properties. A keyed property uses the original property name plus an additional dotted segment to represent
     * a {@code Map} key, as {@code my.property.key}, where {@code my.property} is the property name and {@code key}
     * is the {@code Map} key. All keyed properties are queried for their values, which represent a single entry in the
     * returning {@code Map} converting both the key and value using the specified {@linkplain Converters Converters}.
     * The following configuration:
     * <ul>
     * <li>server.reasons.200=OK</li>
     * <li>server.reasons.201=CREATED</li>
     * <li>server.reasons.404=NOT_FOUND</li>
     * </ul>
     * <p>
     * Results in a {@code Map} with the entries {@code 200=OK}, {@code 201=CREATED}, and {@code 404=NOT_FOUND},
     * considering the configuration name as {@code server.reasons} and {@linkplain Converters Converters} to
     * convert the key type as an {@code Integer} and the property type as a {@code String}.
     * <p>
     * Otherwise, the configuration value is a single element represented by key value pairs as
     * {@code <key1>=<value1>;<key2>=<value2>...} separated by a semicolon {@code ;} with the backslash ({@code \}) as
     * the escape character.
     * A configuration of {@code server.reasons=200=OK;201=CREATED;404=NOT_FOUND} results in a {@code Map}
     * with the entries {@code 200=OK}, {@code 201=CREATED}, and {@code 404=NOT_FOUND}, considering the configuration
     * name as {@code server.reasons} and {@linkplain Converters Converters} to convert the key type as an
     * {@code Integer} and the property type as a {@code String}.
     * <p>
     * The keyed property format has priority when both styles are found in the same configuration source. When
     * available in multiple sources, the higher ordinal source wins, like any other configuration lookup.
     *
     * @param name The configuration property name to look for in the configuration
     * @param keyConverter The {@link Converter} to use to convert the resolved property keys
     * @param valueConverter The {@link Converter} to use to convert the resolved property values
     * @param mapFactory the resulting instance of a {@code Map} to return the property keys and values
     * @return the resolved property values as a {@code Map} of keys of the property name and values of the property
     *         type
     * @param <K> the key type
     * @param <V> the value type
     * @throws java.lang.IllegalArgumentException if the property keys or values cannot be converted to the specified
     *         type
     * @throws java.util.NoSuchElementException if the property isn't present in the configuration or is defined as
     *         an empty string, or the converter returns {@code null}
     *
     * @see SmallRyeConfig#getValues(String, Class, Class)
     * @see SmallRyeConfig#getValues(String, Converter, Converter)
     * @see SmallRyeConfig#getOptionalValues(String, Class)
     * @see SmallRyeConfig#getOptionalValues(String, Class, IntFunction)
     * @see SmallRyeConfig#getOptionalValues(String, Converter, IntFunction)
     */
    public <K, V> Map<K, V> getValues(
            final String name,
            final Converter<K> keyConverter,
            final Converter<V> valueConverter,
            final IntFunction<Map<K, V>> mapFactory) {

        Map<String, String> keys = getMapKeys(name);
        if (keys.isEmpty()) {
            // Try legacy MapConverter
            return getValue(name, newMapConverter(keyConverter, valueConverter, mapFactory));
        }

        return getMapValues(keys, keyConverter, valueConverter, mapFactory);
    }

    /**
     * Returns the values for the specified configuration name from the underlying
     * {@linkplain ConfigSource configuration sources}.
     * <p>
     * The lookup to the configuration will first query {@link SmallRyeConfig#getPropertyNames()} for keyed indexed
     * properties. A keyed indexed property uses the original property name plus an additional dotted segment to
     * represent a {@code Map} key followed by square brackets and an index in between, as {@code my.property.key[0]},
     * where {@code my.property} is the property name, {@code key} is the {@code Map} key and {code [0]} the index of
     * the {@code Collection} element. All keyed indexed properties are queried for their value, which represent
     * a single entry in the returning {@code Map}, and single element in the {@code Collection} value, converting both
     * the key and value to their specified types. The following configuration:
     * <ul>
     * <li>server.env.prod[0]=alpha</li>
     * <li>server.env.prod[1]=beta</li>
     * <li>server.env.dev[0]=local</li>
     * </ul>
     * <p>
     * Results in a {@code Map} with the entry key {@code prod} and entry value {@code Collection} with the values
     * {@code alpha} and {@code beta}, and the entry key {@code dev} and entry value {@code Collection} with the value
     * {@code local}, considering the configuration name as {@code server.env}, the key type as a {@code String}, and
     * the property type as a {@code String}.
     * <p>
     * Otherwise, the configuration value is a single element represented by key value pairs as
     * {@code <key1>=<value1>;<key2>=<value2>...} separated by a semicolon {@code ;} and value as a comma-separated
     * string ({@code ,}) can represent, and split into multiple elements with the backslash ({@code \}) as the
     * escape character. A configuration of {@code server.env=prod=alpha,beta;dev=local} results in a {@code Map} with
     * the entry key {@code prod} and entry value {@code Collection} with the values {@code alpha} and {@code beta},
