package com.huangzj.protoplugin.json;

import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.PsiArrayType;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiEnumConstant;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypes;
import com.intellij.psi.PsiWildcardType;
import java.util.HashSet;
import java.util.Set;
import org.jetbrains.annotations.Nullable;

/** 根据 {@link PsiClass} 生成带类型默认值的 JSON 文本。 */
public final class JavaClassToJsonBuilder {

    private JavaClassToJsonBuilder() {}

    public static String buildPrettyJson(PsiClass psiClass) {
        JsonObject root = new JsonObject();
        for (PsiField field : psiClass.getAllFields()) {
            if (field.hasModifierProperty(PsiModifier.STATIC)) {
                continue;
            }
            if (field instanceof PsiEnumConstant) {
                continue;
            }
            if ("serialVersionUID".equals(field.getName())) {
                continue;
            }
            JsonElement value =
                    typeToDefaultJson(field.getType(), new HashSet<>(), psiClass);
            root.add(field.getName(), value);
        }
        return new GsonBuilder().setPrettyPrinting().serializeNulls().create().toJson(root);
    }

    private static JsonElement typeToDefaultJson(
            PsiType type, Set<String> visitingQualifiedNames, @Nullable PsiClass contextClass) {
        if (type == null) {
            return JsonNull.INSTANCE;
        }
        if (type instanceof PsiWildcardType) {
            PsiType bound = ((PsiWildcardType) type).getExtendsBound();
            if (bound != null) {
                return typeToDefaultJson(bound, visitingQualifiedNames, contextClass);
            }
            PsiType superBound = ((PsiWildcardType) type).getSuperBound();
            if (superBound != null) {
                return typeToDefaultJson(superBound, visitingQualifiedNames, contextClass);
            }
            return JsonNull.INSTANCE;
        }
        if (type instanceof PsiArrayType) {
            return new JsonArray();
        }
        if (type instanceof PsiPrimitiveType) {
            return primitiveDefault((PsiPrimitiveType) type);
        }
        if (!(type instanceof PsiClassType)) {
            return JsonNull.INSTANCE;
        }
        PsiClassType classType = (PsiClassType) type;
        PsiClass resolved = classType.resolve();
        if (resolved == null) {
            return JsonNull.INSTANCE;
        }
        String qn = resolved.getQualifiedName();
        if (qn != null && qn.equals("java.lang.Object")) {
            return JsonNull.INSTANCE;
        }
        if (resolved.isEnum()) {
            for (PsiField f : resolved.getFields()) {
                if (f instanceof PsiEnumConstant) {
                    return new JsonPrimitive(f.getName());
                }
            }
            return JsonNull.INSTANCE;
        }
        if (qn != null && qn.startsWith("java.")) {
            return javaLangDefault(classType, resolved, visitingQualifiedNames, contextClass);
        }
        if (InheritanceUtil.isInheritor(resolved, "java.util.Collection")) {
            return new JsonArray();
        }
        if (InheritanceUtil.isInheritor(resolved, "java.util.Map")) {
            return new JsonObject();
        }
        if (qn == null) {
            return JsonNull.INSTANCE;
        }
        if (visitingQualifiedNames.contains(qn)) {
            return JsonNull.INSTANCE;
        }
        visitingQualifiedNames.add(qn);
        try {
            JsonObject obj = new JsonObject();
            for (PsiField field : resolved.getAllFields()) {
                if (field.hasModifierProperty(PsiModifier.STATIC)) {
                    continue;
                }
                if (field instanceof PsiEnumConstant) {
                    continue;
                }
                if ("serialVersionUID".equals(field.getName())) {
                    continue;
                }
                obj.add(
                        field.getName(),
                        typeToDefaultJson(field.getType(), visitingQualifiedNames, resolved));
            }
            return obj;
        } finally {
            visitingQualifiedNames.remove(qn);
        }
    }

    private static JsonElement primitiveDefault(PsiPrimitiveType p) {
        if (p.equals(PsiTypes.booleanType())) {
            return new JsonPrimitive(false);
        }
        if (p.equals(PsiTypes.charType())) {
            return new JsonPrimitive("\u0000");
        }
        if (p.equals(PsiTypes.floatType()) || p.equals(PsiTypes.doubleType())) {
            return new JsonPrimitive(0.0);
        }
        if (p.equals(PsiTypes.longType())) {
            return new JsonPrimitive(0L);
        }
        if (p.equals(PsiTypes.intType())
                || p.equals(PsiTypes.shortType())
                || p.equals(PsiTypes.byteType())) {
            return new JsonPrimitive(0);
        }
        return JsonNull.INSTANCE;
    }

    private static JsonElement javaLangDefault(
            PsiClassType classType,
            PsiClass resolved,
            Set<String> visitingQualifiedNames,
            @Nullable PsiClass contextClass) {
        String qn = resolved.getQualifiedName();
        if (qn == null) {
            return JsonNull.INSTANCE;
        }
        switch (qn) {
            case "java.lang.String":
                return new JsonPrimitive("");
            case "java.lang.Boolean":
                return new JsonPrimitive(false);
            case "java.lang.Character":
                return new JsonPrimitive("\u0000");
            case "java.lang.Byte":
            case "java.lang.Short":
            case "java.lang.Integer":
                return new JsonPrimitive(0);
            case "java.lang.Long":
                return new JsonPrimitive(0L);
            case "java.lang.Float":
            case "java.lang.Double":
                return new JsonPrimitive(0.0);
            case "java.math.BigDecimal":
            case "java.math.BigInteger":
                return new JsonPrimitive("0");
            case "java.util.UUID":
                return new JsonPrimitive("00000000-0000-0000-0000-000000000000");
            case "java.util.Optional":
                {
                    PsiType[] p = classType.getParameters();
                    if (p.length == 1) {
                        return typeToDefaultJson(p[0], visitingQualifiedNames, contextClass);
                    }
                    return JsonNull.INSTANCE;
                }
            default:
                if (InheritanceUtil.isInheritor(resolved, "java.util.Collection")) {
                    return new JsonArray();
                }
                if (InheritanceUtil.isInheritor(resolved, "java.util.Map")) {
                    return new JsonObject();
                }
                if (qn.startsWith("java.time.")
                        || "java.util.Date".equals(qn)
                        || "java.util.Calendar".equals(qn)) {
                    return new JsonPrimitive("");
                }
                if (Number.class.getName().equals(qn)) {
                    return new JsonPrimitive(0);
                }
                return JsonNull.INSTANCE;
        }
    }
}
