// 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.plugins.gradle.service.settings;

import com.intellij.icons.AllIcons;
import com.intellij.ide.util.projectWizard.WizardContext;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.externalSystem.service.execution.ExternalSystemJdkUtil;
import com.intellij.openapi.externalSystem.util.ExternalSystemUiUtil;
import com.intellij.openapi.externalSystem.util.PaintAwarePanel;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkModificator;
import com.intellij.openapi.roots.ui.configuration.SdkComboBox;
import com.intellij.openapi.roots.ui.configuration.SdkLookupProvider;
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
import com.intellij.openapi.roots.ui.util.CompositeAppearance;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.*;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.Alarm;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ThrowableRunnable;
import com.intellij.util.ui.GridBag;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import com.intellij.xml.util.XmlStringUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.execution.target.GradleRuntimeTargetUI;
import org.jetbrains.plugins.gradle.execution.target.TargetPathFieldWithBrowseButton;
import org.jetbrains.plugins.gradle.service.GradleInstallationManager;
import org.jetbrains.plugins.gradle.service.execution.GradleDaemonJvmCriteria;
import org.jetbrains.plugins.gradle.service.execution.GradleDaemonJvmHelper;
import org.jetbrains.plugins.gradle.settings.DistributionType;
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings;
import org.jetbrains.plugins.gradle.settings.TestRunner;
import org.jetbrains.plugins.gradle.util.GradleBundle;
import org.jetbrains.plugins.gradle.util.GradleConstants;
import org.jetbrains.plugins.gradle.util.GradleUtil;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import static com.intellij.openapi.externalSystem.util.ExternalSystemUiUtil.INSETS;
import static com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil.createUniqueSdkName;
import static com.intellij.openapi.roots.ui.configuration.SdkComboBoxModel.createJdkComboBoxModel;
import static com.intellij.openapi.roots.ui.configuration.SdkLookupProvider.SdkInfo;
import static org.jetbrains.plugins.gradle.util.GradleJvmComboBoxUtil.*;
import static org.jetbrains.plugins.gradle.util.GradleJvmResolutionUtil.getGradleJvmLookupProvider;
import static org.jetbrains.plugins.gradle.util.GradleJvmUtil.nonblockingResolveGradleJvmInfo;

/**
 * @author Vladislav.Soroka
 */
@SuppressWarnings("FieldCanBeLocal") // Used implicitly by reflection at disposeUIResources() and showUi()
public class IdeaGradleProjectSettingsControlBuilder implements GradleProjectSettingsControlBuilder {
  private static final Logger LOG = Logger.getInstance(IdeaGradleProjectSettingsControlBuilder.class);

  private static final long BALLOON_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(1);
  private static final String HIDDEN_KEY = "hidden";

  private final @NotNull GradleProjectSettings myInitialSettings;

  private final @NotNull Disposable myDisposable = Disposer.newDisposable("IdeaGradleProjectSettingsControlBuilder.myDisposable");

  private final @NotNull Alarm myAlarm = new Alarm(myDisposable);

  /**
   * The target {@link Project} reference of the UI control.
   * It can be the current project of the settings UI configurable (see {@link GradleConfigurable}),
   * or the target project from the wizard context.
   */
  private final @NotNull Ref<Project> myProjectRef = Ref.create();
  private final @NotNull Disposable myProjectRefDisposable = () -> myProjectRef.set(null);

  private boolean myShowBalloonIfNecessary;
  private boolean dropGradleJdkComponents;
  private boolean dropGradleDaemonJvmCriteriaComponents;
  private boolean dropUseWrapperButton;
  private boolean dropCustomizableWrapperButton;
  private boolean dropUseLocalDistributionButton;
  private boolean dropUseBundledDistributionButton;
  private boolean dropResolveModulePerSourceSetCheckBox;
  private boolean dropResolveExternalAnnotationsCheckBox;
  private boolean dropDelegateBuildCombobox;
  private boolean dropTestRunnerCombobox;

  private @Nullable JComboBox<DistributionTypeItem> myGradleDistributionComboBox;
  private @Nullable JBLabel myGradleDistributionHint;
  private @NotNull GradleLocationSettingType myGradleHomeSettingType = GradleLocationSettingType.UNKNOWN;
  private @Nullable TargetPathFieldWithBrowseButton myGradleHomePathField;
  @SuppressWarnings({"unused", "RedundantSuppression"}) // used by ExternalSystemUiUtil.showUi to show/hide the component via reflection
  private @Nullable JPanel myGradlePanel;
  private @Nullable SdkComboBox myGradleJdkComboBox;
  private @Nullable JPanel myGradleJvmWrapper;
  private @Nullable GradleDaemonJvmCriteriaView myGradleDaemonJvmCriteriaView;
  @SuppressWarnings({"unused", "RedundantSuppression"}) // used by ExternalSystemUiUtil.showUi to show/hide the component via reflection
  private @Nullable JPanel myImportPanel;
  private @Nullable JPanel myModulePerSourceSetPanel;
  private @Nullable JBCheckBox myResolveModulePerSourceSetCheckBox;
  private @Nullable JBCheckBox myResolveExternalAnnotationsCheckBox;
  private @Nullable JLabel myDelegateBuildLabel;

  private @Nullable ComboBox<BuildRunItem> myDelegateBuildCombobox;
  private @Nullable JLabel myTestRunnerLabel;
  private @Nullable ComboBox<TestRunnerItem> myTestRunnerCombobox;
  private @Nullable JPanel myDelegatePanel;

  public IdeaGradleProjectSettingsControlBuilder(@NotNull GradleProjectSettings initialSettings) {
    myInitialSettings = initialSettings;
  }

  public IdeaGradleProjectSettingsControlBuilder dropGradleJdkComponents() {
    dropGradleJdkComponents = true;
    return this;
  }

  public IdeaGradleProjectSettingsControlBuilder dropGradleDaemonJvmCriteriaComponents() {
    dropGradleDaemonJvmCriteriaComponents = true;
    return this;
  }

  public IdeaGradleProjectSettingsControlBuilder dropUseWrapperButton() {
    dropUseWrapperButton = true;
    return this;
  }

  public IdeaGradleProjectSettingsControlBuilder dropCustomizableWrapperButton() {
    dropCustomizableWrapperButton = true;
    return this;
  }

  public IdeaGradleProjectSettingsControlBuilder dropUseLocalDistributionButton() {
    dropUseLocalDistributionButton = true;
    return this;
  }

  public IdeaGradleProjectSettingsControlBuilder dropUseBundledDistributionButton() {
    dropUseBundledDistributionButton = true;
    return this;
  }

  /**
   * @deprecated Auto-import cannot be disabled
   * @see com.intellij.openapi.externalSystem.autoimport.ExternalSystemProjectTracker
   */
  @Deprecated(forRemoval = true)
  public IdeaGradleProjectSettingsControlBuilder dropUseAutoImportBox() {
    return this;
  }

  public IdeaGradleProjectSettingsControlBuilder dropResolveModulePerSourceSetCheckBox() {
    dropResolveModulePerSourceSetCheckBox = true;
    return this;
  }

  public IdeaGradleProjectSettingsControlBuilder dropResolveExternalAnnotationsCheckBox() {
    dropResolveExternalAnnotationsCheckBox = true;
    return this;
  }

  public IdeaGradleProjectSettingsControlBuilder dropDelegateBuildCombobox() {
    dropDelegateBuildCombobox = true;
    return this;
  }

  public IdeaGradleProjectSettingsControlBuilder dropTestRunnerCombobox() {
    dropTestRunnerCombobox = true;
    return this;
  }

  @Override
  public void showUi(boolean show) {
    ExternalSystemUiUtil.showUi(this, show);

    if (show) {
      // some controls need to remain hidden depending on the selection
      // also error notifications should be shown
      updateDistributionComponents();
      updateDeprecatedControls();
    }
  }

  @Override
  public @NotNull GradleProjectSettings getInitialSettings() {
    return myInitialSettings;
  }

  @Override
  public void createAndFillControls(PaintAwarePanel content, int indentLevel) {
    content.setPaintCallback(graphics -> showBalloonIfNecessary());

    content.addPropertyChangeListener(new PropertyChangeListener() {
      @Override
      public void propertyChange(PropertyChangeEvent evt) {
        if (!"ancestor".equals(evt.getPropertyName())) {
          return;
        }

        // Configure the balloon to show on initial configurable drawing.
        myShowBalloonIfNecessary = evt.getNewValue() != null && evt.getOldValue() == null;

        if (evt.getNewValue() == null && evt.getOldValue() != null) {
          // Cancel delayed balloons when the configurable is hidden.
          myAlarm.cancelAllRequests();
        }
      }
    });

    addImportComponents(content, indentLevel);
    addDelegationComponents(content, indentLevel);
    addGradleComponents(content, indentLevel);
  }

  private void addImportComponents(PaintAwarePanel content, int indentLevel) {
    myImportPanel = addComponentsGroup(null, content, indentLevel, panel -> {
      if (!dropResolveModulePerSourceSetCheckBox) {
        myModulePerSourceSetPanel = new JPanel(new GridBagLayout());
        panel.add(myModulePerSourceSetPanel, ExternalSystemUiUtil.getFillLineConstraints(0).insets(0, 0, 0, 0));

        myModulePerSourceSetPanel.add(
          myResolveModulePerSourceSetCheckBox = new JBCheckBox(GradleBundle.message("gradle.settings.text.module.per.source.set",
                                                                                    getIDEName())),
          ExternalSystemUiUtil.getFillLineConstraints(indentLevel));

        JBLabel myResolveModulePerSourceSetHintLabel = new JBLabel(
          XmlStringUtil.wrapInHtml(GradleBundle.message("gradle.settings.text.module.per.source.set.hint")),
          UIUtil.ComponentStyle.SMALL);
        myResolveModulePerSourceSetHintLabel.setIcon(AllIcons.General.BalloonWarning12);
        myResolveModulePerSourceSetHintLabel.setVerticalTextPosition(SwingConstants.TOP);
        myResolveModulePerSourceSetHintLabel.setForeground(UIUtil.getLabelFontColor(UIUtil.FontColor.BRIGHTER));

        GridBag constraints = ExternalSystemUiUtil.getFillLineConstraints(indentLevel);
        constraints.insets.top = 0;
        constraints.insets.left += UIUtil.getCheckBoxTextHorizontalOffset(myResolveModulePerSourceSetCheckBox);
        myModulePerSourceSetPanel.add(myResolveModulePerSourceSetHintLabel, constraints);
      }

      if (!dropResolveExternalAnnotationsCheckBox) {
        panel.add(
          myResolveExternalAnnotationsCheckBox = new JBCheckBox(GradleBundle.message("gradle.settings.text.download.annotations")),
          ExternalSystemUiUtil.getFillLineConstraints(indentLevel));
      }
    });
  }

  @Override
  public void disposeUIResources() {
    ExternalSystemUiUtil.disposeUi(this);
    Disposer.dispose(myDisposable);
  }

  /**
   * Updates GUI of the gradle configurable in order to show deduced path to gradle (if possible).
   */
  private void deduceGradleHomeIfPossible() {
    if (myGradleHomePathField == null) return;

    Path gradleHome = GradleInstallationManager.getInstance().getAutodetectedGradleHome(myProjectRef.get());
    if (gradleHome == null) {
      new DelayedBalloonInfo(MessageType.WARNING, GradleLocationSettingType.UNKNOWN, BALLOON_DELAY_MILLIS).run();
      return;
    }
    myGradleHomeSettingType = GradleLocationSettingType.DEDUCED;
    new DelayedBalloonInfo(MessageType.INFO, GradleLocationSettingType.DEDUCED, BALLOON_DELAY_MILLIS).run();
    myGradleHomePathField.setText(gradleHome.toString());
    myGradleHomePathField.getTextField().setForeground(GradleLocationSettingType.DEDUCED.getColor());
  }

  private void addGradleComponents(PaintAwarePanel content, int indentLevel) {
    myGradlePanel = addComponentsGroup(GradleConstants.GRADLE_NAME, content, indentLevel, panel -> { //NON-NLS GRADLE_NAME
      addGradleChooserComponents(panel, indentLevel + 1);
      if (GradleDaemonJvmHelper.isProjectUsingDaemonJvmCriteria(myInitialSettings)) {
        addGradleDaemonJvmCriteriaComponents(panel, indentLevel + 1);
      } else {
        addGradleJdkComponents(panel, indentLevel + 1);
      }
    });
  }

  @Override
  public IdeaGradleProjectSettingsControlBuilder addGradleJdkComponents(JPanel content, int indentLevel) {
    if (!dropGradleJdkComponents) {
      Project project = ProjectManager.getInstance().getDefaultProject();
      JBLabel gradleJvmLabel = new JBLabel(GradleBundle.message("gradle.settings.text.jvm.path"));
      myGradleJvmWrapper = new JPanel(new BorderLayout());
      recreateGradleJdkComboBox(project, new ProjectSdksModel());

      gradleJvmLabel.setLabelFor(myGradleJvmWrapper);

      content.add(gradleJvmLabel, ExternalSystemUiUtil.getLabelConstraints(indentLevel));
      content.add(myGradleJvmWrapper, ExternalSystemUiUtil.getFillLineConstraints(0));
    }
    return this;
  }

  @Override
  public IdeaGradleProjectSettingsControlBuilder addGradleDaemonJvmCriteriaComponents(JPanel content, int indentLevel) {
    if (!dropGradleDaemonJvmCriteriaComponents) {
      JBLabel gradleJvmLabel = new JBLabel(GradleBundle.message("gradle.settings.text.daemon.toolchain.component"));
      myGradleJvmWrapper = new JPanel(new BorderLayout());
      recreateGradleDaemonJvmCriteriaComponent(myInitialSettings);

      gradleJvmLabel.setLabelFor(myGradleJvmWrapper);

      Insets insets = JBUI.insets(INSETS * indentLevel, INSETS + INSETS * indentLevel, 0, INSETS);
      GridBag gradleJdkLabelConstraint = new GridBag().anchor(GridBagConstraints.NORTHWEST).weightx(0).insets(insets);
      content.add(gradleJvmLabel, gradleJdkLabelConstraint);
      content.add(myGradleJvmWrapper, ExternalSystemUiUtil.getFillLineConstraints(0));
    }
    return this;
  }

  @Override
  public IdeaGradleProjectSettingsControlBuilder addGradleChooserComponents(JPanel content, int indentLevel) {
    ArrayList<DistributionTypeItem> availableDistributions = new ArrayList<>();

    if (!dropUseWrapperButton) availableDistributions.add(new DistributionTypeItem(DistributionType.DEFAULT_WRAPPED));
    if (!dropCustomizableWrapperButton) availableDistributions.add(new DistributionTypeItem(DistributionType.WRAPPED));
    if (!dropUseLocalDistributionButton) availableDistributions.add(new DistributionTypeItem(DistributionType.LOCAL));
    if (!dropUseBundledDistributionButton) availableDistributions.add(new DistributionTypeItem(DistributionType.BUNDLED));

    myGradleDistributionComboBox = new ComboBox<>();
    myGradleDistributionComboBox.setRenderer(new MyItemCellRenderer<>());

    myGradleDistributionHint = new JBLabel();
    myGradleHomePathField = new TargetPathFieldWithBrowseButton();
    myGradleDistributionHint.setLabelFor(myGradleHomePathField);

    myGradleHomePathField.getTextField().getDocument().addDocumentListener(new DocumentListener() {
      @Override
      public void insertUpdate(DocumentEvent e) {
        myGradleHomePathField.getTextField().setForeground(GradleLocationSettingType.EXPLICIT_CORRECT.getColor());
      }

      @Override
      public void removeUpdate(DocumentEvent e) {
        myGradleHomePathField.getTextField().setForeground(GradleLocationSettingType.EXPLICIT_CORRECT.getColor());
      }

      @Override
      public void changedUpdate(DocumentEvent e) {
      }
    });

    myGradleDistributionComboBox.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        updateDistributionComponents();
      }
    });

    myGradleDistributionComboBox.setModel(new CollectionComboBoxModel<>(availableDistributions));
    if (!availableDistributions.isEmpty()) {
      content.add(new JBLabel(GradleBundle.message("gradle.project.settings.distribution")),
                  ExternalSystemUiUtil.getLabelConstraints(indentLevel));
      content.add(myGradleDistributionComboBox, ExternalSystemUiUtil.getLabelConstraints(0));

      JPanel additionalControlsPanel = new JPanel(new GridBagLayout());
      additionalControlsPanel.add(myGradleDistributionHint);
      additionalControlsPanel.add(myGradleHomePathField, ExternalSystemUiUtil.getFillLineConstraints(0));
      content.add(additionalControlsPanel, ExternalSystemUiUtil.getFillLineConstraints(0).insets(0, 0, 0, 0));

      // adjust the combobox height to match the height of the editor and Gradle GDK combobox.
      // - without setting the prefered size, it's resized when path component is shown/hidden
      // - without adjusting the height the combobox is a little smaller then the next combobox (Gradle JVM)
      boolean macTheme = UIUtil.isUnderDefaultMacTheme();
      myGradleDistributionComboBox.setPreferredSize(new Dimension(myGradleDistributionComboBox.getPreferredSize().width,
                                                                  myGradleHomePathField.getPreferredSize().height + (macTheme ? 3 : 0)));
    }

    return this;
  }

  private void updateDistributionComponents() {
    if (myGradleDistributionComboBox == null) return;
    if (myGradleHomePathField == null) return;

    boolean localEnabled = getSelectedGradleDistribution() == DistributionType.LOCAL;
    boolean wrapperSelected = getSelectedGradleDistribution() == DistributionType.DEFAULT_WRAPPED;

    myGradleHomePathField.setEnabled(localEnabled);
    myGradleHomePathField.setVisible(localEnabled);

    if (myGradleDistributionHint != null) {
      myGradleDistributionHint.setEnabled(wrapperSelected);
      myGradleDistributionHint.setVisible(wrapperSelected);
    }

    if (localEnabled) {
      if (myGradleHomePathField.getText().isEmpty()) {
        deduceGradleHomeIfPossible();
      }
      else {
        Project project = myProjectRef.get();
        if (GradleInstallationManager.getInstance().isGradleSdkHome(project, Path.of(myGradleHomePathField.getText()))) {
          myGradleHomeSettingType = GradleLocationSettingType.EXPLICIT_CORRECT;
        }
        else {
          myGradleHomeSettingType = GradleLocationSettingType.EXPLICIT_INCORRECT;
          myShowBalloonIfNecessary = true;
        }
      }
      showBalloonIfNecessary();
    }
    else {
      myAlarm.cancelAllRequests();
    }
  }

  private @Nullable DistributionType getSelectedGradleDistribution() {
    if (myGradleDistributionComboBox == null) return null;
    Object selection = myGradleDistributionComboBox.getSelectedItem();
    return selection == null ? null : ((DistributionTypeItem)selection).value;
  }

  @Override
  public boolean validate(GradleProjectSettings settings) throws ConfigurationException {
    if (myGradleJdkComboBox != null && !ApplicationManager.getApplication().isUnitTestMode()) {
      SdkInfo sdkInfo = getSelectedGradleJvmInfo(myGradleJdkComboBox);
      if (sdkInfo instanceof SdkInfo.Undefined) {
        throw new ConfigurationException(GradleBundle.message("gradle.jvm.undefined"));
      }
      if (sdkInfo instanceof SdkInfo.Resolved) {
        String homePath = ((SdkInfo.Resolved)sdkInfo).getHomePath();
        if (!ExternalSystemJdkUtil.isValidJdk(homePath)) {
          throw new ConfigurationException(GradleBundle.message("gradle.jvm.incorrect", homePath));
        }
      }
    }

    if (myGradleDaemonJvmCriteriaView != null) {
      myGradleDaemonJvmCriteriaView.validateSelection();
    }

    if (myGradleHomePathField != null && getSelectedGradleDistribution() == DistributionType.LOCAL) {
      String gradleHomePath = FileUtil.toCanonicalPath(myGradleHomePathField.getText());
      if (StringUtil.isEmpty(gradleHomePath)) {
        myGradleHomeSettingType = GradleLocationSettingType.UNKNOWN;
        throw new ConfigurationException(GradleBundle.message("gradle.home.setting.type.explicit.empty", gradleHomePath));
      }
      else if (!GradleInstallationManager.getInstance().isGradleSdkHome(myProjectRef.get(), Path.of(gradleHomePath))) {
        myGradleHomeSettingType = GradleLocationSettingType.EXPLICIT_INCORRECT;
        new DelayedBalloonInfo(MessageType.ERROR, myGradleHomeSettingType, 0).run();
        throw new ConfigurationException(GradleBundle.message("gradle.home.setting.type.explicit.incorrect", gradleHomePath));
      }
    }
    return true;
  }

  private @NotNull SdkLookupProvider getSdkLookupProvider(@NotNull Project project) {
    return getGradleJvmLookupProvider(project, myInitialSettings);
  }

  private @NotNull SdkInfo getSelectedGradleJvmInfo(@NotNull SdkComboBox comboBox) {
    Project project = comboBox.getModel().getProject();
    SdkLookupProvider sdkLookupProvider = getSdkLookupProvider(project);
    String externalProjectPath = myInitialSettings.getExternalProjectPath();
    Sdk projectSdk = comboBox.getModel().getSdksModel().getProjectSdk();
    String gradleJvm = getSelectedGradleJvmReference(comboBox, sdkLookupProvider);
    return nonblockingResolveGradleJvmInfo(sdkLookupProvider, project, projectSdk, externalProjectPath, gradleJvm);
  }

  @Override
  public void apply(GradleProjectSettings settings) {
    settings.setCompositeBuild(myInitialSettings.getCompositeBuild());
    if (myGradleHomePathField != null) {
      Path gradleHomePath = Path.of(myGradleHomePathField.getText().trim());
      Path finalGradleHomePath;
      if (GradleInstallationManager.getInstance().isGradleSdkHome(myProjectRef.get(), gradleHomePath)) {
        finalGradleHomePath = gradleHomePath;
      }
      else {
        finalGradleHomePath = GradleInstallationManager.getInstance().suggestBetterGradleHomePath(myProjectRef.get(), gradleHomePath);
        if (finalGradleHomePath != null) {
          SwingUtilities.invokeLater(() -> {
            myGradleHomePathField.setText(finalGradleHomePath.toString());
          });
        }
      }
      settings.setGradleHomePath(finalGradleHomePath);
      if (finalGradleHomePath != null) {
        GradleUtil.storeLastUsedGradleHome(finalGradleHomePath.toString());
      }
    }

    if (myGradleJdkComboBox != null) {
      wrapExceptions(() -> myGradleJdkComboBox.getModel().getSdksModel().apply());
      SdkLookupProvider sdkLookupProvider = getSdkLookupProvider(myGradleJdkComboBox.getModel().getProject());
      String gradleJvm = getSelectedGradleJvmReference(myGradleJdkComboBox, sdkLookupProvider);
      settings.setGradleJvm(StringUtil.isEmpty(gradleJvm) ? null : gradleJvm);
    }

    if (myGradleDaemonJvmCriteriaView != null) {
      if (myGradleDaemonJvmCriteriaView.isModified()) {
        myGradleDaemonJvmCriteriaView.applySelection();

        applyDaemonJvmCriteria(myProjectRef.get(), settings.getExternalProjectPath(), myGradleDaemonJvmCriteriaView.getInitialCriteria());
      }
    }

    if (myResolveModulePerSourceSetCheckBox != null) {
      settings.setResolveModulePerSourceSet(myResolveModulePerSourceSetCheckBox.isSelected());
    }

    if (myResolveExternalAnnotationsCheckBox != null) {
      settings.setResolveExternalAnnotations(myResolveExternalAnnotationsCheckBox.isSelected());
    }

    if (myGradleDistributionComboBox != null) {
      Object selected = myGradleDistributionComboBox.getSelectedItem();
      if (selected instanceof DistributionTypeItem) {
        settings.setDistributionType(((DistributionTypeItem)selected).value);
      }
    }

    if (myDelegateBuildCombobox != null) {
      Object delegateBuildSelectedItem = myDelegateBuildCombobox.getSelectedItem();
      if (delegateBuildSelectedItem instanceof BuildRunItem) {
        settings.setDelegatedBuild(ObjectUtils.notNull(((BuildRunItem)delegateBuildSelectedItem).value,
                                                       GradleProjectSettings.DEFAULT_DELEGATE));
      }
    }
    if (myTestRunnerCombobox != null) {
      Object testRunnerSelectedItem = myTestRunnerCombobox.getSelectedItem();
      if (testRunnerSelectedItem instanceof TestRunnerItem) {
        settings.setTestRunner(ObjectUtils.notNull(((TestRunnerItem)testRunnerSelectedItem).value,
                                                   GradleProjectSettings.DEFAULT_TEST_RUNNER));
      }
    }
  }

  @Override
  public void applyDaemonJvmCriteria(Project project, String externalProjectPath, GradleDaemonJvmCriteria daemonJvmCriteria) {
    GradleDaemonJvmHelper.updateProjectDaemonJvmCriteria(project, externalProjectPath, daemonJvmCriteria);
  }

  @Override
  public boolean isModified() {
    if (myGradleDistributionComboBox != null && myGradleDistributionComboBox.getSelectedItem() instanceof DistributionTypeItem
        && ((DistributionTypeItem)myGradleDistributionComboBox.getSelectedItem()).value != myInitialSettings.getDistributionType()) {
      return true;
    }

    if (myResolveModulePerSourceSetCheckBox != null &&
        (myResolveModulePerSourceSetCheckBox.isSelected() != myInitialSettings.isResolveModulePerSourceSet())) {
      return true;
    }

    if (myResolveExternalAnnotationsCheckBox != null &&
        (myResolveExternalAnnotationsCheckBox.isSelected() != myInitialSettings.isResolveExternalAnnotations())) {
      return true;
    }

    if (myDelegateBuildCombobox != null && myDelegateBuildCombobox.getSelectedItem() instanceof MyItem
        && !Objects.equals(((MyItem<?>)myDelegateBuildCombobox.getSelectedItem()).value, myInitialSettings.getDelegatedBuild())) {
      return true;
    }

    if (myTestRunnerCombobox != null && myTestRunnerCombobox.getSelectedItem() instanceof MyItem
        && !Objects.equals(((MyItem<?>)myTestRunnerCombobox.getSelectedItem()).value, myInitialSettings.getTestRunner())) {
      return true;
    }

    if (myGradleJdkComboBox != null) {
      SdkLookupProvider sdkLookupProvider = getSdkLookupProvider(myGradleJdkComboBox.getModel().getProject());
      String gradleJvm = getSelectedGradleJvmReference(myGradleJdkComboBox, sdkLookupProvider);
      if (!StringUtil.equals(gradleJvm, myInitialSettings.getGradleJvm())) {
        return true;
      }
      if (myGradleJdkComboBox.getModel().getSdksModel().isModified()) {
        return true;
      }
    }

    if (myGradleDaemonJvmCriteriaView != null && myGradleDaemonJvmCriteriaView.isModified()) {
      return true;
    }

    if (myGradleHomePathField == null) return false;
    String gradleHome = myGradleHomePathField.getText().trim();
    if (StringUtil.isEmpty(gradleHome)) {
      return myInitialSettings.getGradleHomePath() != null;
    }
    else {
      return !Path.of(gradleHome).equals(myInitialSettings.getGradleHomePath());
    }
  }

  @Override
  public void reset(@Nullable Project project, GradleProjectSettings settings, boolean isDefaultModuleCreation) {
    reset(project, settings, isDefaultModuleCreation, null);
  }

  @Override
  public void reset(@Nullable Project project,
                    GradleProjectSettings settings,
                    boolean isDefaultModuleCreation,
                    @Nullable WizardContext wizardContext) {
    updateProjectRef(project, wizardContext);

    Path gradleHomePath = settings.getGradleHomePath();
    if (myGradleHomePathField != null) {
      GradleRuntimeTargetUI.installActionListener(myGradleHomePathField, myProjectRef.get(),
                                                  GradleBundle.message("gradle.settings.text.home.path"));
      myGradleHomePathField.setText(gradleHomePath == null ? "" : gradleHomePath.toString());
      myGradleHomePathField.getTextField().setForeground(GradleLocationSettingType.EXPLICIT_CORRECT.getColor());
    }
    resetImportControls(settings);

    resetGradleDaemonJvmCriteriaComponent();
    resetGradleJdkComboBox(project, settings, wizardContext);
    resetWrapperControls(settings.getExternalProjectPath(), settings, isDefaultModuleCreation);
    resetGradleDelegationControls(wizardContext);

    if (gradleHomePath == null) {
      myGradleHomeSettingType = GradleLocationSettingType.UNKNOWN;
      deduceGradleHomeIfPossible();
    }
    else {
      if (GradleInstallationManager.getInstance().isGradleSdkHome(project, gradleHomePath)) {
        myGradleHomeSettingType = GradleLocationSettingType.EXPLICIT_CORRECT;
      }
      else {
        myGradleHomeSettingType = GradleInstallationManager.getInstance().suggestBetterGradleHomePath(project, gradleHomePath) != null
                                  ? GradleLocationSettingType.EXPLICIT_CORRECT
                                  : GradleLocationSettingType.EXPLICIT_INCORRECT;
      }
      myAlarm.cancelAllRequests();
      if (myGradleHomeSettingType == GradleLocationSettingType.EXPLICIT_INCORRECT &&
          settings.getDistributionType() == DistributionType.LOCAL) {
        new DelayedBalloonInfo(MessageType.ERROR, myGradleHomeSettingType, 0).run();
      }
    }
    updateDeprecatedControls();
  }

  @Override
  public void update(String linkedProjectPath, GradleProjectSettings settings, boolean isDefaultModuleCreation) {
    resetWrapperControls(linkedProjectPath, settings, isDefaultModuleCreation);
    resetImportControls(settings);
    updateDeprecatedControls();
  }

  private void resetImportControls(GradleProjectSettings settings) {
    if (myResolveModulePerSourceSetCheckBox != null) {
      myResolveModulePerSourceSetCheckBox.setSelected(settings.isResolveModulePerSourceSet());
      boolean showSetting = !settings.isResolveModulePerSourceSet()
                            || Registry.is("gradle.settings.showDeprecatedSettings", false);
      myModulePerSourceSetPanel.putClientProperty(HIDDEN_KEY, showSetting);
    }
    if (myResolveExternalAnnotationsCheckBox != null) {
      myResolveExternalAnnotationsCheckBox.setSelected(settings.isResolveExternalAnnotations());
    }
  }

  private void updateDeprecatedControls() {
    if (myModulePerSourceSetPanel != null) {
      myModulePerSourceSetPanel.setVisible(myModulePerSourceSetPanel.getClientProperty(HIDDEN_KEY) == Boolean.TRUE);
    }
  }

  protected void resetGradleJdkComboBox(final @Nullable Project project,
                                        GradleProjectSettings settings,
                                        @Nullable WizardContext wizardContext) {
    ProjectSdksModel sdksModel = new ProjectSdksModel();
    resetGradleJdkComboBox(project, settings, wizardContext, sdksModel);
  }

  protected final void resetGradleJdkComboBox(
    @Nullable Project project,
    @NotNull GradleProjectSettings settings,
    @Nullable WizardContext wizardContext,
    @NotNull ProjectSdksModel sdksModel
  ) {
    if (myGradleJdkComboBox == null) return;
    project = project == null || project.isDisposed() ? ProjectManager.getInstance().getDefaultProject() : project;
    Sdk projectSdk = wizardContext != null ? wizardContext.getProjectJdk() : null;
    setupProjectSdksModel(sdksModel, project, projectSdk);
    recreateGradleJdkComboBox(project, sdksModel);

    SdkLookupProvider sdkLookupProvider = getSdkLookupProvider(project);
    String externalProjectPath = myInitialSettings.getExternalProjectPath();
    addUsefulGradleJvmReferences(myGradleJdkComboBox, externalProjectPath);
    setSelectedGradleJvmReference(myGradleJdkComboBox, sdkLookupProvider, externalProjectPath, settings.getGradleJvm());
  }

  protected final void resetGradleDaemonJvmCriteriaComponent() {
    if (myGradleDaemonJvmCriteriaView == null) return;
    myGradleDaemonJvmCriteriaView.resetSelection();
  }

  private void recreateGradleJdkComboBox(@NotNull Project project, @NotNull ProjectSdksModel sdksModel) {
    if (myGradleJvmWrapper != null) {
      if (myGradleJdkComboBox != null) {
        myGradleJvmWrapper.remove(myGradleJdkComboBox);
      }
      myGradleJdkComboBox = new SdkComboBox(createJdkComboBoxModel(project, sdksModel));
