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

import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReferenceProvider;
import com.intellij.util.containers.BidirectionalMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.List;

public final class JvmPsiTypeConverterImpl extends JvmPsiTypeConverter implements CustomReferenceConverter<PsiType> {

  private static final BidirectionalMap<PsiType, Character> ourPrimitiveTypes = new BidirectionalMap<>();

  private static final JavaClassReferenceProvider JVM_REFERENCE_PROVIDER = new JavaClassReferenceProvider();
  private static final JavaClassReferenceProvider REFERENCE_PROVIDER = new JavaClassReferenceProvider();
  private static final CanonicalPsiTypeConverterImpl CANONICAL_PSI_TYPE_CONVERTER = new CanonicalPsiTypeConverterImpl();

  static {
    JVM_REFERENCE_PROVIDER.setOption(JavaClassReferenceProvider.JVM_FORMAT, Boolean.TRUE);
    JVM_REFERENCE_PROVIDER.setSoft(true);
    REFERENCE_PROVIDER.setSoft(true);
  }

  static {
    ourPrimitiveTypes.put(PsiTypes.byteType(), 'B');
    ourPrimitiveTypes.put(PsiTypes.charType(), 'C');
    ourPrimitiveTypes.put(PsiTypes.doubleType(), 'D');
    ourPrimitiveTypes.put(PsiTypes.floatType(), 'F');
    ourPrimitiveTypes.put(PsiTypes.intType(), 'I');
    ourPrimitiveTypes.put(PsiTypes.longType(), 'L');
    ourPrimitiveTypes.put(PsiTypes.shortType(), 'S');
    ourPrimitiveTypes.put(PsiTypes.booleanType(), 'Z');
  }

  @Override
  public PsiType fromString(final String s, final @NotNull ConvertContext context) {
    return convertFromString(s, context);
  }

  public static @Nullable PsiType convertFromString(final String s, final ConvertContext context) {
    if (s == null) return null;

    if (s.startsWith("[")) {
      int arrayDimensions = getArrayDimensions(s);

      if (arrayDimensions >= s.length()) {
        return null;
      }

      final char c = s.charAt(arrayDimensions);
      if (c == 'L') {
        if (!s.endsWith(";")) return null;
        final PsiClass aClass =
          DomJavaUtil.findClass(s.substring(arrayDimensions + 1, s.length() - 1), context.getFile(), context.getModule(), null);
        return aClass == null ? null : makeArray(arrayDimensions, createType(aClass));
      }

      if (s.length() == arrayDimensions + 1) {
        final List<PsiType> list = ourPrimitiveTypes.getKeysByValue(c);
        return list == null || list.isEmpty() ? null : makeArray(arrayDimensions, list.get(0));
      }

      return null;
    }
    if (Arrays.binarySearch(CanonicalPsiTypeConverterImpl.PRIMITIVES, s) >= 0) {
      return JavaPsiFacade.getElementFactory(context.getProject()).createPrimitiveType(s);
    }
    final PsiClass aClass1 = DomJavaUtil.findClass(s, context.getFile(), context.getModule(), context.getSearchScope());
    return aClass1 == null ? null : createType(aClass1);
  }

  private static int getArrayDimensions(final String s) {
    int arrayDimensions = 0;

    while (arrayDimensions < s.length() && s.charAt(arrayDimensions) == '[') {
      arrayDimensions++;
    }
    return arrayDimensions;
  }

  private static PsiClassType createType(final PsiClass aClass) {
    return JavaPsiFacade.getElementFactory(aClass.getProject()).createType(aClass);
  }

  private static PsiType makeArray(final int dimensions, final PsiType type) {
    return dimensions == 0 ? type : makeArray(dimensions - 1, new PsiArrayType(type));
  }

  @Override
  public String toString(final PsiType psiType, final @NotNull ConvertContext context) {
    return convertToString(psiType);
  }

  public static @Nullable String convertToString(final PsiType psiType) {
    if (psiType instanceof PsiArrayType) {
      return '[' + toStringArray(((PsiArrayType)psiType).getComponentType());
    }
    else if (psiType instanceof PsiClassType) {
      return psiType.getCanonicalText();
    }
    return null;
  }

  private static @NonNls @Nullable String toStringArray(final PsiType psiType) {
    if (psiType instanceof PsiArrayType) {
      return '[' + toStringArray(((PsiArrayType)psiType).getComponentType());
    }
    else if (psiType instanceof PsiPrimitiveType) {
      return String.valueOf(ourPrimitiveTypes.get(psiType));
    }
    else if (psiType instanceof PsiClassType) {
      return "L" + psiType.getCanonicalText() + ";";
    }
    return null;
  }

  @Override
  public PsiReference @NotNull [] createReferences(GenericDomValue<PsiType> value, PsiElement element, ConvertContext context) {
    final PsiType psiType = value.getValue();
    final String s = value.getStringValue();
    assert s != null;
    final int dimensions = getArrayDimensions(s);
    if (dimensions > 0) {
      if (s.charAt(dimensions) == 'L' && s.endsWith(";")) {
        return JVM_REFERENCE_PROVIDER.getReferencesByString(s.substring(dimensions + 1), element,
                                                            element.getText().indexOf(s) + dimensions + 1);
      }
      if (psiType != null) return PsiReference.EMPTY_ARRAY;
    }
    PsiReference[] references = REFERENCE_PROVIDER.getReferencesByElement(element);
    return references.length == 1 ? CANONICAL_PSI_TYPE_CONVERTER.createReferences(value, element, context) : references;
  }
}
