// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.jsonSchema.impl;

import com.intellij.json.pointer.JsonPointerPosition;
import com.intellij.json.psi.JsonObject;
import com.intellij.json.psi.JsonProperty;
import com.intellij.lang.documentation.DocumentationMarkup;
import com.intellij.lang.documentation.DocumentationProvider;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.FakePsiElement;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.jsonSchema.extension.JsonLikePsiWalker;
import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider;
import com.jetbrains.jsonSchema.ide.JsonSchemaService;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.List;


public class JsonSchemaDocumentationProvider implements DocumentationProvider {
  @Nullable
  @Override
  public String getQuickNavigateInfo(PsiElement element, PsiElement originalElement) {
    return findSchemaAndGenerateDoc(element, originalElement, true, null);
  }

  @Nullable
  @Override
  public List<String> getUrlFor(PsiElement element, PsiElement originalElement) {
    return null;
  }

  @Nullable
  @Override
  public String generateDoc(PsiElement element, @Nullable PsiElement originalElement) {
    String forcedPropName = null;
    if (element instanceof FakeDocElement) {
      forcedPropName = ((FakeDocElement)element).myAltName;
      element = ((FakeDocElement)element).myContextElement;
    }
    return findSchemaAndGenerateDoc(element, originalElement, false, forcedPropName);
  }

  @Nullable
  public static String findSchemaAndGenerateDoc(PsiElement element,
                                                @Nullable PsiElement originalElement,
                                                final boolean preferShort,
                                                @Nullable String forcedPropName) {
    if (element instanceof FakeDocElement) return null;
    element = isWhitespaceOrComment(originalElement) ? element : ObjectUtils.coalesce(originalElement, element);
    final PsiFile containingFile = element.getContainingFile();
    if (containingFile == null) return null;
    final JsonSchemaService service = JsonSchemaService.Impl.get(element.getProject());
    VirtualFile virtualFile = containingFile.getViewProvider().getVirtualFile();
    if (!service.isApplicableToFile(virtualFile)) return null;
    final JsonSchemaObject rootSchema = service.getSchemaObject(virtualFile);
    if (rootSchema == null) return null;

    return generateDoc(element, rootSchema, preferShort, forcedPropName);
  }

  private static boolean isWhitespaceOrComment(@Nullable PsiElement originalElement) {
    return originalElement instanceof PsiWhiteSpace || originalElement instanceof PsiComment;
  }

  @Nullable
  public static String generateDoc(@NotNull final PsiElement element,
                                   @NotNull final JsonSchemaObject rootSchema,
                                   final boolean preferShort,
                                   @Nullable String forcedPropName) {
    final JsonLikePsiWalker walker = JsonLikePsiWalker.getWalker(element, rootSchema);
    if (walker == null) return null;

    final PsiElement checkable = walker.findElementToCheck(element);
    if (checkable == null) return null;
    final JsonPointerPosition position = walker.findPosition(checkable, true);
    if (position == null) return null;
    if (forcedPropName != null) {
      if (isWhitespaceOrComment(element)) {
        position.addFollowingStep(forcedPropName);
      }
      else {
        if (position.isEmpty()) {
          return null;
        }
        if (position.isArray(position.size() - 1)) return null;
        position.replaceStep(position.size() - 1, forcedPropName);
      }
    }
    final Collection<JsonSchemaObject> schemas = new JsonSchemaResolver(rootSchema, true, position).resolve();

    String htmlDescription = null;
    List<JsonSchemaType> possibleTypes = ContainerUtil.newArrayList();
    for (JsonSchemaObject schema : schemas) {
      if (htmlDescription == null) {
        htmlDescription = getBestDocumentation(preferShort, schema);
      }
      if (schema.getType() != null && schema.getType() != JsonSchemaType._any) {
        possibleTypes.add(schema.getType());
      }
      else if (schema.getTypeVariants() != null) {
        possibleTypes.addAll(schema.getTypeVariants());
      }
      else {
        final JsonSchemaType guessedType = schema.guessType();
        if (guessedType != null) {
          possibleTypes.add(guessedType);
        }
      }
    }

    return appendNameTypeAndApi(position, getThirdPartyApiInfo(element, rootSchema), possibleTypes, htmlDescription, preferShort);
  }

  @Nullable
  private static String appendNameTypeAndApi(@NotNull JsonPointerPosition position,
                                             @NotNull String apiInfo,
                                             @NotNull List<JsonSchemaType> possibleTypes,
                                             @Nullable String htmlDescription, boolean preferShort) {
    if (position.size() == 0) return htmlDescription;

    String name = position.getLastName();
    if (name == null) return htmlDescription;

    String type = "";
    String schemaType = JsonSchemaObject.getTypesDescription(false, possibleTypes);
    if (schemaType != null) {
      type = ": " + schemaType;
    }

    if (preferShort) {
      htmlDescription = "<b>" + name + "</b>" + type + apiInfo + (htmlDescription == null ? "" : ("<br/>" + htmlDescription));
    }
    else {
      htmlDescription = DocumentationMarkup.DEFINITION_START + name + type + apiInfo + DocumentationMarkup.DEFINITION_END +
                        (htmlDescription == null ? "" : (DocumentationMarkup.CONTENT_START + htmlDescription + DocumentationMarkup.CONTENT_END));
    }
    return htmlDescription;
  }

  @NotNull
  private static String getThirdPartyApiInfo(@NotNull PsiElement element,
                                             @NotNull JsonSchemaObject rootSchema) {
    JsonSchemaService service = JsonSchemaService.Impl.get(element.getProject());
    String apiInfo = "";
    JsonSchemaFileProvider provider = service.getSchemaProvider(rootSchema.getSchemaFile());
    if (provider != null) {
      String information = provider.getThirdPartyApiInformation();
      if (information != null) {
        apiInfo = "&nbsp;&nbsp;<i>(" + information + ")</i>";
      }
    }
    return apiInfo;
  }

  @Nullable
  public static String getBestDocumentation(boolean preferShort, @NotNull final JsonSchemaObject schema) {
    final String htmlDescription = schema.getHtmlDescription();
    final String description = schema.getDescription();
    final String title = schema.getTitle();
    if (preferShort && !StringUtil.isEmptyOrSpaces(title)) {
      return plainTextPostProcess(title);
    } else if (!StringUtil.isEmptyOrSpaces(htmlDescription)) {
      String desc = htmlDescription;
      if (!StringUtil.isEmptyOrSpaces(title)) desc = plainTextPostProcess(title) + "<br/>" + desc;
      return desc;
    } else if (!StringUtil.isEmptyOrSpaces(description)) {
      String desc = plainTextPostProcess(description);
      if (!StringUtil.isEmptyOrSpaces(title)) desc = plainTextPostProcess(title) + "<br/>" + desc;
      return desc;
    }
    return null;
  }

  @NotNull
  private static String plainTextPostProcess(@NotNull String text) {
    return StringUtil.escapeXmlEntities(text).replace("\\n", "<br/>");
  }

  @Nullable
  @Override
  public PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Object object, PsiElement element) {
    if ((element instanceof JsonProperty || isWhitespaceOrComment(element) && element.getParent() instanceof JsonObject) && object instanceof String) {
      return new FakeDocElement(element instanceof JsonProperty ? ((JsonProperty)element).getNameElement() : element, StringUtil.unquoteString((String)object));
    }
    return null;
  }

  @Nullable
  @Override
  public PsiElement getDocumentationElementForLink(PsiManager psiManager, String link, PsiElement context) {
    return null;
  }

  private static class FakeDocElement extends FakePsiElement {
    private final PsiElement myContextElement;
    private final String myAltName;

    private FakeDocElement(PsiElement context, String name) {
      myContextElement = context;
      myAltName = name;
    }

    @Override
    public PsiElement getParent() {
      return myContextElement;
    }

    @NotNull
    @Override
    public TextRange getTextRangeInParent() {
      return myContextElement.getTextRange().shiftLeft(myContextElement.getTextOffset());
    }
  }
}

@@ -2210,35 +2210,35 @@
  instanceof Fake
-Doc
+Psi
 Element) return 

fix ctrl+hover for package.json packages