import { ItemComponentTypes, EnchantmentTypes, BlockComponentTypes, Direction, GameMode, system, BlockVolume, EntityComponentTypes } from '@minecraft/server';
import { MCSounds, ADDON_ID } from '../constants.js';
import { setEntityHeldItem, getEntityHeldItem } from './players.js';
import { MinecraftEnchantmentTypes, MinecraftBlockTypes } from '../minecraft_vanilla_data.js';

function updateBlockStates(block, states) {
    let permutation = block.permutation;
    for (const stateName in states) {
        const value = states[stateName];
        permutation = permutation.withState(stateName, value);
    }
    block.setPermutation(permutation);
}
function updateBlockState(block, stateName, value) {
    updateBlockStates(block, {
        [stateName]: value
    });
}
function getBlockState(blockPermutation, stateName) {
    return blockPermutation.getState(stateName);
}
function shrinkItemStack(itemStack, amount = 1) {
    const newAmount = itemStack.amount - amount;
    if (newAmount <= 0) {
        return undefined;
    }
    itemStack.amount = newAmount;
    return itemStack;
}
/**
 * Adds given values to the Vector3
 *
 * @param vec3 the location
 * @param x the x offset to add
 * @param y the y offset to add
 * @param z the z offset to add
 */
function addVec3(vec3, x, y, z) {
    return {
        x: vec3.x + x,
        y: vec3.y + y,
        z: vec3.z + z
    };
}
/**
 * Removes given values to the Vector3
 *
 * @param vec3 the location
 * @param x the x offset to remove
 * @param y the y offset to remove
 * @param z the z offset to remove
 */
function removeVec3(vec3, x, y, z) {
    return addVec3(vec3, -x, -y, -z);
}
function spawnItemAtBlock(block, itemStack) {
    block.dimension.spawnItem(itemStack, block.center());
}
function vec3ToString(vec3) {
    return `${vec3.x} ${vec3.y} ${vec3.z}`;
}
function getBlockContainer(block) {
    const blockInventoryComponent = block.getComponent(BlockComponentTypes.Inventory);
    return blockInventoryComponent?.container;
}
function isBlockContainer(block) {
    return getBlockContainer(block) !== undefined;
}
function getBlockFromDirection(block, direction, amount = 1) {
    try {
        switch (direction) {
            case Direction.Down:
                return block.below(amount);
            case Direction.Up:
                return block.above(amount);
            case Direction.North:
                return block.north(amount);
            case Direction.South:
                return block.south(amount);
            case Direction.East:
                return block.east(amount);
            case Direction.West:
                return block.west(amount);
        }
    }
    catch (exp) {
        return undefined;
    }
}
function getBlocksAround(block) {
    const blocks = [];
    for (let directionKey in Direction) {
        const direction = Direction[directionKey];
        const blockInDirection = getBlockFromDirection(block, direction);
        if (blockInDirection) {
            blocks.push(blockInDirection);
        }
    }
    return blocks;
}
function zeroOutVec3(vec3) {
    return {
        x: Math.floor(vec3.x),
        y: Math.floor(vec3.y),
        z: Math.floor(vec3.z)
    };
}
/**
 * Spawns loot table at the given block location
 *
 * @param itemStack the item stack to check
 * @param enchantmentType the enchantment type to check
 * @returns the level of the enchantment on the item stack 0 if it doesn't exist
 */
function getEnchantmentLevel(itemStack, enchantmentType) {
    const enchantable = itemStack.getComponent(ItemComponentTypes.Enchantable);
    if (!enchantable) {
        return 0;
    }
    const enchantment = enchantable.getEnchantment(enchantmentType);
    if (!enchantment) {
        return 0;
    }
    return enchantment.level;
}
function isPlayerCreative(entity) {
    if (entity.typeId === "minecraft:player") {
        const player = entity;
        if (player.getGameMode() === GameMode.Creative) {
            return true;
        }
    }
    return false;
}
/**
 * Calculates the amount of damage to apply to an item stack based on the unbreaking enchantment
 *
 * @param itemStack item stack to damage
 * @param amount how much raw damage to apply
 * @return the amount of damage to apply to the item stack
 */
function calculateItemDamageAmount(itemStack, amount = 1) {
    if (!itemStack) {
        return 0;
    }
    const itemDamage = itemStack.getComponent(ItemComponentTypes.Durability);
    if (!itemDamage) {
        return 0;
    }
    const unbreakingLevel = getEnchantmentLevel(itemStack, EnchantmentTypes.get(MinecraftEnchantmentTypes.Unbreaking));
    const breakChance = itemDamage.getDamageChance(unbreakingLevel);
    let breakAmount = 0;
    for (let i = 0; i < amount; i++) {
        const shouldBreak = Math.random() < breakChance;
        if (shouldBreak) {
            breakAmount++;
        }
    }
    return breakAmount;
}
/**
 * Damages the entity held item and if it breaks, "destroys" the item and plays the break sound
 *
 * @param entity the entity whose item to damage
 * @param amount the amount to damage the item
 * @param heldItem the item to damage
 */
function damageEntityHeldItem(entity, amount, heldItem = getEntityHeldItem(entity)) {
    if (amount <= 0) {
        return;
    }
    if (isPlayerCreative(entity)) {
        return;
    }
    const itemDamage = heldItem.getComponent(ItemComponentTypes.Durability);
    const newDamage = itemDamage.damage + amount;
    if (newDamage >= itemDamage.maxDurability) {
        entity.dimension.playSound(MCSounds.RANDOM_BREAK, entity.location);
        setEntityHeldItem(entity, undefined);
        return;
    }
    itemDamage.damage = newDamage;
    setEntityHeldItem(entity, heldItem);
}
/**
 * Fake and entity breaking a block with the item in their main hand
 *
 * @param block the block to break
 * @param entity the entity to break the block
 * @param breakBlock if false the block wont be broken but the loot of the block will still be spawned
 */
function fakeBlockBreak(block, entity, breakBlock = true) {
    const unifedBlockCords = vec3ToString(zeroOutVec3(block.location));
    const command = `loot spawn ${unifedBlockCords} mine ${unifedBlockCords} mainhand`;
    entity.runCommand(command);
    if (breakBlock) {
        system.runTimeout(() => {
            block.setType(MinecraftBlockTypes.Air);
        }, 1);
    }
}
/**
 * Gets the block in a range of given block
 *
 * @param centerBlock block to get the block around
 * @param range how big of a range to get the blocks in
 * @param includeY should the range extend in the Y direction
 * @param filter block filter to apply
 * @returns the iterator of blocks in the range
 */
function getBlocksInRange(centerBlock, range, includeY = true, filter = {}) {
    const xZ = Math.floor(range / 2);
    const y = includeY ? range / 2 : 0;
    const vec3From = zeroOutVec3(removeVec3(centerBlock, xZ, y, xZ));
    const vec3To = zeroOutVec3(addVec3(centerBlock, xZ, y, xZ));
    const blockVolume = new BlockVolume(vec3From, vec3To);
    const blocks = centerBlock.dimension.getBlocks(blockVolume, filter);
    return blocks.getBlockLocationIterator();
}
function playSoundAtBlock(block, sound, soundOptions) {
    block.dimension.playSound(sound, block.center(), soundOptions);
}
function spawnParticleAtBlock(block, particle, molangVariables) {
    block.dimension.spawnParticle(particle, block.center());
}
/**
 * Inserts loot table at the given block location
 *
 * @param block block to spawn loot at
 * @param table item to give
 * @returns if the loot table was inserted into the block
 */
function insertLootToBlock(block, table) {
    const commandString = `loot insert ${vec3ToString(zeroOutVec3(block.location))} loot "${table}"`;
    const commandResult = block.dimension.runCommand(commandString);
    return commandResult.successCount !== 0;
}
/**
 * Spawns loot table at the given block location
 *
 * @param block block to spawn loot at
 * @param table item to give
 * @returns if the loot table was spawned
 */
function spawnLootAtBlock(block, table) {
    // Todo find better location
    const commandString = `loot spawn ${vec3ToString(addVec3(block.location, 0.80, 1.75, 0.80))} loot "${table}"`;
    const commandResult = block.dimension.runCommand(commandString);
    return commandResult.successCount !== 0;
}
/**
 * Either gives the item back to the player or drops it on the ground if they have no space
 *
 * @param player target player
 * @param stack item to give
 * @param dropLocation drop location, defaults to player location
 * @returns true if the item was given to the player, false if it failed
 */
function giveItemToPlayer(player, stack, dropLocation = player.location) {
    const heldItem = getEntityHeldItem(player);
    if (!heldItem) {
        setEntityHeldItem(player, stack);
    }
    // Fine, let's try and put it in their inventory
    const inventory = player.getComponent(EntityComponentTypes.Inventory);
    if (!inventory) {
        return false;
    }
    const result = inventory.container.addItem(stack);
    if (!result) {
        return true;
    }
    // Right, I guess we drop it on the ground
    player.dimension.spawnItem(stack, dropLocation);
    return true;
}
function areVectorsEqual(vec1, vec2) {
    return vec1.x === vec2.x && vec1.y === vec2.y && vec1.z === vec2.z;
}
/**
 * If the block can see the sky, check if there are blocks above it the block it from having direct sunlight (Note blocks
 * such as leaves might not count)
 *
 * @param block target player
 * @returns true if the block can see the sky
 */
function canBlockSeeSky(block) {
    const blockLocation = block.location;
    const topMostBlockLocation = block.dimension.getTopmostBlock(blockLocation)?.location;
    return areVectorsEqual(blockLocation, topMostBlockLocation);
}
function translated(key, withText) {
    return {
        translate: key,
        with: withText
    };
}
function isPlayerOp(player) {
    return player.hasTag(ADDON_ID + "_admin");
}

export { addVec3, areVectorsEqual, calculateItemDamageAmount, canBlockSeeSky, damageEntityHeldItem, fakeBlockBreak, getBlockContainer, getBlockFromDirection, getBlockState, getBlocksAround, getBlocksInRange, getEnchantmentLevel, giveItemToPlayer, insertLootToBlock, isBlockContainer, isPlayerCreative, isPlayerOp, playSoundAtBlock, removeVec3, shrinkItemStack, spawnItemAtBlock, spawnLootAtBlock, spawnParticleAtBlock, translated, updateBlockState, updateBlockStates, vec3ToString, zeroOutVec3 };
