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

import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.ide.scratch.RootType;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiFileSystemItemProcessor;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.Processor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * @author anna
 */
public class DefaultNavBarExtension extends AbstractNavBarModelExtension {
  @Override
  @Nullable
  public String getPresentableText(final Object object) {
    if (object instanceof Project) {
      return ((Project)object).getName();
    }
    else if (object instanceof Module) {
      return ((Module)object).getName();
    }
    else if (object instanceof PsiFile) {
      VirtualFile file = ((PsiFile)object).getVirtualFile();
      return file != null ? file.getPresentableName() : ((PsiFile)object).getName();
    }
    else if (object instanceof PsiDirectory) {
      return ((PsiDirectory)object).getVirtualFile().getName();
    }
    else if (object instanceof JdkOrderEntry) {
      return ((JdkOrderEntry)object).getJdkName();
    }
    else if (object instanceof LibraryOrderEntry) {
      final String libraryName = ((LibraryOrderEntry)object).getLibraryName();
      return libraryName != null ? libraryName : CodeInsightBundle.message("package.dependencies.library.node.text");
    }
    else if (object instanceof ModuleOrderEntry moduleOrderEntry) {
      return moduleOrderEntry.getModuleName();
    }
    return null;
  }

  @Override
  public PsiElement adjustElement(@NotNull final PsiElement psiElement) {
    final PsiFile containingFile = psiElement.getContainingFile();
    if (containingFile != null) return containingFile;
    return psiElement;
  }

  @Override
  public boolean processChildren(final Object object, final Object rootElement, final Processor<Object> processor) {
    if (object instanceof Project) {
      return processChildren((Project)object, processor);
    }
    else if (object instanceof Module) {
      return processChildren((Module)object, processor);
    }
    else if (object instanceof PsiDirectoryContainer psiPackage) {
      final PsiDirectory[] psiDirectories = ReadAction.compute(() -> rootElement instanceof Module
                                                                     ? psiPackage.getDirectories(
        GlobalSearchScope.moduleScope((Module)rootElement))
                                                                     : psiPackage.getDirectories());
      for (PsiDirectory psiDirectory : psiDirectories) {
        if (!processChildren(psiDirectory, rootElement, processor)) return false;
      }
      return true;
    }
    else if (object instanceof PsiDirectory) {
      return processChildren((PsiDirectory)object, rootElement, processor);
    }
    else if (object instanceof PsiFileSystemItem) {
      return processChildren((PsiFileSystemItem)object, processor);
    }
    return true;
  }

  private static boolean processChildren(final Project object, final Processor<Object> processor) {
    return ReadAction.compute(() -> {
      for (Module module : ModuleManager.getInstance(object).getModules()) {
        if (!ModuleType.isInternal(module) && !processor.process(module)) return false;
      }
      return true;
    });
  }

  private static boolean processChildren(Module module, Processor<Object> processor) {
    final PsiManager psiManager = PsiManager.getInstance(module.getProject());
    ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
    VirtualFile[] roots = moduleRootManager.getContentRoots();
    for (final VirtualFile root : roots) {
      final PsiDirectory psiDirectory = ReadAction.compute(() -> psiManager.findDirectory(root));
      if (psiDirectory != null) {
        if (!processor.process(psiDirectory)) return false;
      }
    }
    return true;
  }

  private static boolean processChildren(PsiDirectory directory, @Nullable Object rootElement, Processor<Object> processor) {
    return ReadAction.compute(() -> {
      Project project = directory.getProject();
      RootType scratchRootType = RootType.forFile(PsiUtilCore.getVirtualFile(directory));
      ModuleFileIndex moduleFileIndex =
        rootElement instanceof Module ? ModuleRootManager.getInstance((Module)rootElement).getFileIndex() : null;
      return directory.processChildren(child -> {
        VirtualFile childFile = PsiUtilCore.getVirtualFile(child);
        if (childFile != null && scratchRootType != null && scratchRootType.isIgnored(project, childFile)) return true;
        if (childFile != null && moduleFileIndex != null && !moduleFileIndex.isInContent(childFile)) return true;
        return processor.process(child);
      });
    });
  }

  private static boolean processChildren(final PsiFileSystemItem object, final Processor<Object> processor) {
    return ReadAction.compute(() -> object.processChildren(new PsiFileSystemItemProcessor() {
      @Override
      public boolean acceptItem(@NotNull String name, boolean isDirectory) {
        return true;
      }

      @Override
      public boolean execute(@NotNull PsiFileSystemItem element) {
        return processor.process(element);
      }
    }));
  }

  @Nullable
  @Override
  public PsiElement getParent(PsiElement psiElement) {
    PsiFile containingFile = psiElement.getContainingFile();
    if (containingFile != null) {
      PsiDirectory containingDirectory = containingFile.getContainingDirectory();
      if (containingDirectory != null) {
        return containingDirectory;
      }
    }
    else if (psiElement instanceof PsiDirectory psiDirectory) {
      Project project = psiElement.getProject();
      PsiDirectory parentDirectory = psiDirectory.getParentDirectory();
      if (parentDirectory == null) {
        VirtualFile jar = VfsUtil.getLocalFile(psiDirectory.getVirtualFile());
        if (ProjectRootManager.getInstance(project).getFileIndex().isInContent(jar)) {
          parentDirectory = PsiManager.getInstance(project).findDirectory(jar.getParent());
        }
      }
      return parentDirectory;
    }
    else if (psiElement instanceof PsiFileSystemItem) {
      VirtualFile virtualFile = ((PsiFileSystemItem)psiElement).getVirtualFile();
      if (virtualFile == null) return null;
      PsiManager psiManager = psiElement.getManager();
      PsiElement resultElement;
      if (virtualFile.isDirectory()) {
        resultElement = psiManager.findDirectory(virtualFile);
      }
      else {
        resultElement = psiManager.findFile(virtualFile);
      }
      if (resultElement == null) return null;
      VirtualFile parentVFile = virtualFile.getParent();
      if (parentVFile != null) {
        PsiDirectory parentDirectory = psiManager.findDirectory(parentVFile);
        if (parentDirectory != null) {
          return parentDirectory;
        }
      }
    }
    return null;
  }
}
