// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.

package org.jetbrains.kotlin.idea.base.compilerPreferences.configuration;

import com.intellij.compiler.server.BuildManager;
import com.intellij.icons.AllIcons;
import com.intellij.jarRepository.JarRepositoryManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SearchableConfigurable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.RootsChangeRescanningInfo;
import com.intellij.openapi.ui.*;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.MutableCollectionComboBoxModel;
import com.intellij.ui.PopupMenuListenerAdapter;
import com.intellij.ui.RawCommandLineEditor;
import com.intellij.ui.dsl.listCellRenderer.BuilderKt;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.VersionComparatorUtil;
import com.intellij.util.ui.ThreeStateCheckBox;
import com.intellij.util.ui.UIUtil;
import kotlin.KotlinVersion;
import kotlin.collections.ArraysKt;
import kotlin.collections.CollectionsKt;
import kotlin.enums.EnumEntries;
import kotlin.jvm.functions.Function0;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.maven.utils.library.RepositoryLibraryDescription;
import org.jetbrains.kotlin.cli.common.arguments.*;
import org.jetbrains.kotlin.config.*;
import org.jetbrains.kotlin.idea.PluginStartupApplicationService;
import org.jetbrains.kotlin.idea.base.compilerPreferences.KotlinBaseCompilerConfigurationUiBundle;
import org.jetbrains.kotlin.idea.base.plugin.artifacts.KotlinArtifactConstants;
import org.jetbrains.kotlin.idea.base.util.KotlinPlatformUtils;
import org.jetbrains.kotlin.idea.base.util.ProjectStructureUtils;
import org.jetbrains.kotlin.idea.compiler.configuration.*;
import org.jetbrains.kotlin.idea.facet.KotlinFacet;
import org.jetbrains.kotlin.idea.util.application.ApplicationUtilsKt;
import org.jetbrains.kotlin.platform.IdePlatformKind;
import org.jetbrains.kotlin.platform.PlatformUtilKt;
import org.jetbrains.kotlin.platform.TargetPlatform;
import org.jetbrains.kotlin.platform.impl.JsIdePlatformUtil;
import org.jetbrains.kotlin.platform.impl.JvmIdePlatformKind;
import org.jetbrains.kotlin.platform.jvm.JdkPlatform;

import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import java.util.*;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.intellij.openapi.options.Configurable.isCheckboxModified;
import static com.intellij.openapi.options.Configurable.isFieldModified;
import static org.jetbrains.kotlin.idea.base.compilerPreferences.facet.DescriptionListCellRendererKt.createDescriptionAwareRenderer;

public class KotlinCompilerConfigurableTab implements SearchableConfigurable {
    private static final Logger LOG = Logger.getInstance(KotlinCompilerConfigurableTab.class);
    private static final Map<String, @NlsSafe String> moduleKindDescriptions = new LinkedHashMap<>();
    private static final Map<String, @NlsSafe String> sourceMapSourceEmbeddingDescriptions = new LinkedHashMap<>();
    private static final int MAX_WARNING_SIZE = 75;

    static {
        moduleKindDescriptions.put(K2JsArgumentConstants.MODULE_PLAIN,
                                   KotlinBaseCompilerConfigurationUiBundle.message("configuration.description.plain.put.to.global.scope"));
        moduleKindDescriptions.put(K2JsArgumentConstants.MODULE_AMD, KotlinBaseCompilerConfigurationUiBundle.message("configuration.description.amd"));
        moduleKindDescriptions.put(K2JsArgumentConstants.MODULE_COMMONJS, KotlinBaseCompilerConfigurationUiBundle.message("configuration.description.commonjs"));
        moduleKindDescriptions.put(K2JsArgumentConstants.MODULE_UMD, KotlinBaseCompilerConfigurationUiBundle.message(
                "configuration.description.umd.detect.amd.or.commonjs.if.available.fallback.to.plain"));

        sourceMapSourceEmbeddingDescriptions
                .put(K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_NEVER, KotlinBaseCompilerConfigurationUiBundle.message("configuration.description.never"));
        sourceMapSourceEmbeddingDescriptions
                .put(K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_ALWAYS, KotlinBaseCompilerConfigurationUiBundle.message("configuration.description.always"));
        sourceMapSourceEmbeddingDescriptions.put(K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_INLINING, KotlinBaseCompilerConfigurationUiBundle.message(
                "configuration.description.when.inlining.a.function.from.other.module.with.embedded.sources"));
    }

    private final @Nullable KotlinCompilerWorkspaceSettings compilerWorkspaceSettings;
    private final Project project;
    private final boolean isProjectSettings;
    private CommonCompilerArguments commonCompilerArguments;
    private K2JSCompilerArguments k2jsCompilerArguments;
    private K2JVMCompilerArguments k2jvmCompilerArguments;
    private CompilerSettings compilerSettings;
    private final @Nullable JpsPluginSettings jpsPluginSettings;
    private JPanel contentPane;
    private ThreeStateCheckBox reportWarningsCheckBox;
    private RawCommandLineEditor additionalArgsOptionsField;
    private JLabel additionalArgsLabel;
    private ThreeStateCheckBox generateSourceMapsCheckBox;
    private JLabel labelForOutputDirectory;
    private TextFieldWithBrowseButton outputDirectory;
    private ThreeStateCheckBox copyRuntimeFilesCheckBox;
    private ThreeStateCheckBox keepAliveCheckBox;
    private JCheckBox enableIncrementalCompilationForJvmCheckBox;
    private JCheckBox enableIncrementalCompilationForJsCheckBox;
    private JComboBox<String> moduleKindComboBox;
    private JPanel k2jvmPanel;
    private JPanel k2jsPanel;
    private JComboBox<String> jvmVersionComboBox;
    private JPanel kotlinJpsPluginVersionPanel;
    private JComboBox<JpsVersionItem> kotlinJpsPluginVersionComboBox;
    private ComboBoxModelWithPossiblyDisabledItems jpsPluginComboBoxModel;
    private JpsVersionItem defaultJpsVersionItem;
    private JComboBox<VersionView> languageVersionComboBox;
    private JComboBox<VersionView> apiVersionComboBox;
    private JLabel warningLabel;
    private JTextField sourceMapPrefix;
    private JComboBox<String> sourceMapEmbedSources;
    private boolean isEnabled = true;

    private @Nullable Disposable validatorsDisposable = null;

    public KotlinCompilerConfigurableTab(
            Project project,
            @NotNull CommonCompilerArguments commonCompilerArguments,
            @NotNull K2JSCompilerArguments k2jsCompilerArguments,
            @NotNull K2JVMCompilerArguments k2jvmCompilerArguments,
            @NotNull CompilerSettings compilerSettings,
            @Nullable KotlinCompilerWorkspaceSettings compilerWorkspaceSettings,
            boolean isProjectSettings,
            boolean isMultiEditor
    ) {
        this.project = project;
        this.commonCompilerArguments = commonCompilerArguments;
        this.k2jsCompilerArguments = k2jsCompilerArguments;
        this.compilerSettings = compilerSettings;
        this.jpsPluginSettings = Optional.ofNullable(isProjectSettings ? KotlinJpsPluginSettings.getInstance(project) : null)
                .map(KotlinJpsPluginSettings::getSettings)
                .map(FreezableKt::unfrozen)
                .orElse(null);
        this.compilerWorkspaceSettings = compilerWorkspaceSettings;
        this.k2jvmCompilerArguments = k2jvmCompilerArguments;
        this.isProjectSettings = isProjectSettings;

        warningLabel.setIcon(AllIcons.General.WarningDialog);

        additionalArgsOptionsField.attachLabel(additionalArgsLabel);
        if (isJpsCompilerVisible()) {
            kotlinJpsPluginVersionComboBox.addActionListener(
                    e -> onLanguageLevelChanged(getSelectedKotlinJpsPluginVersionView()));
        }

        fillVersions();

        if (KotlinPlatformUtils.isCidr()) {
            keepAliveCheckBox.setVisible(false);
            k2jvmPanel.setVisible(false);
            k2jsPanel.setVisible(false);
        } else {
            initializeNonCidrSettings(isMultiEditor);
        }

        reportWarningsCheckBox.setThirdStateEnabled(isMultiEditor);

        if (isProjectSettings) {
            List<String> modulesOverridingProjectSettings = ArraysKt.mapNotNull(
                    ModuleManager.getInstance(project).getModules(),
                    module -> {
                        KotlinFacet facet = KotlinFacet.Companion.get(module);
                        if (facet == null) return null;
                        IKotlinFacetSettings facetSettings = facet.getConfiguration().getSettings();
                        if (facetSettings.getUseProjectSettings()) return null;
                        return module.getName();
                    }
            );
            CollectionsKt.sort(modulesOverridingProjectSettings);
            if (!modulesOverridingProjectSettings.isEmpty()) {
                warningLabel.setVisible(true);
                warningLabel.setText(buildOverridingModulesWarning(modulesOverridingProjectSettings));
            }
        }
    }

    @SuppressWarnings("unused") // Empty constructor fixes 'Extension should not have constructor with parameters (except Project)'
    public KotlinCompilerConfigurableTab(Project project) {
        this(project,
             FreezableKt.unfrozen(KotlinCommonCompilerArgumentsHolder.getInstance(project).getSettings()),
             FreezableKt.unfrozen(Kotlin2JsCompilerArgumentsHolder.getInstance(project).getSettings()),
             FreezableKt.unfrozen(Kotlin2JvmCompilerArgumentsHolder.getInstance(project).getSettings()),
             FreezableKt.unfrozen(KotlinCompilerSettings.getInstance(project).getSettings()),
             KotlinCompilerWorkspaceSettings.getInstance(project),
             true,
             false);
    }

    private void initializeNonCidrSettings(boolean isMultiEditor) {
        setupFileChooser(labelForOutputDirectory, outputDirectory,
                         KotlinBaseCompilerConfigurationUiBundle.message("configuration.title.choose.output.directory"),
                         false);

        fillModuleKindList();
        fillSourceMapSourceEmbeddingList();
        fillJvmVersionList();

        generateSourceMapsCheckBox.setThirdStateEnabled(isMultiEditor);
        generateSourceMapsCheckBox.addActionListener(event -> sourceMapPrefix.setEnabled(generateSourceMapsCheckBox.isSelected()));

        copyRuntimeFilesCheckBox.setThirdStateEnabled(isMultiEditor);
        keepAliveCheckBox.setThirdStateEnabled(isMultiEditor);

        if (compilerWorkspaceSettings == null) {
            keepAliveCheckBox.setVisible(false);
            k2jvmPanel.setVisible(false);
            enableIncrementalCompilationForJsCheckBox.setVisible(false);
        }

        updateOutputDirEnabled();
    }

    private static int calculateNameCountToShowInWarning(List<String> allNames) {
        int lengthSoFar = 0;
        int size = allNames.size();
        for (int i = 0; i < size; i++) {
            lengthSoFar = (i > 0 ? lengthSoFar + 2 : 0) + allNames.get(i).length();
            if (lengthSoFar > MAX_WARNING_SIZE) return i;
        }
        return size;
    }

    private static @NotNull @NlsSafe String buildOverridingModulesWarning(List<String> modulesOverridingProjectSettings) {
        int nameCountToShow = calculateNameCountToShowInWarning(modulesOverridingProjectSettings);
        int allNamesCount = modulesOverridingProjectSettings.size();
        if (nameCountToShow == 0) {
            return KotlinBaseCompilerConfigurationUiBundle.message("configuration.warning.text.modules.override.project.settings", String.valueOf(allNamesCount));
        }

        StringBuilder builder = new StringBuilder();
        builder.append("<html>");
        builder.append(KotlinBaseCompilerConfigurationUiBundle.message("configuration.warning.text.following.modules.override.project.settings")).append(" ");
        CollectionsKt.joinTo(
                modulesOverridingProjectSettings.subList(0, nameCountToShow),
                builder,
                ", ",
                "",
                "",
                -1,
                "",
                new Function1<>() {
                    @Override
                    public CharSequence invoke(String s) {
                        return "<strong>" + s + "</strong>";
                    }
                }
        );
        if (nameCountToShow < allNamesCount) {
            builder.append(" ").append(KotlinBaseCompilerConfigurationUiBundle.message("configuration.text.and")).append(" ").append(allNamesCount - nameCountToShow)
                    .append(" ").append(KotlinBaseCompilerConfigurationUiBundle.message("configuration.text.other.s"));
        }
        return builder.toString();
    }

    @Nls
    private static @NotNull
    String getModuleKindDescription(@Nullable String moduleKind) {
        if (moduleKind == null) return "";
        String result = moduleKindDescriptions.get(moduleKind);
        assert result != null : "Module kind " + moduleKind + " was not added to combobox, therefore it should not be here";
        return result;
    }

    @Nls
    private static @NotNull
    String getSourceMapSourceEmbeddingDescription(@Nullable String sourceMapSourceEmbeddingId) {
        if (sourceMapSourceEmbeddingId == null) return "";
        String result = sourceMapSourceEmbeddingDescriptions.get(sourceMapSourceEmbeddingId);
        assert result != null : "Source map source embedding mode " + sourceMapSourceEmbeddingId +
                                " was not added to combobox, therefore it should not be here";
        return result;
    }

    private static @NotNull @NlsSafe String getModuleKindOrDefault(@Nullable String moduleKindId) {
        if (moduleKindId == null) {
            moduleKindId = K2JsArgumentConstants.MODULE_PLAIN;
        }
        return moduleKindId;
    }

    private static @NotNull @NlsSafe String getSourceMapSourceEmbeddingOrDefault(@Nullable String sourceMapSourceEmbeddingId) {
        if (sourceMapSourceEmbeddingId == null) {
            sourceMapSourceEmbeddingId = K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_INLINING;
        }
        return sourceMapSourceEmbeddingId;
    }

    private static @NlsSafe String getJvmVersionOrDefault(@Nullable String jvmVersion) {
        return jvmVersion != null ? jvmVersion : JvmTarget.DEFAULT.getDescription();
    }

    private static void setupFileChooser(
            @NotNull JLabel label,
            @NotNull TextFieldWithBrowseButton fileChooser,
            @NotNull @NlsContexts.DialogTitle String title,
            boolean forFiles
    ) {
        label.setLabelFor(fileChooser);
        var descriptor = new FileChooserDescriptor(forFiles, !forFiles, false, false, false, false).withTitle(title);
        fileChooser.addBrowseFolderListener(null, descriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);
    }

    private static boolean isBrowseFieldModified(@NotNull TextFieldWithBrowseButton chooser, @NotNull String currentValue) {
        return !StringUtil.equals(chooser.getText(), currentValue);
    }

    private void updateOutputDirEnabled() {
        if (isEnabled && copyRuntimeFilesCheckBox != null) {
            outputDirectory.setEnabled(copyRuntimeFilesCheckBox.isSelected());
            labelForOutputDirectory.setEnabled(copyRuntimeFilesCheckBox.isSelected());
        }
    }

    private static boolean isLessOrEqual(LanguageOrApiVersion version, LanguageOrApiVersion upperBound) {
        return VersionComparatorUtil.compare(version.getVersionString(), upperBound.getVersionString()) <= 0;
    }

    public void onLanguageLevelChanged(@Nullable VersionView languageLevel) {
        if (languageLevel == null) return;
        restrictAPIVersions(languageLevel);
    }

    private void restrictAPIVersions(VersionView upperBoundView) {
        VersionView selectedAPIView = getSelectedAPIVersionView();
        LanguageOrApiVersion selectedAPIVersion = selectedAPIView.getVersion();
        LanguageOrApiVersion upperBound = upperBoundView.getVersion();
        EnumEntries<LanguageVersion> languageVersions = LanguageVersion.getEntries();
        List<VersionView> permittedAPIVersions = new ArrayList<>(languageVersions.size());

        final VersionView latestStable = getLatestStableVersion();

        int index = 0;
        int latestStableIndex = languageVersions.size();
        for (LanguageVersion version : languageVersions) {
            if (index > latestStableIndex) {
                break;
            }
            ApiVersion apiVersion = ApiVersion.createByLanguageVersion(version);
            if (!isLessOrEqual(apiVersion, upperBound) && (index < latestStableIndex)) {
                latestStableIndex = index;
            }
            if (!apiVersion.isUnsupported()) {
                permittedAPIVersions.add(new VersionView.Specific(version));
            }
            index++;
        }

        if (isLessOrEqual(latestStable.getVersion(), upperBound) && !permittedAPIVersions.contains(latestStable)) {
            permittedAPIVersions.add(latestStable);
        }

        apiVersionComboBox.setModel(new MutableCollectionComboBoxModel<>(permittedAPIVersions));
        languageVersionComboBox.setModel(new MutableCollectionComboBoxModel<>(permittedAPIVersions));

        VersionView selectedItem =
                VersionComparatorUtil.compare(selectedAPIVersion.getVersionString(), upperBound.getVersionString()) <= 0
                ? selectedAPIView
                : upperBoundView;
        apiVersionComboBox.setSelectedItem(selectedItem);
        if (isJpsCompilerVisible()) {
            languageVersionComboBox.setSelectedItem(selectedItem);
        }
    }

    private void fillJvmVersionList() {
        for (TargetPlatform jvm : JvmIdePlatformKind.INSTANCE.getPlatforms()) {
            JvmTarget jvmTarget = PlatformUtilKt.subplatformsOfType(jvm, JdkPlatform.class).get(0).getTargetVersion();
            @NlsSafe String description = jvmTarget.getDescription();
            if (jvmTarget == JvmTarget.JVM_1_6) {
                description += " " + KotlinBaseCompilerConfigurationUiBundle.message("deprecated.jvm.version");
            }

            jvmVersionComboBox.addItem(description);
        }
    }

    private void fetchAvailableJpsCompilersAsync(Consumer<? super @NlsSafe @Nullable Collection<IdeKotlinVersion>> onFinish) {
        JarRepositoryManager.getAvailableVersions(project, RepositoryLibraryDescription.findDescription(
                        KotlinArtifactConstants.KOTLIN_MAVEN_GROUP_ID, KotlinArtifactConstants.KOTLIN_DIST_FOR_JPS_META_ARTIFACT_ID))
                .onProcessed(distVersions -> {
                    if (distVersions == null) {
                        onFinish.accept(null);
                        return;
                    }
                    JarRepositoryManager.getAvailableVersions(project, RepositoryLibraryDescription.findDescription(
                                    KotlinArtifactConstants.KOTLIN_MAVEN_GROUP_ID,
                                    KotlinArtifactConstants.KOTLIN_JPS_PLUGIN_PLUGIN_ARTIFACT_ID))
                            .onProcessed(jpsClassPathVersions -> {
                                if (jpsClassPathVersions == null) {
                                    onFinish.accept(null);
                                    return;
                                }

                                KotlinVersion min = KotlinJpsPluginSettings.getJpsMinimumSupportedVersion();
                                KotlinVersion max = KotlinJpsPluginSettings.getJpsMaximumSupportedVersion();
                                HashSet<IdeKotlinVersion> ideKotlinVersions = new HashSet<>();
                                for (String version : distVersions) {
                                    if (!jpsClassPathVersions.contains(version)) continue;

                                    IdeKotlinVersion parsedVersion = IdeKotlinVersion.opt(version);
                                    if (parsedVersion != null) {
                                        KotlinVersion parsedKotlinVersion = parsedVersion.getKotlinVersion();
                                        if (parsedKotlinVersion.compareTo(min) >= 0 && parsedKotlinVersion.compareTo(max) <= 0) {
                                            ideKotlinVersions.add(parsedVersion);
                                        }
                                    }
                                }

                                onFinish.accept(ideKotlinVersions);
                            });
                });
    }

    private boolean isJpsCompilerVisible() {
        return isProjectSettings && jpsPluginSettings != null;
    }

    private void fillVersions() {
        if (isJpsCompilerVisible()) {
            defaultJpsVersionItem = JpsVersionItem.createFromRawVersion(
                    KotlinJpsPluginSettingsKt.getVersionWithFallback(jpsPluginSettings)
            );

            kotlinJpsPluginVersionComboBox.addItem(defaultJpsVersionItem);

            IdeKotlinVersion bundledVersion = KotlinJpsPluginSettings.getBundledVersion();
            IdeKotlinVersion defaultVersion = defaultJpsVersionItem.getVersion();
            Integer compare = defaultVersion != null ? defaultVersion.compareTo(bundledVersion) : null;
            if (compare == null || compare > 0) {
                jpsPluginComboBoxModel.add(new JpsVersionItem(bundledVersion));
            } else if (compare < 0) {
                jpsPluginComboBoxModel.add(0, new JpsVersionItem(bundledVersion));
            }

            JpsVersionItem loadingItem = JpsVersionItem.createLabel(KotlinBaseCompilerConfigurationUiBundle.message("loading.available.versions.from.maven"));
            kotlinJpsPluginVersionComboBox.addItem(loadingItem);
            PopupMenuListenerAdapter popupListener = new PopupMenuListenerAdapter() {
                @Override
                public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                    kotlinJpsPluginVersionComboBox.removePopupMenuListener(this);
                    fetchAvailableJpsCompilersAsync(
                            availableVersions -> {
                                kotlinJpsPluginVersionComboBox.removeItem(loadingItem);
                                if (availableVersions == null) {
                                    kotlinJpsPluginVersionComboBox.addItem(
                                            JpsVersionItem.createLabel(
                                                    KotlinBaseCompilerConfigurationUiBundle.message("failed.fetching.all.available.versions.from.maven")
                                            )
                                    );
                                } else {
                                    SortedSet<IdeKotlinVersion> newItems = new TreeSet<>(availableVersions);
                                    for (JpsVersionItem item : jpsPluginComboBoxModel.getItems()) {
                                        IdeKotlinVersion ideKotlinVersion = item.getVersion();
                                        if (ideKotlinVersion != null) {
                                            newItems.add(ideKotlinVersion);
                                        }
                                    }

                                    Object selectedItem = jpsPluginComboBoxModel.getSelectedItem();
                                    jpsPluginComboBoxModel.update(
                                            ContainerUtil.reverse(ContainerUtil.map(newItems, it -> new JpsVersionItem(it))));
                                    kotlinJpsPluginVersionComboBox.setSelectedItem(selectedItem);
                                }

                                if (kotlinJpsPluginVersionComboBox.isPopupVisible()) {
                                    kotlinJpsPluginVersionComboBox.hidePopup();
                                    kotlinJpsPluginVersionComboBox.showPopup();
                                }
                            });
                }
            };

            kotlinJpsPluginVersionComboBox.addPopupMenuListener(popupListener);
        } else {
            kotlinJpsPluginVersionPanel.setVisible(false);
        }

        VersionView latestStable = getLatestStableVersion();
        int index = 0;
        EnumEntries<LanguageVersion> languageVersions = LanguageVersion.getEntries();
        int latestStableIndex = languageVersions.size();
        for (LanguageVersion languageVersion : languageVersions) {
            if (index > latestStableIndex) break;
            if (!isLessOrEqual(languageVersion, latestStable.getVersion()) && (index < latestStableIndex)) {
                latestStableIndex = index;
            }

            if (!languageVersion.isStable()) {
                continue;
            }

            ApiVersion apiVersion = ApiVersion.createByLanguageVersion(languageVersion);

            if (!apiVersion.isUnsupported()) {
                apiVersionComboBox.addItem(new VersionView.Specific(languageVersion));
            }
            if (!languageVersion.isUnsupported()) {
                languageVersionComboBox.addItem(new VersionView.Specific(languageVersion));
            }
            index++;
        }

        languageVersionComboBox.setRenderer(createDescriptionAwareRenderer());
        kotlinJpsPluginVersionComboBox.setRenderer(createDescriptionAwareRenderer());
        apiVersionComboBox.setRenderer(createDescriptionAwareRenderer());
    }

    private static VersionView latestStableVersion = null;

    private static VersionView getLatestStableVersion() {
        VersionView latestStable = latestStableVersion;
        if (latestStable != null) {
            return latestStable;
        }

        LanguageVersion bundledLanguageVersion = KotlinJpsPluginSettings.getBundledVersion().getLanguageVersion();
        latestStable = VersionView.LatestStable.INSTANCE;

        // workaround to avoid cases when Kotlin plugin bundles the latest compiler with effectively NOT STABLE version.
        // Actually, the latest stable version is bundled in jps
        for (LanguageVersion languageVersion : LanguageVersion.getEntries()) {
            if (languageVersion.compareTo(bundledLanguageVersion) <= 0) {
                latestStable = VersionView.Companion.deserialize(languageVersion.getVersionString(), false);
            } else {
                break;
            }
        }

        latestStableVersion = latestStable;
        return latestStable;
    }

    public void setTargetPlatform(@Nullable IdePlatformKind targetPlatform) {
        k2jsPanel.setVisible(JsIdePlatformUtil.isJavaScript(targetPlatform));
    }

    private void fillModuleKindList() {
        for (@Nls String moduleKind : moduleKindDescriptions.keySet()) {
            moduleKindComboBox.addItem(moduleKind);
        }

        moduleKindComboBox.setRenderer(BuilderKt.textListCellRenderer("", o -> getModuleKindDescription(o)));
    }

    private void fillSourceMapSourceEmbeddingList() {
        for (@Nls String moduleKind : sourceMapSourceEmbeddingDescriptions.keySet()) {
            sourceMapEmbedSources.addItem(moduleKind);
        }

        sourceMapEmbedSources.setRenderer(BuilderKt.textListCellRenderer("", o -> getSourceMapSourceEmbeddingDescription(o)));
    }

    @Override
    public @NotNull String getId() {
        return "project.kotlinCompiler";
    }

    @Override
    public @Nullable JComponent createComponent() {
        if (validatorsDisposable != null) {
            LOG.error(new IllegalStateException("validatorsDisposable is not null. Disposing and rewriting it."));
            Disposer.dispose(validatorsDisposable);
        }
        validatorsDisposable = Disposer.newDisposable();
        createVersionValidator(languageVersionComboBox, "configuration.warning.text.language.version.unsupported", validatorsDisposable);
        createVersionValidator(apiVersionComboBox, "configuration.warning.text.api.version.unsupported", validatorsDisposable);

        return contentPane;
    }

    @Override
    public boolean isModified() {
        return isCheckboxModified(reportWarningsCheckBox, !commonCompilerArguments.getSuppressWarnings()) ||
               !getSelectedLanguageVersionView().equals(KotlinFacetSettingsKt.getLanguageVersionView(commonCompilerArguments)) ||
               !getSelectedAPIVersionView().equals(KotlinFacetSettingsKt.getApiVersionView(commonCompilerArguments)) ||
               jpsPluginSettings != null &&
               !getSelectedKotlinJpsPluginVersion().equals(KotlinJpsPluginSettingsKt.getVersionWithFallback(jpsPluginSettings)) ||
               !additionalArgsOptionsField.getText().equals(compilerSettings.getAdditionalArguments()) ||
               isCheckboxModified(copyRuntimeFilesCheckBox, compilerSettings.getCopyJsLibraryFiles()) ||
               isBrowseFieldModified(outputDirectory, compilerSettings.getOutputDirectoryForJsLibraryFiles()) ||

               (compilerWorkspaceSettings != null &&
                (isCheckboxModified(enableIncrementalCompilationForJvmCheckBox, compilerWorkspaceSettings.getPreciseIncrementalEnabled()) ||
                 isCheckboxModified(enableIncrementalCompilationForJsCheckBox, compilerWorkspaceSettings.getIncrementalCompilationForJsEnabled()) ||
                 isCheckboxModified(keepAliveCheckBox, compilerWorkspaceSettings.getEnableDaemon()))) ||

               isCheckboxModified(generateSourceMapsCheckBox, k2jsCompilerArguments.getSourceMap()) ||
               !getSelectedModuleKind().equals(getModuleKindOrDefault(k2jsCompilerArguments.getModuleKind())) ||
               isFieldModified(sourceMapPrefix, StringUtil.notNullize(k2jsCompilerArguments.getSourceMapPrefix())) ||
               !getSelectedSourceMapSourceEmbedding().equals(
                       getSourceMapSourceEmbeddingOrDefault(k2jsCompilerArguments.getSourceMapEmbedSources())) ||
               !getSelectedJvmVersion().equals(getJvmVersionOrDefault(k2jvmCompilerArguments.getJvmTarget()));
    }

    private @NotNull String getSelectedModuleKind() {
        return getModuleKindOrDefault((String) moduleKindComboBox.getSelectedItem());
    }

    private String getSelectedSourceMapSourceEmbedding() {
        return getSourceMapSourceEmbeddingOrDefault((String) sourceMapEmbedSources.getSelectedItem());
    }

    public @NotNull String getSelectedJvmVersion() {
        return getJvmVersionOrDefault((String) jvmVersionComboBox.getSelectedItem());
    }

    public @NotNull VersionView getSelectedLanguageVersionView() {
        Object item = languageVersionComboBox.getSelectedItem();
        return item != null ? (VersionView) item : getLatestStableVersion();
    }

    private @NotNull VersionView getSelectedAPIVersionView() {
        Object item = apiVersionComboBox.getSelectedItem();
        return item != null ? (VersionView) item : getLatestStableVersion();
    }

    public VersionView getSelectedKotlinJpsPluginVersionView() {
        JpsVersionItem selectedItem = (JpsVersionItem) kotlinJpsPluginVersionComboBox.getSelectedItem();
        IdeKotlinVersion version = selectedItem != null ? selectedItem.getVersion() : null;
        LanguageVersion languageVersion = version != null ? LanguageVersion.fromFullVersionString(version.toString()) : null;
        VersionView versionView;
        if (languageVersion != null) {
            versionView = new VersionView.Specific(languageVersion);
        } else {
            String compilerVersionFromSettings = KotlinJpsPluginSettings.getInstance(project).getSettings().getVersion();
            versionView = VersionView.Companion.deserialize(compilerVersionFromSettings, /*isAutoAdvance =*/ false);
        }
        return versionView;
    }

    private @NotNull String getSelectedKotlinJpsPluginVersion() {
        JpsVersionItem item = (JpsVersionItem) kotlinJpsPluginVersionComboBox.getSelectedItem();
        return normalizeKotlinJpsPluginVersion(item != null ? item.getRawVersion() : null);
    }

