/*
 *  Copyright 2025-2026 R3944Realms
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package top.r3944realms.eroticdungeongame.content.item;

import com.mojang.datafixers.util.Pair;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.FilteredText;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.*;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.SignText;
import net.minecraft.world.level.block.state.properties.WoodType;
import net.minecraftforge.client.extensions.common.IClientItemExtensions;
import org.jetbrains.annotations.NotNull;
import software.bernie.geckolib.animatable.GeoItem;
import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.core.animation.AnimatableManager;
import software.bernie.geckolib.renderer.GeoArmorRenderer;
import software.bernie.geckolib.util.GeckoLibUtil;
import top.r3944realms.eroticdungeongame.EroticDungeon;
import top.r3944realms.eroticdungeongame.client.renderer.armor.SignArmorRenderer;
import top.r3944realms.lib39.util.nbt.NBTReader;
import top.r3944realms.lib39.util.nbt.NBTWriter;

import javax.annotation.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;

public class SignArmorItem extends ArmorItem implements GeoItem {
    private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this);
    private final WoodType woodType;
    public static String IS_WAXED = "is_waxed";
    public static String TEXT = "text";
    public static String PLAYER_WHO_MAY_EDIT = "player_who_may_edit";

    public SignArmorItem(WoodType woodType) {
        super(ArmorMaterials.CHAIN, Type.CHESTPLATE, new Properties());
        this.woodType = woodType;
    }

    public WoodType getWoodType() {
        return woodType;
    }

    protected SignText createDefaultSignText() {
        return new SignText();
    }

    public SignText getSignText(@NotNull ItemStack stack) {
        CompoundTag tag = stack.getOrCreateTag();
        if (tag.contains(TEXT)) {
            return SignText.DIRECT_CODEC.decode(NbtOps.INSTANCE, tag.get(TEXT))
                    .resultOrPartial(EroticDungeon.LOGGER::error)
                    .map(Pair::getFirst)
                    .orElse(createDefaultSignText());
        }
        return createDefaultSignText();
    }

    public int getTextLineHeight() {
        return 9;
    }

    public int getMaxTextLineWidth() {
        return 60;
    }

    public void save(@NotNull ItemStack stack, SignText signText, boolean isWaxed, @Nullable UUID playerWhoMayEdit) {
        CompoundTag tag = stack.getOrCreateTag();
        SignText.DIRECT_CODEC.encodeStart(NbtOps.INSTANCE, signText)
                .resultOrPartial(EroticDungeon.LOGGER::error)
                .ifPresent((text) -> tag.put(TEXT, text));
        NBTWriter.of(tag)
                .booleanValue(IS_WAXED, isWaxed)
                .uuidValueIf(PLAYER_WHO_MAY_EDIT, playerWhoMayEdit != null, () -> playerWhoMayEdit);
    }

    public boolean updateText(@NotNull ItemStack stack, @NotNull UnaryOperator<SignText> updater) {
        SignText currentText = getSignText(stack);
        SignText newText = updater.apply(currentText);
        return setText(stack, newText);
    }

    public boolean setText(@NotNull ItemStack stack, SignText text) {
        SignText currentText = getSignText(stack);
        if (!text.equals(currentText)) {
            save(stack, text, isWaxed(stack), getPlayerWhoMayEdit(stack));
            return true;
        }
        return false;
    }

    private SignText setMessages(Player player, @NotNull List<FilteredText> filteredText, SignText text) {
        for(int i = 0; i < filteredText.size(); ++i) {
            FilteredText filteredtext = filteredText.get(i);
            Style style = text.getMessage(i, player.isTextFilteringEnabled()).getStyle();
            if (player.isTextFilteringEnabled()) {
                text = text.setMessage(i, Component.literal(filteredtext.filteredOrEmpty()).setStyle(style));
            } else {
                text = text.setMessage(i, Component.literal(filteredtext.raw()).setStyle(style), Component.literal(filteredtext.filteredOrEmpty()).setStyle(style));
            }
        }
        return text;
    }

    public void updateSignText(@NotNull ItemStack stack, Player player, List<FilteredText> filteredText) {
        UUID playerWhoMayEdit = getPlayerWhoMayEdit(stack);
        if (!isWaxed(stack) && (playerWhoMayEdit == null || player.getUUID().equals(playerWhoMayEdit))) {
            SignText currentText = getSignText(stack);
            SignText newText = setMessages(player, filteredText, currentText);
            setText(stack, newText);
            setAllowedPlayerEditor(stack, null);
        } else {
            EroticDungeon.LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString());
        }
    }

    public boolean isWaxed(@NotNull ItemStack stack) {
        CompoundTag tag = stack.getOrCreateTag();
        AtomicBoolean isWax = new AtomicBoolean();
        NBTReader.of(tag).booleanValue(IS_WAXED, isWax::set);
        return isWax.get();
    }

    public void setWaxed(@NotNull ItemStack stack, boolean waxed) {
        CompoundTag tag = stack.getOrCreateTag();
        NBTWriter.of(tag).booleanValue(IS_WAXED, waxed);
    }

    public void setAllowedPlayerEditor(@NotNull ItemStack stack, @Nullable UUID playerWhoMayEdit) {
        CompoundTag tag = stack.getOrCreateTag();
        if (playerWhoMayEdit != null) {
            NBTWriter.of(tag).uuid(PLAYER_WHO_MAY_EDIT, playerWhoMayEdit);
        } else {
            tag.remove(PLAYER_WHO_MAY_EDIT);
        }
    }

    @Nullable
    public UUID getPlayerWhoMayEdit(@NotNull ItemStack stack) {
        CompoundTag tag = stack.getOrCreateTag();
        AtomicReference<UUID> playerWhoMayEdit = new AtomicReference<>();
        NBTReader.of(tag).uuid(PLAYER_WHO_MAY_EDIT, playerWhoMayEdit::set);
        return playerWhoMayEdit.get();
    }

    public void tick(ItemStack stack, Level level, LivingEntity equip) {
        UUID uuid = getPlayerWhoMayEdit(stack);
        if (uuid != null) {
            clearInvalidPlayerWhoMayEdit(stack, equip, level, uuid);
        }
    }

    private void clearInvalidPlayerWhoMayEdit(ItemStack stack, LivingEntity equip, Level level, UUID uuid) {
        if (playerIsTooFarAwayToEdit(equip, level, uuid)) {
            setAllowedPlayerEditor(stack, null);
        }
    }

    public boolean playerIsTooFarAwayToEdit(Entity target, @NotNull Level level, UUID uuid) {
        Player player = level.getPlayerByUUID(uuid);
        return player == null || player.distanceToSqr(target.getX(), target.getY(), target.getZ()) > 64.0;
    }

    @Override
    public void registerControllers(AnimatableManager.ControllerRegistrar controllerRegistrar) {
    }

    @Override
    public AnimatableInstanceCache getAnimatableInstanceCache() {
        return cache;
    }

    @Override
    public void initializeClient(@NotNull Consumer<IClientItemExtensions> consumer) {
        consumer.accept(new IClientItemExtensions() {
            private GeoArmorRenderer<?> renderer;

            @Override
            public @NotNull HumanoidModel<?> getHumanoidArmorModel(LivingEntity livingEntity, ItemStack itemStack, EquipmentSlot equipmentSlot, HumanoidModel<?> original) {
                if (this.renderer == null)
                    this.renderer = new SignArmorRenderer(woodType);
                this.renderer.prepForRender(livingEntity, itemStack, equipmentSlot, original);
                return this.renderer;
            }
        });
    }

    @Override
    public void appendHoverText(@NotNull ItemStack stack, @Nullable Level level, @NotNull List<Component> tooltipComponents, @NotNull TooltipFlag isAdvanced) {
        SignText signText = getSignText(stack);

        // 添加蜡封状态提示
        if (isWaxed(stack)) {
            tooltipComponents.add(Component.translatable("item.eroticdungeongame.sign_armor.waxed")
                    .withStyle(Style.EMPTY.withColor(0xFFAA00)));
        }

        // 添加可编辑者提示
        UUID editorUUID = getPlayerWhoMayEdit(stack);
        if (editorUUID != null && level != null) {
            Player editor = level.getPlayerByUUID(editorUUID);
            if (editor != null) {
                tooltipComponents.add(Component.translatable("item.eroticdungeongame.sign_armor.editable_by", editor.getName())
                        .withStyle(Style.EMPTY.withColor(0x55FF55)));
            }
        }

        // 添加文字内容（非空行才显示）
        boolean hasText = false;
        for (int i = 0; i < 4; i++) {
            Component message = signText.getMessage(i, false);
            if (!message.getString().trim().isEmpty()) {
                if (!hasText) {
                    tooltipComponents.add(Component.empty());
                    tooltipComponents.add(Component.translatable("item.eroticdungeongame.sign_armor.text")
                            .withStyle(Style.EMPTY.withColor(0xAAAAAA)));
                    hasText = true;
                }
                // 显示每行文字，如果有发光效果则添加样式
                MutableComponent line = Component.literal("  " + (i + 1) + ": ").append(message.copy().withStyle(Style.EMPTY.withColor(signText.getColor().getTextColor())));
                if (signText.hasGlowingText()) {
                    line = line.withStyle(Style.EMPTY.withColor(0xFFDD55));
                }
                tooltipComponents.add(line);
            }
        }

        // 如果没有文字内容，显示空白提示
        if (!hasText) {
            tooltipComponents.add(Component.translatable("item.eroticdungeongame.sign_armor.empty")
                    .withStyle(Style.EMPTY.withColor(0x888888)));
        }

        // 显示颜色信息
        if (signText.getColor() != DyeColor.BLACK) {
            tooltipComponents.add(Component.translatable("item.eroticdungeongame.sign_armor.color",
                            Component.translatable("color.minecraft." + signText.getColor().getName()).withStyle(Style.EMPTY.withColor(signText.getColor().getTextColor()))
                    ).withStyle(Style.EMPTY.withColor(0x888888))
            );
        }

        // 显示发光效果提示
        if (signText.hasGlowingText()) {
            tooltipComponents.add(Component.translatable("item.eroticdungeongame.sign_armor.glowing")
                    .withStyle(Style.EMPTY.withColor(0xFFDD55)));
        }
    }

    public static boolean handleSignArmor(Item funcItem, @NotNull ItemStack signArmor, Level level, Entity target) {
        if (signArmor.getItem() instanceof SignArmorItem signArmorItem){
            if (funcItem instanceof DyeItem dyeItem && !Objects.equals(signArmorItem.getSignText(signArmor).getColor(), dyeItem.getDyeColor())) {
                signArmorItem.updateText(signArmor, signText -> signText.setColor(dyeItem.getDyeColor()));
                level.playSound(null, target.getOnPos(), SoundEvents.DYE_USE, SoundSource.BLOCKS, 1.0F, 1.0F);
                return true;
            } else if (funcItem instanceof HoneycombItem honeycombItem && !signArmorItem.isWaxed(signArmor)) {
                signArmorItem.setWaxed(signArmor, true);
                return true;
            } else if (funcItem instanceof InkSacItem inkSacItem && signArmorItem.getSignText(signArmor).hasGlowingText()) {
                signArmorItem.updateText(signArmor, signText -> signText.setHasGlowingText(false));
                level.playSound(null, target.getOnPos(), SoundEvents.INK_SAC_USE, SoundSource.BLOCKS, 1.0F, 1.0F);
                return true;
            } else if (funcItem instanceof GlowInkSacItem inkSacItem && !signArmorItem.getSignText(signArmor).hasGlowingText()) {
                signArmorItem.updateText(signArmor, signText -> signText.setHasGlowingText(true));
                level.playSound(null, target.getOnPos(), SoundEvents.INK_SAC_USE, SoundSource.BLOCKS, 1.0F, 1.0F);
                return true;
            }
        }
        return false;
    }
}