import { EquipmentSlot, GameMode, world, system, Direction, BlockPermutation, Block, EntityDamageCause, BlockVolumeBase, BlockVolume } from "@minecraft/server"

// List of all hoe types
const hoeTypes = [
    "minecraft:wooden_hoe",
    "minecraft:stone_hoe",
    "minecraft:iron_hoe",
    "minecraft:golden_hoe",
    "minecraft:diamond_hoe",
    "minecraft:netherite_hoe",
]

const hingeConditions = {
    "north": { primary: ["west", "east"], secondary: ["east", "west"] },
    "east": { primary: ["north", "south"], secondary: ["south", "north"] },
    "south": { primary: ["east", "west"], secondary: ["west", "east"] },
    "west": { primary: ["south", "north"], secondary: ["north", "south"] }
}


system.beforeEvents.startup.subscribe((event) => {
    event.blockComponentRegistry.registerCustomComponent("aria_ah:slab", {
        beforeOnPlayerPlace: (event) => {

        }
    })

    event.blockComponentRegistry.registerCustomComponent("aria_ah:fence", {
        onPlayerBreak: (event) => {
            updateFencesAround(event.block)
        },

        onPlace: (event) => {
            updateFencesAround(event.block)
        }
    })

    event.blockComponentRegistry.registerCustomComponent("aria_ah:door", {
        onPlayerInteract(event) {
            let permutation = event.block.permutation
            const block = permutation.getState("aria_ah:upper_block_bit") ? event.block.below() : event.block
            permutation = block.permutation
            const isOpen = permutation.getState("aria_ah:is_open")
            const newPermutation = permutation.withState("aria_ah:is_open", !isOpen)
            block.setPermutation(newPermutation)
            block.above().setPermutation(newPermutation.withState("aria_ah:upper_block_bit", true))

            if (isOpen) {
                event.dimension.playSound("random.door_close", block.center())
            } else {
                event.dimension.playSound("random.door_open", block.center())
            }
        },

        beforeOnPlayerPlace(event) {
            if (!event.block.above().isAir) {
                event.cancel = true
            }
        },

        onPlace(event) {
            const permutation = event.block.permutation
            if (permutation.getState("aria_ah:upper_block_bit")) {
                return
            }

            const direction = permutation.getState("minecraft:cardinal_direction")
            const above = event.block.above()
            if (direction in hingeConditions) {
                const { primary, secondary } = hingeConditions[direction]
                const [checkBlock1, checkBlock2] = primary
                const [checkBlock1Above, checkBlock2Above] = secondary

                if (
                    (!event.block[checkBlock1]().isAir && event.block[checkBlock2]().isAir) ||
                    (event.block[checkBlock1]().isAir && event.block[checkBlock2]().isAir && above[checkBlock1Above]().isAir && !above[checkBlock2Above]().isAir)
                ) {
                    event.block.setPermutation(event.block.permutation.withState("aria_ah:door_hinge_bit", true))
                }
            }

            above.setType("aria_ah:door")
            above.setPermutation(event.block.permutation.withState("aria_ah:upper_block_bit", true))
        },

        onPlayerBreak(event) {
            const below = event.block.below()
            if (!below.permutation.getState("aria_ah:upper_block_bit") && below.typeId === "aria_ah:door") {
                event.dimension.runCommand(`setblock ${event.block.location.x} ${below.location.y} ${event.block.location.z} air ${event.player.getGameMode() == GameMode.Creative ? "" : "destroy"}`)
            }
        }
    })

    event.blockComponentRegistry.registerCustomComponent("aria_ah:trapdoor", {
        onPlayerInteract(event) {
            const permutation = event.block.permutation
            const isOpen = permutation.getState("aria_ah:is_open")
            event.block.setPermutation(permutation.withState("aria_ah:is_open", !isOpen))

            if (isOpen) {
                event.dimension.playSound("random.door_close", event.block.center())
            } else {
                event.dimension.playSound("random.door_open", event.block.center())
            }
        }
    })

    event.blockComponentRegistry.registerCustomComponent("aria_ah:fence_gate", {
        onPlayerInteract(event) {
            const permutation = event.block.permutation
            const isOpen = permutation.getState("aria_ah:is_open")
            const direction = permutation.getState("minecraft:cardinal_direction")
            event.block.setPermutation(permutation.withState("aria_ah:is_open", !isOpen))

            if (isOpen) {
                event.dimension.playSound("close.fence_gate", event.block.center())
            } else {
                if (direction === "north" && event.player.location.z > event.block.z) {
                    event.block.setPermutation(event.block.permutation.withState("minecraft:cardinal_direction", "south"))
                } else if (direction === "south" && event.player.location.z < event.block.z) {
                    event.block.setPermutation(event.block.permutation.withState("minecraft:cardinal_direction", "north"))
                } else if (direction === "east" && event.player.location.x < event.block.x) {
                    event.block.setPermutation(event.block.permutation.withState("minecraft:cardinal_direction", "west"))
                } else if (direction === "west" && event.player.location.x > event.block.x) {
                    event.block.setPermutation(event.block.permutation.withState("minecraft:cardinal_direction", "east"))
                }

                event.dimension.playSound("open.fence_gate", event.block.center())
            }
        },

        onPlace(event) {
            updateFencesAround(event.block)
        },

        onPlayerBreak(event) {
            updateFencesAround(event.block)
        }
    })

    event.blockComponentRegistry.registerCustomComponent("aria_ah:stairs", {
        onPlace(event) {
            updateStairs(event.block)
            updateStairsAround(event.block)
        },

        onPlayerBreak(event) {
            updateStairsAround(event.block)
        }
    })


    event.blockComponentRegistry.registerCustomComponent("aria_ah:random_state", {
        beforeOnPlayerPlace(event) {
            system.run(() => {
                const randomType = Math.floor(Math.random() * 2);
                const randomDir = ["north", "east", "south", "west"][Math.floor(Math.random() * 4)];
                const newPermutation = event.block.permutation
                    .withState("aria_ah:type", randomType)
                    .withState("minecraft:cardinal_direction", randomDir);
                event.block.setPermutation(newPermutation);
            })
        }
    })
    event.blockComponentRegistry.registerCustomComponent("aria_ah:random_state_6", {
        beforeOnPlayerPlace(event) {
            system.run(() => {
                const randomType = Math.floor(Math.random() * 6);
                const randomDir = ["north", "east", "south", "west"][Math.floor(Math.random() * 4)];
                const newPermutation = event.block.permutation
                    .withState("aria_ah:type", randomType)
                    .withState("minecraft:cardinal_direction", randomDir);
                event.block.setPermutation(newPermutation);
            })
        }
    })
    event.blockComponentRegistry.registerCustomComponent("aria_ah:random_state_scratches", {
        beforeOnPlayerPlace(event) {
            system.run(() => {
                const randomType = Math.floor(Math.random() * 6);
                const newPermutation = event.block.permutation
                    .withState("aria_ah:type", randomType)
                event.block.setPermutation(newPermutation);
            })
        }
    })

    event.blockComponentRegistry.registerCustomComponent("aria_ah:vine", {
        onPlace: (event) => {
            if (event.block.permutation.getState("aria_ah:initialized")) return

            event.block.setPermutation(event.block.permutation.withState("aria_ah:vine_direction_bits", {
                north: 1,
                east: 2,
                south: 4,
                west: 8
            }[event.block.permutation.getState("minecraft:block_face")]).withState("aria_ah:initialized", true))
        }
    })

    event.blockComponentRegistry.registerCustomComponent("aria_ah:bamboo", {
        onPlace: (event) => {
            updateBamboo(event.block)
        }
    })

    event.blockComponentRegistry.registerCustomComponent("aria_ah:firefly_bush", {
        onTick: (event) => {
            if (Math.random() > 0.99 && world.getTimeOfDay() >= 12542 && world.scoreboard.getObjective("aria_ah.general").getScore("firefly_bush") == 1) {
                const freePos = []
                for (const pos of event.dimension.getBlocks(
                    new BlockVolume(
                        {
                            x: event.block.location.x - 10,
                            y: event.block.location.y - 0,
                            z: event.block.location.z - 10
                        },
                        {
                            x: event.block.location.x + 10,
                            y: event.block.location.y + 5,
                            z: event.block.location.z + 10
                        }
                    ),
                    {
                        includeTypes: ["minecraft:air"]
                    },
                    true
                ).getBlockLocationIterator()) {
                    freePos.push(pos)
                }
                event.block.dimension.spawnParticle("minecraft:firefly_particle", freePos[Math.floor(Math.random() * freePos.length)])
            }
        }
    })
})

system.runInterval(() => {
    for (const player of world.getPlayers()) {
        if (player.location.y < player.dimension.heightRange.min || player.location.y > player.dimension.heightRange.max) {
            return
        }

        const atBlock = player.dimension.getBlock(player.location)
        if (atBlock.isValid && atBlock.hasTag("aria_ah:climbable")) {
            if (player.isJumping) {
                player.addEffect("minecraft:levitation", 2, { showParticles: false, amplifier: 2 })
            } else if (!player.isSneaking) {
                player.addEffect("minecraft:slow_falling", 2, { showParticles: false, amplifier: 2 })
            } else {
                player.applyKnockback({ x: 0, z: 0 }, 0.5)
            }
        }

        if (player.dimension.getBlock(player.location).typeId == "aria_ah:wire") {
            player.applyDamage(1)
        }
    }
}, 1)

world.beforeEvents.playerInteractWithBlock.subscribe((event) => {
    const { block, player } = event
    const equippable = player.getComponent("minecraft:equippable")
    if (!equippable) return
    const mainhand = equippable.getEquipmentSlot(EquipmentSlot.Mainhand)
    if (!mainhand || !mainhand.hasItem()) return

    if (player && ["aria_ah:grass", "aria_ah:podzol", "aria_ah:dirt"].includes(block.typeId)) {
        if (!hoeTypes.includes(mainhand.typeId)) {
            return
        }

        // Get the item in the main hand
        if (!mainhand || !mainhand.hasItem()) {
            event.cancel = true
            return
        }
        
        system.run(() => {
            // Trigger a function or say "hi" to the player
            block.dimension.setBlockType(block.location, "aria_ah:farmland")

            const centerTopLocation = {
                x: block.location.x + 0.5,
                y: block.location.y + 1,
                z: block.location.z + 0.5
            }
            block.dimension.spawnParticle("aria_ah:farm", centerTopLocation)

            // Optionally, play a sound when the function is triggered
            block.dimension.playSound("use.gravel", block.center(), {
                pitch: 1.0,
                volume: 0.8,
            })

            damageItem(player, mainhand.getItem())
        })
    }

    if (mainhand.typeId === "minecraft:flint_and_steel") {
        if (block.typeId == "aria_ah:torch") {
            const blockFace = {
                north: "south",
                south: "north",
                east: "west",
                west: "east",
            }[block.permutation.getState("minecraft:block_face")] || "up"

            event.cancel = true
            system.run(() => {
                block.dimension.spawnParticle("minecraft:large_explosion", block.center())
                block.setType("minecraft:torch")
                block.setPermutation(block.permutation.withState("torch_facing_direction", blockFace))
                block.dimension.playSound("aria_ah.light_on", block.center())
                damageItem(player, mainhand.getItem())
            })
        } else if (block.typeId == "aria_ah:lantern") {
            const hanging = block.permutation.getState("minecraft:block_face") == "down"
            
            event.cancel = true
            system.run(() => {
                block.dimension.spawnParticle("minecraft:large_explosion", block.center())
                block.setType("minecraft:lantern")
                block.setPermutation(block.permutation.withState("hanging", hanging))
                block.dimension.playSound("aria_ah.light_on", block.center())
                damageItem(player, mainhand.getItem())
            })
        } else if (block.typeId == "aria_ah:campfire") {
            const rotation = block.permutation.getState("minecraft:cardinal_direction")
            
            event.cancel = true
            system.run(() => {
                block.dimension.spawnParticle("minecraft:large_explosion", block.center())
                block.setType("minecraft:campfire")
                block.setPermutation(block.permutation.withState("minecraft:cardinal_direction", rotation))
                block.dimension.playSound("aria_ah.light_on", block.center())
                damageItem(player, mainhand.getItem())
            })
        }
    } else if (mainhand.hasTag("minecraft:is_axe")) {
        if (block.typeId == "aria_ah:dark_log") {
            event.cancel = true
            system.run(() => {
                block.setType("aria_ah:stripped_dark_log")
                block.dimension.playSound("use.wood", block.center())
                damageItem(player, mainhand.getItem())
            })
        }
    } else if (mainhand.typeId === "aria_ah:slab") {
        const face = getOppositeDirection(event.blockFace)
        const verticalHalf = event.block.permutation.getState("minecraft:vertical_half")
        const isDouble = event.block.permutation.getState("aria_ah:is_double")
        const blockToCheck = (() => {
            if (face === Direction.Up || face === Direction.Down) {
                if ((event.blockFace === Direction.Up && (verticalHalf === "top" || isDouble)) || (face === Direction.Down && isDouble)) {
                    return event.block.above();
                }
                if ((event.blockFace === Direction.Down && (verticalHalf === "bottom" || isDouble)) || (face === Direction.Up && isDouble)) {
                    return event.block.below();
                }

                return event.block;
            }

            return event.block.offset(directionToVector3(event.blockFace));
        })();

        if (blockToCheck.typeId === "aria_ah:slab") {
            event.cancel = true

            system.run(() => {
                blockToCheck.setPermutation(BlockPermutation.resolve(blockToCheck.typeId, { "aria_ah:is_double": true }))

                if (event.player.getGameMode() !== GameMode.Creative) {
                    const amount = mainhand.amount
                    if (amount > 1) {
                        mainhand.amount--
                        equippableComponent.setEquipment(EquipmentSlot.Mainhand, mainhand)
                    } else {
                        equippableComponent.setEquipment(EquipmentSlot.Mainhand, undefined)
                    }
                }
            })
        }
    }
})


function getOppositeDirection(direction) {
    switch (direction) {
        case Direction.Up:
            return Direction.Down
        case Direction.Down:
            return Direction.Up
        case Direction.North:
            return Direction.South
        case Direction.East:
            return Direction.West
        case Direction.South:
            return Direction.North
        case Direction.West:
            return Direction.East
        case "north":
            return "south"
        case "east":
            return "west"
        case "south":
            return "north"
        case "west":
            return "east"
        default:
            break
    }
}

function directionToVector3(direction) {
    switch (direction) {
        case Direction.Down:
            return { x: 0, y: -1, z: 0 }
        case Direction.Up:
            return { x: 0, y: 1, z: 0 }
        case Direction.North:
            return { x: 0, y: 0, z: -1 }
        case Direction.South:
            return { x: 0, y: 0, z: 1 }
        case Direction.West:
            return { x: -1, y: 0, z: 0 }
        case Direction.East:
            return { x: 1, y: 0, z: 0 }
    }
}

const directions = [
    { block: (block, i) => block.north(i), direction: "north" },
    { block: (block, i) => block.south(i), direction: "south" },
    { block: (block, i) => block.east(i), direction: "east" },
    { block: (block, i) => block.west(i), direction: "west" }
]
/** @type {import("@minecraft/server").Block} */
function updateFencesAround(block) {
    for (const direction of directions) {
        const blockToCheck = direction.block(block, 1)

        if (!blockToCheck) {
            continue
        }

        if (!block.isAir && !blockToCheck.isLiquid && !blockToCheck.isAir && (blockToCheck.hasTag("aria_ah:is_fence") || blockToCheck.hasTag("aria_ah:is_fence_gate")) && !block.hasTag("aria_ah:is_fence_gate")) {
            block.setPermutation(block.permutation.withState(`aria_ah:connect_${direction.direction}`, true))
        }

        if (blockToCheck.hasTag("aria_ah:is_fence")) {
            for (const direction of directions) {
                const newBlockToCheck = direction.block(blockToCheck, 1)
        
                if (!newBlockToCheck) {
                    continue
                }
        
                if (!blockToCheck.isAir && !newBlockToCheck.isLiquid && !newBlockToCheck.isAir) {
                    blockToCheck.setPermutation(blockToCheck.permutation.withState(`aria_ah:connect_${direction.direction}`, true))
                } else {
                    blockToCheck.setPermutation(blockToCheck.permutation.withState(`aria_ah:connect_${direction.direction}`, false))
                }
            }
        }
        
        /*if (blockToCheck && blockToCheck.hasTag("aria_ah:is_fence")) {console.warn(2)
            block.setPermutation(block.permutation.withState(`aria_ah:connect_${direction.direction}`, blockToCheck && blockToCheck.hasTag("aria_ah:is_fence")))

            for (const direction of directions) {
                const newBlockToCheck = direction.block(blockToCheck, 1)
                
            }
        }*/
    }
}

/**
 * 
 * @param {Block} block 
 */
function updateStairs(block) {
    if (block.hasTag("stairs")) {
        block.setPermutation(block.permutation.withState("aria_ah:connected", "not"))
    }

    for (const direction of directions) {
        /**
         * @type {Block}
         */
        const blockToCheck = direction.block(block, 1)

        if (!blockToCheck || blockToCheck.isAir) {
            continue
        }
        const blockDirection = block.permutation.getState("minecraft:cardinal_direction")
        const blockToCheckDirection = blockToCheck.permutation.getState("minecraft:cardinal_direction")
        if (!block.isAir && blockToCheck.hasTag("stairs") && blockDirection !== blockToCheckDirection && blockDirection !== getOppositeDirection(blockToCheckDirection) && rotateDirection(blockDirection, -90) !== direction.direction && rotateDirection(blockDirection, 90) !== direction.direction) {
            if ((blockDirection === "north" && direction.direction === "south") || (blockDirection === "south" && direction.direction === "north") || (blockDirection === "east" && direction.direction === "west") || (blockDirection === "west" && direction.direction === "east")) {
                const sortedDirection = sortDirection(blockDirection, blockToCheckDirection)
                block.setPermutation(block.permutation.withState("aria_ah:connected", `${sortedDirection[0]}_${sortedDirection[1]}`))
            } else {
                const sortedDirection = sortDirection(getOppositeDirection(blockDirection), blockToCheckDirection)
                block.setPermutation(block.permutation.withState("aria_ah:connected", `${sortedDirection[0]}_${sortedDirection[1]}`))
            }
        }
    }
}
function updateStairsAround(block) {
    for (const direction of directions) {
        const blockToCheck = direction.block(block, 1)

        if (blockToCheck?.isValid && blockToCheck.hasTag("stairs")) {
            updateStairs(blockToCheck)
        }
    }
}

function updateBamboo(block) {
    let foundLowestBlock = false
    let lowestBlockDelta = 0

    while (!foundLowestBlock) {
        if (block.below(lowestBlockDelta + 1).typeId == block.typeId) {
            lowestBlockDelta++
        } else {
            foundLowestBlock = true
        }
    }

    if (lowestBlockDelta > 4) {
        const below2 = block.below(2)
        const below3 = block.below(3)
        
        block.setPermutation(block.permutation.withState("aria_ah:bamboo_leaf_size", "large_leaves").withState("aria_ah:bamboo_stalk_thickness", "thick"))
        below2.setPermutation(below2.permutation.withState("aria_ah:bamboo_leaf_size", "small_leaves"))
        below3.setPermutation(below3.permutation.withState("aria_ah:bamboo_leaf_size", "no_leaves"))
    } else if (lowestBlockDelta >= 4) {
        block.setPermutation(block.permutation.withState("aria_ah:bamboo_leaf_size", "large_leaves").withState("aria_ah:bamboo_stalk_thickness", "thick"))
    } else if (lowestBlockDelta >= 3) {
        block.setPermutation(block.permutation.withState("aria_ah:bamboo_stalk_thickness", "thick").withState("aria_ah:bamboo_leaf_size", "large_leaves"))
    } else if (lowestBlockDelta >= 1 && lowestBlockDelta <= 2) {
        block.setPermutation(block.permutation.withState("aria_ah:bamboo_leaf_size", "small_leaves"))
    }

    const thick = (block.below().permutation.getState("aria_ah:bamboo_stalk_thickness") == "thick")
    if (lowestBlockDelta == 3 && !thick) {

        for (let i = 1; i <= lowestBlockDelta; i++) {
            const below = block.below(i)
            
            below.setPermutation(
                below.permutation
                    .withState("aria_ah:bamboo_stalk_thickness", "thick")
                    .withState("aria_ah:bamboo_leaf_size", "no_leaves")
                )

            if (i == 1) {
                below.setPermutation(below.permutation.withState("aria_ah:bamboo_leaf_size", "small_leaves"))
            }
        }
    }
}

function sortDirection(a, b) {
    const high = ["north", "south"]
  
    if (high.includes(a)) {
        return [a, b]
    } else if (high.includes(b)) {
        return [b, a]
    } else {
        return [a, b]
    }
}
function rotateDirection(direction, degree) {
    const directions = ["north", "east", "south", "west"]
    const index = directions.indexOf(direction)
    let newIndex = (index + degree / 90) % 4
    if (newIndex < 0) {
        newIndex += 4
    }
    return directions[newIndex]
}

function damageItem(player, itemStack) {
    if (player.getGameMode() !== GameMode.Creative) {
        const durability = itemStack.getComponent("minecraft:durability")
        if (!durability) return

        const enchantable = itemStack.getComponent("minecraft:enchantable")
        const unbreakingLevel = enchantable?.getEnchantment("unbreaking")?.level

        const damageChance = durability.getDamageChance(unbreakingLevel) / 100

        if (Math.random() > damageChance) return

        const shouldBreak = durability.damage === durability.maxDurability
        const equippable = player.getComponent("minecraft:equippable")
        const mainhand = equippable.getEquipmentSlot(EquipmentSlot.Mainhand)

        if (shouldBreak) {
            mainhand.setItem(undefined)
            player.playSound("random.break")
        } else {
            durability.damage++
            mainhand.setItem(itemStack)
        }
    }
}
