import { Block, Dimension, Direction, Entity, EquipmentSlot, GameMode, ItemStack, ItemUseOnBeforeEvent, Player, system } from "@minecraft/server"
import { AnimationIds, Piki, SoundsIds } from "../../utils/typingConstants"
import { ItemTypeIds } from "../../items/utils/itemConstants"
import { BlockValuedTagsValues } from "../utils/blockConstants"
import { BlockValuedTagsKeys } from "../utils/blockConstants"
import { BlockStateValues } from "../utils/blockConstants"
import { BlockStateNames } from "../utils/blockConstants"
import { multiClicking, testing } from "../../utils/systemHelper"
import { CustomBlockTags } from "../utils/blockTags"
import { BlockHelper as BHelper } from "../utils/blockHelpers"
import { CommandHelper as CHelper } from "../../utils/commandHelper"
import { ItemHelper } from "../../items/utils/itemHelper"

export const CUSTOM_SLABS = {
    _typeIdSuffix: "_slab",
    _addPitchPlacingBlocksIdsContains: ["gold", "iron"],

    /**
     * 
     * @param {string} itemStackId 
     * @param {Player} source 
     * @returns 
     */
    isPlacing(itemStackId, source) {
        const expectedSlabId = BHelper.getPlacerRealBlockId(itemStackId)
        if (!this.isCustomSlab(expectedSlabId)) return false
        if (multiClicking(source)) return false
        return true
    },

    /**
     * 
     * @param {string} blockId 
     * @param {Entity} source 
     * @param {string} itemTypeId
     * @returns 
     */
    isWaterLogging(blockId, source, itemTypeId) {
        if (!this.isCustomSlab(blockId)) return
        if (itemTypeId != ItemTypeIds.waterBucket) return;
        if (multiClicking(source)) return
        return true
    },

    /**
     * 
     * @param {string} blockId
     * @returns 
     */
    isCustomSlab(blockId) {
        return blockId.startsWith(Piki.namespace) && blockId.endsWith(this._typeIdSuffix)
    },

    /**
     * 
     * @param {Block} block 
     * @param {Player} source 
     * @param {Direction} blockFace 
     * @param {ItemStack} itemStack 
     * @param {import("@minecraft/server").Vector3} faceLocation
     */
    placeBlock(block, source, blockFace, itemStack, faceLocation) {
        const blockRequiresSneak = CustomBlockTags.isSneakRequired(block)
        const blockRequiresSneakOnSide = CustomBlockTags.isSneakRequiredOnSide(block)
        const usedBlockWillReplace = CustomBlockTags.replaceAsTarget(block)
        const isPlacingOnSide = blockFace != Direction.Down && blockFace != Direction.Up

        if (blockRequiresSneak && !source.isSneaking) return // testing.info("Sneak to place the slab")
        if (blockRequiresSneakOnSide && !source.isSneaking && isPlacingOnSide) return // testing.info("Sneak to place the slab")

        const itemStackId = itemStack.typeId
        const expectedSlabId = BHelper.getPlacerRealBlockId(itemStackId)
        const blockFromFace = BHelper.getBlockFromFace(block, blockFace)
        const verticalSlabState = block.permutation.getState(BlockStateNames.verticalHalf)
        const isUsedBlockSlab = block.typeId == expectedSlabId
        const isUsedBlockDouble = block.permutation.getState(BlockStateNames.doubleSlab)
        const isUsedBlockWillComplete = isUsedBlockSlab && !isUsedBlockDouble
        let placedBlock;

        if (usedBlockWillReplace) placedBlock = block
        else if (!isUsedBlockWillComplete) placedBlock = blockFromFace
        else if (verticalSlabState == BlockStateValues.verticalHalfTop && blockFace == Direction.Down) placedBlock = block
        else if (verticalSlabState == BlockStateValues.verticalHalfBottom && blockFace == Direction.Up) placedBlock = block
        else if (verticalSlabState == BlockStateValues.verticalHalfTop) placedBlock = blockFromFace
        else if (verticalSlabState == BlockStateValues.verticalHalfBottom) placedBlock = blockFromFace
    
        const placedSlabId = placedBlock.typeId;
        const targetBlockWillReplace = CustomBlockTags.replaceAsTarget(placedBlock)
        const entitiesOnTargetBlock = placedBlock.dimension.getEntitiesAtBlockLocation(placedBlock.location)
        const isTargetBlockSlab = placedBlock.typeId == expectedSlabId
        const isTargetBlockDouble = placedBlock.permutation.getState(BlockStateNames.doubleSlab)
        const isTargetBlockWillComplete = isTargetBlockSlab && !isTargetBlockDouble
    
        if (entitiesOnTargetBlock.length > 0) return // testing.error("Entities in location"); 
        if (!targetBlockWillReplace && !isTargetBlockWillComplete) return // testing.error("Cannot place the block in", placedBlock.typeId)
        
        let verticalHalfState;
        if (faceLocation.y >= 0.50) verticalHalfState = BlockStateValues.verticalHalfTop
        else verticalHalfState = BlockStateValues.verticalHalfBottom
        if (blockFace == Direction.Down) verticalHalfState = BlockStateValues.verticalHalfTop
        else if (blockFace == Direction.Up) verticalHalfState = BlockStateValues.verticalHalfBottom

        system.run(() => {
            // Place a full block first to remove the water
            placedBlock.setType(expectedSlabId.replace(this._typeIdSuffix, ""))
            placedBlock.setType(expectedSlabId)

            let blockStates;
            if (isTargetBlockWillComplete) blockStates = placedBlock.permutation.withState(BlockStateNames.doubleSlab, true) 
            else blockStates = placedBlock.permutation.withState(BlockStateNames.verticalHalf, verticalHalfState)
            placedBlock.setPermutation(blockStates)

            const willAddPitch = this._addPitchPlacingBlocksIdsContains.some(e => placedSlabId.includes(e))
            const correctTool = BHelper.getBlockTagValue(placedBlock, BlockValuedTagsKeys.correctTool)

            let sound;
            if (correctTool == BlockValuedTagsValues.axe) sound = SoundsIds.digWood
            else if (correctTool == BlockValuedTagsValues.pickaxe) sound = SoundsIds.digStone
            else if (correctTool == BlockValuedTagsValues.shovel) sound = SoundsIds.digSnow
            else if (!correctTool) sound = SoundsIds.cloth
            else sound = SoundsIds.useStone
            
            if (willAddPitch) sound = SoundsIds.useStone

            let pitch = 0.8;
            if (willAddPitch) pitch = 1.2

            source.playAnimation(AnimationIds.customSwing) 
            source.playSound(sound, {location: source.location, volume: 1, pitch: pitch})
            
            ItemHelper.clearPlayerItem(source, EquipmentSlot.Mainhand)
        })
    },

    /**
     * 
     * @param {ItemUseOnBeforeEvent} ev
     * @returns 
     */
    waterLog(ev) {
        const {block, blockFace, source} = ev;
        let placedBlock = block
        
        if (block.permutation.getState(BlockStateNames.doubleSlab)) placedBlock = BHelper.getBlockFromFace(block, blockFace)

        if (!this.isCustomSlab(placedBlock.typeId)) return;

        ev.cancel = true;

        const structureFile = this._getStructureFile(block)
        const command = CHelper.generateStructureCommand(structureFile, placedBlock.location)
        block.dimension.runCommandAsync(command)
        system.run(() => {
            source.playSound(SoundsIds.bucketEmptyWater, {location: source.location})
            ItemHelper.clearPlayerItem(source, EquipmentSlot.Mainhand)
        })
    },

    /**
     * 
     * @param {Block} block 
     * @param {Dimension} dimension 
     * @param {ItemStack} itemStack 
     */
    dropDoubleLoot(block, dimension, itemStack, player) {
        const blockId = block.typeId

        if (player.getGameMode() == GameMode.creative) return;

        const toolRequired = BHelper.getBlockTagValue(block, BlockValuedTagsKeys.correctTool)
        const toolUsed = ItemHelper.getItemToolTier(itemStack)
        
        if (toolRequired != toolUsed) return;

        const isDouble = block.permutation.getState(BlockStateNames.doubleSlab)
        const lootFile = this._getLootFile(blockId)
        const command = CHelper.generateSpawnLootCommand(block.location, lootFile)
        dimension.runCommandAsync(command)
        if (isDouble) dimension.runCommandAsync(command)
    },

    /**
     * HACK: Structure filenames are shortened for character limits
     * @param {Block} block 
     */
    _getStructureFile(block) {
        const slabId = block.typeId
        const state = block.permutation.getState(BlockStateNames.verticalHalf)
        const suffix = state.charAt(0).toLowerCase();
        return `${slabId
            .replace(Piki.namespace, "")
            .replace("painted_planks", "planks")
            .replace("_terracotta_bricks", "tcb")
            .replace("terracotta_bricks", "tcb")}` + 
            suffix
    },

    /**
     * HACK: Loot filenames are shortened for character limits
     * @param {string} blockId 
     * @returns 
     */
    _getLootFile(blockId) {
        return `${Piki.blockLootsPath}${blockId
            .replace(Piki.namespace, "")
            .replace("painted_planks", "planks")
            .replace("_terracotta_bricks", "tcb")
            .replace("terracotta_bricks", "tcb")}`
    }
    
}