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

import com.intellij.facet.FacetManager;
import com.intellij.facet.ModifiableFacetModel;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.roots.libraries.LibraryTable;
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
import com.intellij.openapi.roots.ui.configuration.LibraryTableModifiableModelProvider;
import com.intellij.openapi.roots.ui.configuration.ModuleEditor;
import com.intellij.openapi.roots.ui.configuration.ModulesConfigurator;
import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable;
import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesModifiableModel;
import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext;
import com.intellij.openapi.util.Disposer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Proxy;

/**
 * @author Dennis.Ushakov
 */
public final class IdeaModifiableModelsProvider implements ModifiableModelsProvider {
  @Override
  public @Nullable ModifiableRootModel getModuleModifiableModel(final @NotNull Module module) {
    final Project project = module.getProject();
    final ModulesConfigurator configurator = getModulesConfigurator(project);
    if (configurator != null) {
      if (!configurator.isModuleModelCommitted()) {
        final ModuleEditor moduleEditor = configurator.getModuleEditor(module);
        if (moduleEditor != null) {
          return moduleEditor.getModifiableRootModelProxy();
        }
      }
    }
    return ModuleRootManager.getInstance(module).getModifiableModel();
  }

  private static @Nullable ModulesConfigurator getModulesConfigurator(@NotNull Project project) {
    StructureConfigurableContext context = getProjectStructureContext(project);
    return context != null ? context.getModulesConfigurator() : null;
  }

  @Override
  public void commitModuleModifiableModel(final @NotNull ModifiableRootModel model) {
    if (!(model instanceof Proxy)) {
      model.commit();
    }
    //IDEA should commit this model instead of us, because it is was given from StructureConfigurableContext
  }

  @Override
  public void disposeModuleModifiableModel(final @NotNull ModifiableRootModel model) {
    if (!(model instanceof Proxy)) {
      model.dispose();
    }
    //IDEA should dispose this model instead of us, because it is was given from StructureConfigurableContext
  }

  @Override
  public @NotNull ModifiableFacetModel getFacetModifiableModel(@NotNull Module module) {
    final ModulesConfigurator configurator = getModulesConfigurator(module.getProject());
    if (configurator != null) {
      return configurator.getFacetsConfigurator().getOrCreateModifiableModel(module);
    }
    return FacetManager.getInstance(module).createModifiableModel();
  }

  @Override
  public void commitFacetModifiableModel(@NotNull Module module, @NotNull ModifiableFacetModel model) {
    final ModulesConfigurator configurator = getModulesConfigurator(module.getProject());
    if (configurator == null || !(configurator.getFacetsConfigurator().getFacetModel(module) instanceof ModifiableFacetModel)) {
      model.commit();
    }
  }

  @Override
  public @NotNull LibraryTable.ModifiableModel getLibraryTableModifiableModel() {
    final Project[] projects = ProjectManager.getInstance().getOpenProjects();
    for (Project project : projects) {
      if (!project.isInitialized()) {
        continue;
      }
      final LibraryTable.ModifiableModel modifiableModel = getGlobalModifiableModel(project);
      if (modifiableModel != null) {
        return modifiableModel;
      }
    }
    return LibraryTablesRegistrar.getInstance().getLibraryTable().getModifiableModel();
  }

  @Override
  public @NotNull LibraryTable.ModifiableModel getGlobalLibraryTableModifiableModel(@NotNull Project project) {
    if (project.isInitialized()) {
      final LibraryTable.ModifiableModel modifiableModel = getGlobalModifiableModel(project);
      if (modifiableModel != null) {
        return modifiableModel;
      }
    }
    return LibraryTablesRegistrar.getInstance().getGlobalLibraryTable(project).getModifiableModel();
  }

  @Override
  public LibraryTable.ModifiableModel getLibraryTableModifiableModel(@NotNull Project project) {
    StructureConfigurableContext context = getProjectStructureContext(project);
    if (context != null) {
      LibraryTableModifiableModelProvider provider = context.createModifiableModelProvider(LibraryTablesRegistrar.PROJECT_LEVEL);
      return provider.getModifiableModel();
    }
    return LibraryTablesRegistrar.getInstance().getLibraryTable(project).getModifiableModel();
  }

  @Override
  public void disposeLibraryTableModifiableModel(@NotNull LibraryTable.ModifiableModel model) {
    //IDEA should dispose this model instead of us, because it is was given from StructureConfigurableContext
    if (!(model instanceof LibrariesModifiableModel)) {
      Disposer.dispose(model);
    }
  }

  private static @Nullable LibraryTable.ModifiableModel getGlobalModifiableModel(@NotNull Project project) {
    var context = getProjectStructureContext(project);
    var provider = context != null ? context.createModifiableModelProvider(LibraryTablesRegistrar.APPLICATION_LEVEL) : null;
    return provider != null ? provider.getModifiableModel() : null;
  }

  private static @Nullable StructureConfigurableContext getProjectStructureContext(@NotNull Project project) {
    final ProjectStructureConfigurable structureConfigurable = ProjectStructureConfigurable.getInstance(project);
    return structureConfigurable.isUiInitialized() ? structureConfigurable.getContext() : null;
  }
}
