/*
 *  Super Lead rope mod
 *  Copyright (C)  2025  R3944Realms
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package top.r3944realms.lib39.util.storage.nbt;

import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.world.item.component.ResolvableProfile;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;

import java.util.Optional;
import java.util.function.Consumer;

/**
 * The type Nbt reader.
 */
@SuppressWarnings("unused")
public class NBTReader {
    private final CompoundTag nbt;

    private NBTReader(CompoundTag nbt) {
        this.nbt = nbt;
    }

    /**
     * 从CompoundTag创建读取器
     *
     * @param nbt the nbt
     * @return the nbt reader
     */
    @NotNull
    public static NBTReader of(@NotNull CompoundTag nbt) {
        return new NBTReader(nbt);
    }
    /**
     * 从CompoundTag创建读取器
     *
     * @param nbt the nbt opt
     * @return the nbt reader
     */
    @NotNull
    public static NBTReader ofOpt(@NotNull Optional<CompoundTag> nbt) {
        return new NBTReader(nbt.orElse(new CompoundTag()));
    }
    @Contract(pure = true)
    private static boolean checkOpt(@NonNull Optional<?> nbt) {
        return nbt.isPresent();
    }
    @Contract(pure = true)
    private static boolean checkOpt(Optional<?> @NonNull ... nbts) {
        for (Optional<?> nbt : nbts) {
            if (nbt.isEmpty()) {
                return false;
            }
        }
        return true;
    }


    /**
     * String nbt reader with default value.
     *
     * @param key          the key
     * @param setter       the setter
     * @param defaultValue the default value
     * @return the nbt reader
     */
    public NBTReader string(String key, @NotNull Consumer<String> setter, String defaultValue) {
        if (nbt.contains(key)) {
            Optional<String> value = nbt.getString(key);
            if (checkOpt(value)) {
                setter.accept(value.get());
                return this;
            }
        }
        setter.accept(defaultValue);
        return this;
    }

    /**
     * Byte value nbt reader.
     *
     * @param key    the key
     * @param setter the setter
     * @return the nbt reader
     */
    public NBTReader byteValue(String key, Consumer<Byte> setter) {
        if (nbt.contains(key)) {
            Optional<Byte> value = nbt.getByte(key);
            if (checkOpt(value)) setter.accept(value.get());
        }
        return this;
    }

    /**
     * Byte value nbt reader with default value.
     *
     * @param key          the key
     * @param setter       the setter
     * @param defaultValue the default value
     * @return the nbt reader
     */
    public NBTReader byteValue(String key, @NotNull Consumer<Byte> setter, byte defaultValue) {
        if (nbt.contains(key)) {
            Optional<Byte> value = nbt.getByte(key);
            if (checkOpt(value)) {
                setter.accept(value.get());
                return this;
            }
        }
        setter.accept(defaultValue);
        return this;
    }

    /**
     * Short value nbt reader.
     *
     * @param key    the key
     * @param setter the setter
     * @return the nbt reader
     */
    public NBTReader shortValue(String key, Consumer<Short> setter) {
        if (nbt.contains(key)) {
            Optional<Short> value = nbt.getShort(key);
            if (checkOpt(value)) setter.accept(value.get());
        }
        return this;
    }

    /**
     * Short value nbt reader with default value.
     *
     * @param key          the key
     * @param setter       the setter
     * @param defaultValue the default value
     * @return the nbt reader
     */
    public NBTReader shortValue(String key, @NotNull Consumer<Short> setter, short defaultValue) {
        if (nbt.contains(key)) {
            Optional<Short> value = nbt.getShort(key);
            if (checkOpt(value)) {
                setter.accept(value.get());
                return this;
            }
        }
        setter.accept(defaultValue);
        return this;
    }

    /**
     * Int value nbt reader.
     *
     * @param key    the key
     * @param setter the setter
     * @return the nbt reader
     */
    public NBTReader intValue(String key, Consumer<Integer> setter) {
        if (nbt.contains(key)) {
            Optional<Integer> value = nbt.getInt(key);
            if (checkOpt(value)) setter.accept(value.get());
        }
        return this;
    }

    /**
     * Int value nbt reader with default value.
     *
     * @param key          the key
     * @param setter       the setter
     * @param defaultValue the default value
     * @return the nbt reader
     */
    public NBTReader intValue(String key, @NotNull Consumer<Integer> setter, int defaultValue) {
        if (nbt.contains(key)) {
            Optional<Integer> value = nbt.getInt(key);
            if (checkOpt(value)) {
                setter.accept(value.get());
                return this;
            }
        }
        setter.accept(defaultValue);
        return this;
    }

    /**
     * Long value nbt reader.
     *
     * @param key    the key
     * @param setter the setter
     * @return the nbt reader
     */
    public NBTReader longValue(String key, Consumer<Long> setter) {
        if (nbt.contains(key)) {
            Optional<Long> value = nbt.getLong(key);
            if (checkOpt(value)) setter.accept(value.get());
        }
        return this;
    }

    /**
     * Long value nbt reader with default value.
     *
     * @param key          the key
     * @param setter       the setter
     * @param defaultValue the default value
     * @return the nbt reader
     */
    public NBTReader longValue(String key, @NotNull Consumer<Long> setter, long defaultValue) {
        if (nbt.contains(key)) {
            Optional<Long> value = nbt.getLong(key);
            if (checkOpt(value)) {
                setter.accept(value.get());
                return this;
            }
        }
        setter.accept(defaultValue);
        return this;
    }

    /**
     * Float value nbt reader.
     *
     * @param key    the key
     * @param setter the setter
     * @return the nbt reader
     */
    public NBTReader floatValue(String key, Consumer<Float> setter) {
        if (nbt.contains(key)) {
            Optional<Float> value = nbt.getFloat(key);
            if (checkOpt(value)) setter.accept(value.get());
        }
        return this;
    }

    /**
     * Float value nbt reader with default value.
     *
     * @param key          the key
     * @param setter       the setter
     * @param defaultValue the default value
     * @return the nbt reader
     */
    public NBTReader floatValue(String key, @NotNull Consumer<Float> setter, float defaultValue) {
        if (nbt.contains(key)) {
            Optional<Float> value = nbt.getFloat(key);
            if (checkOpt(value)) {
                setter.accept(value.get());
                return this;
            }
        }
        setter.accept(defaultValue);
        return this;
    }

    /**
     * Double value nbt reader.
     *
     * @param key    the key
     * @param setter the setter
     * @return the nbt reader
     */
    public NBTReader doubleValue(String key, Consumer<Double> setter) {
        if (nbt.contains(key)) {
            Optional<Double> value = nbt.getDouble(key);
            if (checkOpt(value)) setter.accept(value.get());
        }
        return this;
    }

    /**
     * Double value nbt reader with default value.
     *
     * @param key          the key
     * @param setter       the setter
     * @param defaultValue the default value
     * @return the nbt reader
     */
    public NBTReader doubleValue(String key, @NotNull Consumer<Double> setter, double defaultValue) {
        if (nbt.contains(key)) {
            Optional<Double> value = nbt.getDouble(key);
            if (checkOpt(value)) {
                setter.accept(value.get());
                return this;
            }
        }
        setter.accept(defaultValue);
        return this;
    }

    /**
     * Boolean value nbt reader.
     *
     * @param key    the key
     * @param setter the setter
     * @return the nbt reader
     */
    public NBTReader booleanValue(String key, Consumer<Boolean> setter) {
        if (nbt.contains(key)) {
            Optional<Boolean> value = nbt.getBoolean(key);
            if (checkOpt(value)) setter.accept(value.get());
        }
        return this;
    }

    /**
     * Boolean value nbt reader with default value.
     *
     * @param key          the key
     * @param setter       the setter
     * @param defaultValue the default value
     * @return the nbt reader
     */
    public NBTReader booleanValue(String key, @NotNull Consumer<Boolean> setter, boolean defaultValue) {
        if (nbt.contains(key)) {
            Optional<Boolean> value = nbt.getBoolean(key);
            if (checkOpt(value)) {
                setter.accept(value.get());
                return this;
            }
        }
        setter.accept(defaultValue);
        return this;
    }

    /**
     * Byte array nbt reader.
     *
     * @param key    the key
     * @param setter the setter
     * @return the nbt reader
     */
    public NBTReader byteArray(String key, Consumer<byte[]> setter) {
        if (nbt.contains(key)) {
            Optional<byte[]> value = nbt.getByteArray(key);
            if (checkOpt(value)) setter.accept(value.get());
        }
        return this;
    }

    /**
     * Int array nbt reader.
     *
     * @param key    the key
     * @param setter the setter
     * @return the nbt reader
     */
    public NBTReader intArray(String key, Consumer<int[]> setter) {
        if (nbt.contains(key)) {
            Optional<int[]> value = nbt.getIntArray(key);
            if (checkOpt(value)) setter.accept(value.get());
        }
        return this;
    }

    /**
     * Long array nbt reader.
     *
     * @param key    the key
     * @param setter the setter
     * @return the nbt reader
     */
    public NBTReader longArray(String key, Consumer<long[]> setter) {
        if (nbt.contains(key)) {
            Optional<long[]> value = nbt.getLongArray(key);
            if (checkOpt(value)) setter.accept(value.get());
        }
        return this;
    }

    /**
     * Compound nbt reader.
     *
     * @param key    the key
     * @param setter the setter
     * @return the nbt reader
     */
    public NBTReader compound(String key, Consumer<CompoundTag> setter) {
        if (nbt.contains(key)) {
            Optional<CompoundTag> value = nbt.getCompound(key);
            if (checkOpt(value)) setter.accept(value.get());
        }
        return this;
    }

    /**
     * Compound nbt reader with default value.
     *
     * @param key          the key
     * @param setter       the setter
     * @param defaultValue the default value
     * @return the nbt reader
     */
    public NBTReader compound(String key, @NotNull Consumer<CompoundTag> setter, CompoundTag defaultValue) {
        if (nbt.contains(key)) {
            Optional<CompoundTag> value = nbt.getCompound(key);
            if (checkOpt(value)) {
                setter.accept(value.get());
                return this;
            }
        }
        setter.accept(defaultValue);
        return this;
    }

    /**
     * List nbt reader.
     *
     * @param key    the key
     * @param setter the setter
     * @return the nbt reader
     */
    public NBTReader list(String key, Consumer<ListTag> setter) {
        if (nbt.contains(key)) {
            Optional<ListTag> value = nbt.getList(key);
            if (checkOpt(value)) setter.accept(value.get());
        }
        return this;
    }

    public NBTReader gameProfile(String key, Consumer<ResolvableProfile> setter) {
        Optional<ResolvableProfile> read = nbt.read(key, ResolvableProfile.CODEC);
        if (read.isPresent()) {
            try {
                ResolvableProfile profile = read.get();
                setter.accept(profile);
                return this;
            } catch (IllegalArgumentException ignored) {

            }
        }
        return this;
    }

    public NBTReader gameProfile(String key, Consumer<ResolvableProfile> setter, ResolvableProfile defaultValue) {
        Optional<ResolvableProfile> read = nbt.read(key, ResolvableProfile.CODEC);
        if (read.isPresent()) {
            try {
                ResolvableProfile profile = read.get();
                setter.accept(profile);
                return this;
            } catch (IllegalArgumentException ignored) {

            }
        }
        setter.accept(defaultValue);
        return this;
    }
    /**
     * Vec 3 nbt reader.
     *
     * @param key    the key
     * @param setter the setter
     * @return the nbt reader
     */
    public NBTReader vec3(String key, Consumer<Vec3> setter) {
        if (nbt.contains(key)) {
            Optional<CompoundTag> vecTag = nbt.getCompound(key);
            if (checkOpt(vecTag)) {
                CompoundTag vec = vecTag.get();
                if (vec.contains("X") && vec.contains("Y") && vec.contains("Z")) {
                    Optional<Double> x = vec.getDouble("X");
                    Optional<Double> y = vec.getDouble("Y");
                    Optional<Double> z = vec.getDouble("Z");
                    if (checkOpt(x, y, z)) {
                        Vec3 value = new Vec3(
                                x.get(),
                                y.get(),
                                z.get()
                        );
                        setter.accept(value);
                    }
                }
            }
        }
        return this;
    }

    /**
     * Vec 3 nbt reader with default value.
     *
     * @param key          the key
     * @param setter       the setter
     * @param defaultValue the default value
     * @return the nbt reader
     */
    public NBTReader vec3(String key, Consumer<Vec3> setter, Vec3 defaultValue) {
        if (nbt.contains(key)) {
            Optional<CompoundTag> vecTag = nbt.getCompound(key);
            if (checkOpt(vecTag)) {
                CompoundTag vec = vecTag.get();
                if (vec.contains("X") && vec.contains("Y") && vec.contains("Z")) {
                    Optional<Double> x = vec.getDouble("X");
                    Optional<Double> y = vec.getDouble("Y");
                    Optional<Double> z = vec.getDouble("Z");
                    if (checkOpt(x, y, z)) {
                        Vec3 value = new Vec3(
                                x.get(),
                                y.get(),
                                z.get()
                        );
                        setter.accept(value);
                        return this;
                    }
                }
            }
        }
        setter.accept(defaultValue);
        return this;
    }

    /**
     * Enum value nbt reader.
     *
     * @param <T>       the type parameter
     * @param key       the key
     * @param enumClass the enum class
     * @param setter    the setter
     * @return the nbt reader
     */
    public <T extends Enum<T>> NBTReader enumValue(String key, Class<T> enumClass, Consumer<T> setter) {
        if (nbt.contains(key)) {
            Optional<String> value = nbt.getString(key);
            if(checkOpt(value)) {
                try {
                    T enumValue = Enum.valueOf(enumClass, value.get().toUpperCase());
                    setter.accept(enumValue);
                } catch (IllegalArgumentException ignored) {
                    // 保持setter的当前值
                }
            }

        }
        return this;
    }

    /**
     * Enum value nbt reader with default value.
     *
     * @param <T>          the type parameter
     * @param key          the key
     * @param enumClass    the enum class
     * @param setter       the setter
     * @param defaultValue the default value
     * @return the nbt reader
     */
    public <T extends Enum<T>> NBTReader enumValue(String key, Class<T> enumClass, Consumer<T> setter, T defaultValue) {
        if (nbt.contains(key)) {
            Optional<String> value = nbt.getString(key);
            if (checkOpt(value)) {
                try {
                    T enumValue = Enum.valueOf(enumClass, value.get().toUpperCase());
                    setter.accept(enumValue);
                    return this;
                } catch (IllegalArgumentException ignored) {
                    // 保持setter的当前值
                }
            }
        }
        setter.accept(defaultValue);
        return this;
    }

    /**
     * Nested nbt reader.
     *
     * @param key      the key
     * @param consumer the consumer
     * @return the nbt reader
     */
    public NBTReader nested(String key, Consumer<NBTReader> consumer) {
        if (nbt.contains(key)) {
            Optional<CompoundTag> compound = nbt.getCompound(key);
            if (checkOpt(compound)) {
                consumer.accept(new NBTReader(compound.get()));
            }
        }
        return this;
    }

    /**
     * Nested nbt reader with orElse.
     *
     * @param key      the key
     * @param consumer the consumer
     * @param orElse   the or else
     * @return the nbt reader
     */
    public NBTReader nested(String key, Consumer<NBTReader> consumer, Runnable orElse) {
        if (nbt.contains(key)) {
            Optional<CompoundTag> compound = nbt.getCompound(key);
            if (checkOpt(compound)) {
                consumer.accept(new NBTReader(compound.get()));
            }
        } else {
            orElse.run();
        }
        return this;
    }

    /**
     * If present nbt reader.
     *
     * @param key    the key
     * @param action the action
     * @return the nbt reader
     */
    public NBTReader ifPresent(String key, Runnable action) {
        if (nbt.contains(key)) {
            action.run();
        }
        return this;
    }

    /**
     * If absent nbt reader.
     *
     * @param key    the key
     * @param action the action
     * @return the nbt reader
     */
    public NBTReader ifAbsent(String key, Runnable action) {
        if (!nbt.contains(key)) {
            action.run();
        }
        return this;
    }

    /**
     * Gets raw.
     *
     * @return the raw
     */
    @NotNull
    public CompoundTag getRaw() {
        return nbt;
    }

    /**
     * Read vec 3 vec 3.
     *
     * @param nbt the nbt
     * @return the vec 3
     */
    @NotNull
    public static Vec3 readVec3(@NotNull CompoundTag nbt) {
        if (nbt.contains("X") && nbt.contains("Y") && nbt.contains("Z")) {
            Optional<Double> x = nbt.getDouble("X");
            Optional<Double> y = nbt.getDouble("Y");
            Optional<Double> z = nbt.getDouble("Z");
            if (checkOpt(x, y, z)) {
                return new Vec3(
                        x.get(),
                        y.get(),
                        z.get()
                );
            } else throw new IllegalArgumentException(
                    "XYZ is null."
            );
        } else {
            throw new IllegalArgumentException("NBT is missing X, Y, or Z value for Vec3");
        }
    }

    /**
     * Read vec 3 safe vec 3.
     *
     * @param nbt the nbt
     * @return the vec 3
     */
    @Nullable
    public static Vec3 readVec3Safe(@NotNull CompoundTag nbt) {
        if (nbt.contains("X") && nbt.contains("Y") && nbt.contains("Z")) {
            Optional<Double> x = nbt.getDouble("X");
            Optional<Double> y = nbt.getDouble("Y");
            Optional<Double> z = nbt.getDouble("Z");
            if (checkOpt(x, y, z)) {
                return new Vec3(
                        x.get(),
                        y.get(),
                        z.get()
                );
            }
        }
        return null;
    }
}
