package com.intellij.bash.psi;

import com.intellij.bash.BashStringUtil;
import com.intellij.codeInsight.AutoPopupController;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.psi.PsiElement;
import com.intellij.util.PlatformIcons;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static com.intellij.bash.BashStringUtil.quote;
import static com.intellij.bash.BashStringUtil.unquote;
import static com.intellij.patterns.PlatformPatterns.psiElement;
import static com.intellij.patterns.StandardPatterns.instanceOf;

public class BashFilePathCompletionContributor extends CompletionContributor implements DumbAware {
  private static final InsertHandler<LookupElement> FILE_INSERT_HANDLER = (context, item) -> {
    File file = (File) item.getObject();
    Document document = context.getEditor().getDocument();
    int end = context.getSelectionEndOffset();
    boolean alreadyFollowedBySlash = document.getTextLength() >= end && document.getCharsSequence().charAt(end) == '/';
    boolean isDirectory = file.isDirectory();
    if (isDirectory) {
      if (!alreadyFollowedBySlash) {
        document.insertString(end, "/");
      }
      else {
        context.getEditor().getCaretModel().moveToOffset(end + 1);
      }
      AutoPopupController.getInstance(context.getProject()).scheduleAutoPopup(context.getEditor());
    }
  };

  @Override
  public void duringCompletion(@NotNull CompletionInitializationContext context) {
    super.duringCompletion(context);
    Caret caret = context.getCaret();
    Editor editor = context.getEditor();
    context.setReplacementOffset(calcDefaultIdentifierEnd(editor, calcSelectionEnd(caret)));
  }

  private static int calcSelectionEnd(Caret caret) {
    return caret.hasSelection() ? caret.getSelectionEnd() : caret.getOffset();
  }

  // @formatter:off
  private static int calcDefaultIdentifierEnd(Editor editor, int startFrom) {
    final CharSequence text = editor.getDocument().getCharsSequence();
    int idEnd = startFrom;
    int length = text.length();
    while (idEnd < length) {
      char ch = text.charAt(idEnd);
      if (Character.isJavaIdentifierPart(ch) || ch == '.' || ch == '-') idEnd++;
      else if (idEnd < length - 1 && ch == '\\' && BashStringUtil.ORIGINS_SET.contains(text.charAt(idEnd + 1))) idEnd += 2;
      else return idEnd;
    }
    return idEnd;
  }
  // @formatter:on

  public BashFilePathCompletionContributor() {
    extend(CompletionType.BASIC, psiElement().inFile(instanceOf(BashFile.class)), new CompletionProvider<CompletionParameters>() {
      @Override
      protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) {
        PsiElement original = parameters.getOriginalPosition();
        String originalText = original == null ? null : original.getText();

        if (originalText != null) {
          originalText = parameters.getOriginalFile().getText().substring(parameters.getPosition().getTextRange().getStartOffset(), parameters.getOffset());
          int lastSlashIndex = originalText.lastIndexOf("/");
          if (lastSlashIndex >= 0) {
            String afterSlash = originalText.substring(lastSlashIndex + 1);
            String beforeSlash = originalText.substring(0, lastSlashIndex);

            boolean isRoot = beforeSlash.isEmpty();

            if (beforeSlash.startsWith("/") || isRoot || beforeSlash.startsWith("~")) {  // absolute paths
              String maybeFilePath = FileUtil.expandUserHome(unquote(beforeSlash));
              File dir = isRoot ? new File("/") : new File(maybeFilePath);
              if (dir.exists() && dir.isDirectory()) {
                File[] files = dir.listFiles();
                if (files != null) {
                  List<LookupElement> collect = Arrays.stream(files).map(BashFilePathCompletionContributor::createFileLookupElement).collect(Collectors.toList());
                  String prefix = afterSlash.endsWith("\\") ? afterSlash.substring(0, afterSlash.length() - 1) : afterSlash;
                  result.withPrefixMatcher(prefix).caseInsensitive().addAllElements(collect);
                }
              }
              result.stopHere();
            }
          }
        }
      }
    });
  }

  private static LookupElement createFileLookupElement(File file) {
    String name = file.getName();
    boolean isDirectory = file.isDirectory();
    return LookupElementBuilder.create(file, quote(name))
        .withIcon(isDirectory ? PlatformIcons.FOLDER_ICON : null)
        .withInsertHandler(FILE_INSERT_HANDLER)
        ;
  }
}
@@ -2177,16 +2177,25 @@
 tionEnd(
+@NotNull 
 Caret ca
@@ -2351,16 +2351,25 @@
 fierEnd(
+@NotNull 
 Editor e
@@ -4755,32 +4755,43 @@
  %7D%0A    %7D);%0A  %7D%0A%0A
+  @NotNull%0A
   private static
@@ -4829,16 +4829,25 @@
 Element(
+@NotNull 
 File fil

infer nullity