// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.roots.ui.configuration.artifacts;

import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.module.ModifiableModuleModel;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.ui.configuration.FacetsProvider;
import com.intellij.openapi.roots.ui.configuration.ModuleEditor;
import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable;
import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext;
import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureDaemonAnalyzerListener;
import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement;
import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureProblemsHolderImpl;
import com.intellij.openapi.util.Disposer;
import com.intellij.packaging.artifacts.*;
import com.intellij.packaging.elements.CompositePackagingElement;
import com.intellij.packaging.elements.ManifestFileProvider;
import com.intellij.packaging.impl.artifacts.ArtifactUtil;
import com.intellij.packaging.impl.artifacts.DefaultPackagingElementResolvingContext;
import com.intellij.packaging.ui.ManifestFileConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public final class ArtifactsStructureConfigurableContextImpl implements ArtifactsStructureConfigurableContext {
  private ModifiableArtifactModel myModifiableModel;
  private final ManifestFilesInfo myManifestFilesInfo = new ManifestFilesInfo();
  private final ArtifactListener myModifiableModelListener;
  private final StructureConfigurableContext myContext;
  private final Project myProject;
  private final Map<Artifact, CompositePackagingElement<?>> myModifiableRoots = new HashMap<>();
  private final Map<Artifact, ArtifactEditorImpl> myArtifactEditors = new HashMap<>();
  private final Map<ArtifactPointer, ArtifactEditorSettings> myEditorSettings = new HashMap<>();
  private final Map<Artifact, ArtifactProjectStructureElement> myArtifactElements = new HashMap<>();
  private final ArtifactEditorSettings myDefaultSettings;
  private final ManifestFileProvider myManifestFileProvider = new ArtifactEditorManifestFileProvider(this);

  public ArtifactsStructureConfigurableContextImpl(StructureConfigurableContext context, Project project,
                                                   ArtifactEditorSettings defaultSettings, final ArtifactListener modifiableModelListener) {
    myDefaultSettings = defaultSettings;
    myModifiableModelListener = modifiableModelListener;
    myContext = context;
    myProject = project;
    context.getDaemonAnalyzer().addListener(new ProjectStructureDaemonAnalyzerListener() {
      @Override
      public void problemsChanged(@NotNull ProjectStructureElement element) {
        if (element instanceof ArtifactProjectStructureElement) {
          final Artifact originalArtifact = ((ArtifactProjectStructureElement)element).getOriginalArtifact();
          final ArtifactEditorImpl artifactEditor = myArtifactEditors.get(originalArtifact);
          if (artifactEditor != null) {
            updateProblems(originalArtifact, artifactEditor);
          }
        }
      }
    });
  }

  private void updateProblems(Artifact originalArtifact, ArtifactEditorImpl artifactEditor) {
    final ProjectStructureProblemsHolderImpl holder = myContext.getDaemonAnalyzer().getProblemsHolder(getOrCreateArtifactElement(originalArtifact));
    artifactEditor.getValidationManager().updateProblems(holder);
  }

  @Override
  public @NotNull Project getProject() {
    return myProject;
  }

  @Override
  public @NotNull ArtifactModel getArtifactModel() {
    if (myModifiableModel != null) {
      return myModifiableModel;
    }
    return ArtifactManager.getInstance(myProject);
  }

  @Override
  public @NotNull Artifact getOriginalArtifact(@NotNull Artifact artifact) {
    if (myModifiableModel != null) {
      return myModifiableModel.getOriginalArtifact(artifact);
    }
    return artifact;
  }

  @Override
  public ModifiableModuleModel getModifiableModuleModel() {
    return myContext.getModulesConfigurator().getModuleModel();
  }

  @Override
  public void queueValidation(Artifact artifact) {
    myContext.getDaemonAnalyzer().queueUpdate(getOrCreateArtifactElement(artifact));
  }

  @Override
  public CompositePackagingElement<?> getRootElement(@NotNull Artifact artifact) {
    artifact = getOriginalArtifact(artifact);
    if (myModifiableModel != null) {
      final Artifact modifiableArtifact = myModifiableModel.getModifiableCopy(artifact);
      if (modifiableArtifact != null) {
        myModifiableRoots.put(artifact, modifiableArtifact.getRootElement());
      }
    }
    return getOrCreateModifiableRootElement(artifact);
  }

  private CompositePackagingElement<?> getOrCreateModifiableRootElement(Artifact originalArtifact) {
    CompositePackagingElement<?> root = myModifiableRoots.get(originalArtifact);
    if (root == null) {
      root = ArtifactUtil.copyFromRoot(originalArtifact.getRootElement(), myProject);
      myModifiableRoots.put(originalArtifact, root);
    }
    return root;
  }

  @Override
  public void editLayout(final @NotNull Artifact artifact, final Runnable action) {
    final Artifact originalArtifact = getOriginalArtifact(artifact);
    WriteAction.run(() -> {
      final ModifiableArtifact modifiableArtifact = getOrCreateModifiableArtifactModel().getOrCreateModifiableArtifact(originalArtifact);
      if (modifiableArtifact.getRootElement() == originalArtifact.getRootElement()) {
        modifiableArtifact.setRootElement(getOrCreateModifiableRootElement(originalArtifact));
      }
      action.run();
    });
    myContext.getDaemonAnalyzer().queueUpdate(getOrCreateArtifactElement(originalArtifact));
  }

  public @Nullable ArtifactEditorImpl getArtifactEditor(Artifact artifact) {
    return myArtifactEditors.get(getOriginalArtifact(artifact));
  }

  @Override
  public ArtifactEditorImpl getOrCreateEditor(Artifact artifact) {
    artifact = getOriginalArtifact(artifact);
    ArtifactEditorImpl artifactEditor = myArtifactEditors.get(artifact);
    if (artifactEditor == null) {
      final ArtifactEditorSettings settings = myEditorSettings.get(ArtifactPointerManager.getInstance(myProject).createPointer(artifact, getArtifactModel()));
      artifactEditor = new ArtifactEditorImpl(this, artifact, settings != null ? settings : myDefaultSettings);
      myArtifactEditors.put(artifact, artifactEditor);
    }
    return artifactEditor;
  }

  public @Nullable ModifiableArtifactModel getActualModifiableModel() {
    return myModifiableModel;
  }

  @Override
  public @NotNull ModifiableArtifactModel getOrCreateModifiableArtifactModel() {
    if (myModifiableModel == null) {
      myModifiableModel = ArtifactManager.getInstance(myProject).createModifiableModel();
      myModifiableModel.addListener(myModifiableModelListener);
    }
    return myModifiableModel;
  }

  @Override
  public ArtifactEditorSettings getDefaultSettings() {
    return myDefaultSettings;
  }

  @Override
  public @NotNull ModulesProvider getModulesProvider() {
    return myContext.getModulesConfigurator();
  }

  @Override
  public @NotNull FacetsProvider getFacetsProvider() {
    return myContext.getModulesConfigurator().getFacetsConfigurator();
  }

  @Override
  public Library findLibrary(@NotNull String level, @NotNull String libraryName) {
    final Library library = DefaultPackagingElementResolvingContext.findLibrary(myProject, level, libraryName);
    return library != null ? myContext.getLibraryModel(library) : myContext.getLibrary(libraryName, level);
  }

  @Override
  public @NotNull ManifestFileProvider getManifestFileProvider() {
    return myManifestFileProvider;
  }

  @Override
  public ManifestFileConfiguration getManifestFile(CompositePackagingElement<?> element, ArtifactType artifactType) {
    return myManifestFilesInfo.getManifestFile(element, artifactType, this);
  }

  public ManifestFilesInfo getManifestFilesInfo() {
    return myManifestFilesInfo;
  }

  public void resetModifiableModel() {
    disposeUIResources();
    myModifiableModel = null;
    myModifiableRoots.clear();
    myManifestFilesInfo.clear();
  }

  public void disposeUIResources() {
    for (ArtifactEditorImpl editor : myArtifactEditors.values()) {
      Disposer.dispose(editor);
    }
    myArtifactEditors.clear();
    if (myModifiableModel != null) {
      myModifiableModel.dispose();
    }
    myArtifactElements.clear();
  }

  public Collection<? extends ArtifactEditorImpl> getArtifactEditors() {
    return Collections.unmodifiableCollection(myArtifactEditors.values());
  }

  public void saveEditorSettings() {
    myEditorSettings.clear();
    for (ArtifactEditorImpl artifactEditor : myArtifactEditors.values()) {
      final ArtifactPointer pointer = ArtifactPointerManager.getInstance(myProject).createPointer(artifactEditor.getArtifact(), getArtifactModel());
      myEditorSettings.put(pointer, artifactEditor.createSettings());
    }
  }

  @Override
  public @NotNull ArtifactProjectStructureElement getOrCreateArtifactElement(@NotNull Artifact artifact) {
    ArtifactProjectStructureElement element = myArtifactElements.get(getOriginalArtifact(artifact));
    if (element == null) {
      element = new ArtifactProjectStructureElement(myContext, this, artifact);
      myArtifactElements.put(artifact, element);
    }
    return element;
  }

  @Override
  public ModifiableRootModel getOrCreateModifiableRootModel(Module module) {
    final ModuleEditor editor = myContext.getModulesConfigurator().getOrCreateModuleEditor(module);
    return editor.getModifiableRootModelProxy();
  }

  @Override
  public @NotNull ProjectStructureConfigurable getProjectStructureConfigurable() {
    return myContext.getModulesConfigurator().getProjectStructureConfigurable();
  }
}
