package net.minecraft.world.item.component;

import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.PropertyMap;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.netty.buffer.ByteBuf;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nullable;
import net.minecraft.Util;
import net.minecraft.core.UUIDUtil;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.world.level.block.entity.SkullBlockEntity;

public record ResolvableProfile(Optional<String> name, Optional<UUID> id, PropertyMap properties, GameProfile gameProfile) {
    private static final Codec<ResolvableProfile> FULL_CODEC = RecordCodecBuilder.create(
        instance -> instance.group(
                ExtraCodecs.PLAYER_NAME.optionalFieldOf("name").forGetter(ResolvableProfile::name),
                UUIDUtil.CODEC.optionalFieldOf("id").forGetter(ResolvableProfile::id),
                UUIDUtil.STRING_CODEC.lenientOptionalFieldOf("Id").forGetter($ -> Optional.empty()), // Paper
                ExtraCodecs.PROPERTY_MAP.optionalFieldOf("properties", new PropertyMap()).forGetter(ResolvableProfile::properties)
            )
            .apply(instance, (name, uuid, uuid2, propertyMap) -> new ResolvableProfile(name, uuid2.or(() -> uuid), propertyMap)) // Paper
    );
    public static final Codec<ResolvableProfile> CODEC = Codec.withAlternative(
        FULL_CODEC, ExtraCodecs.PLAYER_NAME, name -> new ResolvableProfile(Optional.of(name), Optional.empty(), new PropertyMap())
    );
    public static final StreamCodec<ByteBuf, ResolvableProfile> STREAM_CODEC = StreamCodec.composite(
        ByteBufCodecs.stringUtf8(16).apply(ByteBufCodecs::optional),
        ResolvableProfile::name,
        UUIDUtil.STREAM_CODEC.apply(ByteBufCodecs::optional),
        ResolvableProfile::id,
        ByteBufCodecs.GAME_PROFILE_PROPERTIES,
        ResolvableProfile::properties,
        ResolvableProfile::new
    );

    public ResolvableProfile(Optional<String> name, Optional<UUID> id, PropertyMap properties) {
        this(name, id, properties, createGameProfile(id, name, properties));
    }

    public ResolvableProfile(GameProfile profile) {
        this(Optional.of(profile.getName()), Optional.of(profile.getId()), profile.getProperties(), profile);
    }

    @Nullable
    public ResolvableProfile pollResolve() {
        if (this.isResolved()) {
            return this;
        } else {
            Optional<GameProfile> optional;
            if (this.id.isPresent()) {
                optional = SkullBlockEntity.fetchGameProfile(this.id.get(), this.name.orElse(null)).getNow(null); // Paper - player profile events - pass name
            } else {
                optional = SkullBlockEntity.fetchGameProfile(this.name.orElseThrow()).getNow(null);
            }

            return optional != null ? this.createProfile(optional) : null;
        }
    }

    public CompletableFuture<ResolvableProfile> resolve() {
        if (this.isResolved()) {
            return CompletableFuture.completedFuture(this);
        } else {
            return this.id.isPresent()
                ? SkullBlockEntity.fetchGameProfile(this.id.get(), this.name.orElse(null)).thenApply(this::createProfile)  // Paper - player profile events - pass name
                : SkullBlockEntity.fetchGameProfile(this.name.orElseThrow()).thenApply(this::createProfile);
        }
    }

    private ResolvableProfile createProfile(Optional<GameProfile> optional) {
        return new ResolvableProfile(optional.orElseGet(() -> createGameProfile(this.id, this.name)));
    }

    private static GameProfile createGameProfile(Optional<UUID> optional, Optional<String> optional1) {
        return new GameProfile(optional.orElse(Util.NIL_UUID), optional1.orElse(""));
    }

    private static GameProfile createGameProfile(Optional<UUID> optional, Optional<String> optional1, PropertyMap propertyMap) {
        GameProfile gameProfile = createGameProfile(optional, optional1);
        gameProfile.getProperties().putAll(propertyMap);
        return gameProfile;
    }

    public boolean isResolved() {
        return !this.properties.isEmpty() || this.id.isPresent() == this.name.isPresent();
    }
}
