// 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.util.xml;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.*;
import com.intellij.psi.PsiManager;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.xml.reflect.AbstractDomChildrenDescription;
import com.intellij.util.xml.reflect.DomGenericInfo;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Type;

public abstract class DomManager extends CompositeModificationTracker implements ModificationTracker {
  public static final Key<Module> MOCK_ELEMENT_MODULE = Key.create("MockElementModule");

  public static DomManager getDomManager(Project project) {
    return project.getService(DomManager.class);
  }

  public DomManager(@NotNull Project project) {
    super(PsiManager.getInstance(project).getModificationTracker());
  }

  public abstract Project getProject();

  /**
   * @param file XML file
   * @param domClass desired DOM element class
   * @return New or cached DOM file element for the given file. All registered {@link DomFileDescription}s are
   * asked if they are responsible for the file {@link DomFileDescription#isMyFile(XmlFile, Module)}.
   * If there is a {@link DomFileDescription} that is responsible for the file, but its {@link DomFileDescription#getRootElementClass()}
   * result is incompatible with domClass parameter, null is returned
   */
  public abstract @Nullable <T extends DomElement> DomFileElement<T> getFileElement(XmlFile file, Class<T> domClass);

  /**
   * @deprecated use {@link #getFileElement(XmlFile, Class)}
   */
  @Deprecated
  public abstract @Nullable <T extends DomElement> DomFileElement<T> getFileElement(XmlFile file);

  /**
   * @deprecated use {@link #getFileElement(XmlFile, Class)}
   */
  @Deprecated(forRemoval = true)
  public abstract @NotNull <T extends DomElement> DomFileElement<T> getFileElement(XmlFile file, Class<T> aClass, @NonNls String rootTagName);

  public abstract void addDomEventListener(DomEventListener listener, Disposable parentDisposable);

  /**
   * @param type Type. Only {@link Class} and {@link java.lang.reflect.ParameterizedType} are allowed
   * @return {@link DomGenericInfo} instance for the desired type
   */
  public abstract DomGenericInfo getGenericInfo(Type type);

  /**
   * @param element tag
   * @return DOM element for the given tag. If DOM isn't initialized for the containing file, it will be initialized
   */
  public abstract @Nullable DomElement getDomElement(final @Nullable XmlTag element);

  /**
   * @param element attribute
   * @return DOM element for the given XML attribute. If DOM isn't initialized for the containing file, it will be initialized
   */
  public abstract @Nullable GenericAttributeValue<?> getDomElement(final XmlAttribute element);

  /**
   * @param aClass Desired DOM element class
   * @param module One may wish the result to think that it is in a particular module
   * @param physical see {@link com.intellij.psi.PsiFile#isPhysical()}
   * @return DOM element, which doesn't have any real file under itself. A mock file is created for it. See
   * {@link com.intellij.psi.PsiFileFactory#createFileFromText(String, com.intellij.openapi.fileTypes.FileType, CharSequence, long, boolean, boolean)}
   */
  public abstract <T extends DomElement> T createMockElement(Class<T> aClass, final Module module, final boolean physical);

  /**
   * @param element DOM element
   * @return true if this element was created by {@link #createMockElement(Class, Module, boolean)} method
   */
  public abstract boolean isMockElement(DomElement element);

  /**
   * Creates a DOM element of a needed type, that is wrapper around a real DOM element.
   * Once the wrapped element becomes invalid, a new value is requested from provider parameter, so there's a possibility to
   * restore the functionality.
   * The resulting element will also implement StableElement interface.
   *
   * @param provider provides values to be wrapped
   * @return stable DOM element
   */
  public abstract <T extends DomElement> T createStableValue(Factory<? extends T> provider);

  public abstract <T> T createStableValue(final Factory<? extends T> provider, final Condition<? super T> validator);

  /**
   * @return {@link ConverterManager} instance
   * @deprecated This will be moved at the application level
   */
  @Deprecated
  public abstract ConverterManager getConverterManager();

  public abstract ModelMerger createModelMerger();

  /**
   * @param element reference element
   * @return element that represents the resolve scope for the given reference. {@link DomResolveConverter} uses
   * this method to resolve DOM references. This result's subtree will be traversed recursively searching for the reference target. See
   * {@link Resolve} annotation.
   */
  public abstract @NotNull DomElement getResolvingScope(GenericDomValue<?> element);

  /**
   * @param element Named DOM element
   * @return The scope within which the element's name identity will be checked by
   * {@link com.intellij.util.xml.highlighting.DomHighlightingHelper#checkNameIdentity(DomElement, com.intellij.util.xml.highlighting.DomElementAnnotationHolder)}
   */
  public abstract @Nullable DomElement getIdentityScope(DomElement element);

  /**
   * @return {@link TypeChooserManager} instance
   */
  public abstract TypeChooserManager getTypeChooserManager();

  public abstract @Nullable AbstractDomChildrenDescription findChildrenDescription(@NotNull XmlTag templateChildTag, @NotNull DomElement parent);

  public final @Nullable DomFileDescription<?> getDomFileDescription(final XmlFile xmlFile) {
    final DomFileElement<DomElement> element = getFileElement(xmlFile);
    return element != null ? element.getFileDescription() : null;
  }
}
