package top.r3944realms.lib39.datagen.provider.subprovider;

import net.minecraft.advancements.critereon.ItemPredicate;
import net.minecraft.data.loot.BlockLootSubProvider;
import net.minecraft.world.flag.FeatureFlags;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.FlowerPotBlock;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.storage.loot.LootPool;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.level.storage.loot.entries.LootItem;
import net.minecraft.world.level.storage.loot.functions.ApplyBonusCount;
import net.minecraft.world.level.storage.loot.functions.SetItemCountFunction;
import net.minecraft.world.level.storage.loot.predicates.LootItemBlockStatePropertyCondition;
import net.minecraft.world.level.storage.loot.predicates.MatchTool;
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
import net.minecraft.world.level.storage.loot.providers.number.UniformGenerator;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.RegistryObject;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * The type Block loot tables.
 */
@SuppressWarnings("unused")
public class BlockLootTables extends BlockLootSubProvider {

    private final List<BlockEntry> blockEntries = new ArrayList<>();
    private final DeferredRegister<Block> knowBlocks;

    /**
     * Instantiates a new Block loot tables.
     *
     * @param deferredRegister the deferred register
     */
    public BlockLootTables(DeferredRegister<Block> deferredRegister) {
        super(Set.of(), FeatureFlags.REGISTRY.allFlags());
        knowBlocks = deferredRegister;
    }

    @Override
    protected @NotNull Iterable<Block> getKnownBlocks() {
        return knowBlocks.getEntries().stream().map(RegistryObject::get).collect(Collectors.toSet());
    }

    // ==================== 流畅 API 构建方法 ====================

    /**
     * 添加自掉落的方块
     *
     * @param block the block
     */
    public void dropSelf(RegistryObject<Block> block) {
        addEntry(block, this::createSingleItemTable);
    }

    /**
     * 批量添加自掉落的方块
     *
     * @param blocks the blocks
     * @return the block loot tables
     */
    @Contract("_ -> this")
    @SafeVarargs
    public final BlockLootTables dropSelf(RegistryObject<Block> @NotNull ... blocks) {
        for (RegistryObject<Block> block : blocks) {
            dropSelf(block);
        }
        return this;
    }

    /**
     * 添加需要丝绸之触才掉落的方块
     *
     * @param block the block
     * @return the block loot tables
     */
    public BlockLootTables dropWhenSilkTouch(RegistryObject<Block> block) {
        return addEntry(block, BlockLootSubProvider::createSilkTouchOnlyTable);
    }

    /**
     * 添加掉落其他物品的方块
     *
     * @param block the block
     * @param item  the item
     * @return the block loot tables
     */
    public BlockLootTables dropOther(RegistryObject<Block> block, RegistryObject<? extends ItemLike> item) {
        return addEntry(block, pBlock -> this.createSingleItemTable(item.get()));
    }

    /**
     * 添加只能被剪子剪下的方块
     *
     * @param block the block
     * @return the block loot tables
     */
    public BlockLootTables dropWhenShears(RegistryObject<Block> block) {
        return addEntry(block, BlockLootSubProvider::createShearsOnlyDrop);
    }

    /**
     * 添加矿物的掉落表（支持时运附魔）
     *
     * @param block   the block
     * @param oreItem the ore item
     * @return the block loot tables
     */
    public BlockLootTables dropOre(RegistryObject<Block> block, RegistryObject<Item> oreItem) {
        return addEntry(block, b -> this.createOreDrop(b, oreItem.get()));
    }

    /**
     * 添加红石矿石掉落表
     *
     * @param block the block
     * @return the block loot tables
     */
    public BlockLootTables dropRedstoneOre(RegistryObject<Block> block) {
        return addEntry(block, this::createRedstoneOreDrops);
    }

    /**
     * 添加青金石矿石掉落表
     *
     * @param block the block
     * @return the block loot tables
     */
    public BlockLootTables dropLapisOre(RegistryObject<Block> block) {
        return addEntry(block, this::createLapisOreDrops);
    }

    /**
     * 添加铜矿石掉落表
     *
     * @param block the block
     * @return the block loot tables
     */
    public BlockLootTables dropCopperOre(RegistryObject<Block> block) {
        return addEntry(block, this::createCopperOreDrops);
    }

    /**
     * 添加地毯类方块的掉落（一次掉落2个）
     *
     * @param block the block
     * @return the block loot tables
     */
    public BlockLootTables dropCarpet(RegistryObject<Block> block) {
        return addEntry(block, b -> LootTable.lootTable()
                .withPool(LootPool.lootPool()
                        .setRolls(ConstantValue.exactly(1))
                        .add(LootItem.lootTableItem(b)
                                .apply(SetItemCountFunction.setCount(ConstantValue.exactly(2.0F))))));
    }

    /**
     * 添加台阶方块的掉落
     *
     * @param block the block
     * @return the block loot tables
     */
    public BlockLootTables dropSlab(RegistryObject<Block> block) {
        return addEntry(block, this::createSlabItemTable);
    }

    /**
     * 添加门方块的掉落（只掉落下半部分）
     *
     * @param block the block
     * @return the block loot tables
     */
    public BlockLootTables dropDoor(RegistryObject<Block> block) {
        return addEntry(block, this::createDoorTable);
    }

    /**
     * 添加花盆的掉落
     *
     * @param block the block
     * @return the block loot tables
     */
    public BlockLootTables dropFlowerPot(RegistryObject<Block> block) {
        return addEntry(block, (pBlock) -> this.createPotFlowerItemTable(((FlowerPotBlock)pBlock).getContent()));
    }

    /**
     * 添加树叶的掉落
     *
     * @param leavesBlock  the leaves block
     * @param saplingBlock the sapling block
     * @param chances      the chances
     * @return the block loot tables
     */
    public BlockLootTables dropLeaves(RegistryObject<Block> leavesBlock,
                                      RegistryObject<Block> saplingBlock,
                                      float... chances) {
        return addEntry(leavesBlock, b -> this.createLeavesDrops(b, saplingBlock.get(), chances));
    }

    /**
     * 添加橡树叶的掉落（包含苹果）
     *
     * @param leavesBlock  the leaves block
     * @param saplingBlock the sapling block
     * @param chances      the chances
     * @return the block loot tables
     */
    public BlockLootTables dropOakLeaves(RegistryObject<Block> leavesBlock,
                                         RegistryObject<Block> saplingBlock,
                                         float... chances) {
        return addEntry(leavesBlock, b -> this.createOakLeavesDrops(b, saplingBlock.get(), chances));
    }

    /**
     * 添加农作物的掉落
     *
     * @param cropBlock   the crop block
     * @param cropItem    the crop item
     * @param seedsItem   the seeds item
     * @param ageProperty the age property
     * @param maxAge      the max age
     * @return the block loot tables
     */
    public BlockLootTables dropCrop(RegistryObject<Block> cropBlock,
                                    RegistryObject<Item> cropItem,
                                    RegistryObject<Item> seedsItem,
                                    Property<Integer> ageProperty,
                                    int maxAge) {
        return addEntry(cropBlock, b -> this.createCropDrops(
                b,
                cropItem.get(),
                seedsItem.get(),
                LootItemBlockStatePropertyCondition.hasBlockStateProperties(b)
                        .setProperties(net.minecraft.advancements.critereon.StatePropertiesPredicate.Builder.properties()
                                .hasProperty(ageProperty, maxAge))
        ));
    }

    /**
     * 自定义掉落表
     *
     * @param block   the block
     * @param factory the factory
     * @return the block loot tables
     */
    public BlockLootTables custom(RegistryObject<Block> block, Function<Block, LootTable.Builder> factory) {
        return addEntry(block, factory);
    }

    /**
     * 没有掉落
     *
     * @param block the block
     * @return the block loot tables
     */
    public BlockLootTables noDrop(RegistryObject<Block> block) {
        return addEntry(block, b -> noDrop());
    }

    // ==================== 批量操作方法 ====================

    /**
     * 批量操作一系列方块
     *
     * @param blocks    the blocks
     * @param operation the operation
     * @return the block loot tables
     */
    public BlockLootTables batch(@NotNull Iterable<RegistryObject<Block>> blocks,
                                 Function<RegistryObject<Block>, BlockLootTables> operation) {
        for (RegistryObject<Block> block : blocks) {
            operation.apply(block);
        }
        return this;
    }

    /**
     * 对一组方块应用相同的操作
     *
     * @param operation the operation
     * @param blocks    the blocks
     * @return the block loot tables
     */
    @Contract("_, _ -> this")
    @SafeVarargs
    public final BlockLootTables applyToAll(Function<RegistryObject<Block>, BlockLootTables> operation,
                                            RegistryObject<Block> @NotNull ... blocks) {
        for (RegistryObject<Block> block : blocks) {
            operation.apply(block);
        }
        return this;
    }

    // ==================== 构建方法 ====================

    /**
     * 构建并返回自身（用于流畅API链式调用）
     *
     * @return the block loot tables
     */
    public BlockLootTables build() {
        return this;
    }

    @Override
    protected void generate() {
        for (BlockEntry entry : blockEntries) {
            this.add(entry.block.get(), entry.factory);
        }
    }

    // ==================== 内部类和方法 ====================

    private BlockLootTables addEntry(RegistryObject<Block> block, Function<Block, LootTable.Builder> factory) {
        blockEntries.add(new BlockEntry(block, factory));
        return this;
    }

    private record BlockEntry(RegistryObject<Block> block, Function<Block, LootTable.Builder> factory) {}

    // ==================== 静态工厂方法 ====================

    /**
     * 创建新的战利品表生成器
     *
     * @param deferredRegister the deferred register
     * @return the block loot tables
     */
    @Contract("_ -> new")
    public static @NotNull BlockLootTables create(DeferredRegister<Block> deferredRegister) {
        return new BlockLootTables(deferredRegister);
    }

    /**
     * 快速创建基本矿物的掉落表
     *
     * @param oreBlock the ore block
     * @param oreItem  the ore item
     * @return the loot table .@ not null builder
     */
    public static LootTable.@NotNull Builder simpleOreDrop(Block oreBlock, Item oreItem) {
        return LootTable.lootTable()
                .withPool(LootPool.lootPool()
                        .setRolls(ConstantValue.exactly(1))
                        .add(LootItem.lootTableItem(oreItem)
                                .when(MatchTool.toolMatches(ItemPredicate.Builder.item()
                                        .hasEnchantment(new net.minecraft.advancements.critereon.EnchantmentPredicate(
                                                Enchantments.SILK_TOUCH,
                                                net.minecraft.advancements.critereon.MinMaxBounds.Ints.atLeast(1)
                                        ))).invert())
                                .apply(SetItemCountFunction.setCount(UniformGenerator.between(1, 1)))
                                .apply(ApplyBonusCount.addOreBonusCount(Enchantments.BLOCK_FORTUNE))));
    }

    /**
     * 快速创建石质方块的掉落表（需要镐子）
     *
     * @param stoneBlock the stone block
     * @param dropItem   the drop item
     * @return the loot table .@ not null builder
     */
    public static LootTable.@NotNull Builder stoneDrop(Block stoneBlock, Item dropItem) {
        return LootTable.lootTable()
                .withPool(LootPool.lootPool()
                        .setRolls(ConstantValue.exactly(1))
                        .add(LootItem.lootTableItem(dropItem)
                                .when(MatchTool.toolMatches(ItemPredicate.Builder.item()
                                        .hasEnchantment(new net.minecraft.advancements.critereon.EnchantmentPredicate(
                                                Enchantments.SILK_TOUCH,
                                                net.minecraft.advancements.critereon.MinMaxBounds.Ints.atLeast(1)
                                        ))))));
    }
}