import * as mc from "@minecraft/server";
import * as mcui from "@minecraft/server-ui";
import * as GMath from "./utils/GMath";
import { blockComponents } from "./utils/Components";
import blocksConfig from "./configs/blocksConfig";
//Variables
const tilesSound = {
    "vtng_rt:laser": {
        "onActive": {
            "sound": "beacon.activate",
            "options": {
                "volume": 0.5
            }
        },
        "onDisable": {
            "sound": "beacon.deactivate",
            "options": {
                "volume": 0.5
            }
        }
    },
    "vtng_rt:turret": {
        "onActive": {
            "sound": "tile.piston.in",
            "options": {
                "volume": 0.5
            }
        },
        "onDisable": {
            "sound": "tile.piston.out",
            "options": {
                "volume": 0.5
            }
        }
    }
}
const tilesPowerDirection = {
    "blacklist": [
        "vtng_rt:turret"
    ],
    "inverted": {
        "vtng_rt:double_piston": {
            "down": "up",
            "up": "down",
            "north": "north",
            "south": "south",
            "west": "west",
            "east": "east"
        },
        "vtng_rt:double_piston_sticky": {
            "down": "up",
            "up": "down",
            "north": "north",
            "south": "south",
            "west": "west",
            "east": "east"
        },
    }
}
const directions = {
    "down": new GMath.Vector3(0, -1, 0),
    "up": new GMath.Vector3(0, 1, 0),
    "north": new GMath.Vector3(0, 0, -1),
    "south": new GMath.Vector3(0, 0, 1),
    "west": new GMath.Vector3(-1, 0, 0),
    "east": new GMath.Vector3(1, 0, 0)
}
const directionsReverse = {
    "down": "up",
    "up": "down",
    "north": "south",
    "south": "north",
    "west": "east",
    "east": "west"
}
const directionsRot = {
    "north": 180,
    "south": 0,
    "west": 90,
    "east": -90
}
const blockConveyorData = {
    "actives": {},
    "blacklist": [
        "vtng_rt:block_conveyor",
        "minecraft:air",
        "minecraft:redstone_wire",
        "minecraft:bedrock",
        "minecraft:unpowered_repeater",
        "minecraft:powered_repeater",
        "minecraft:powered_comparator",
        "minecraft:unpowered_comparator"
    ],
    "blacklist_tags": [
        "vtng_rt:not_movable"
    ]
}
const loadedBlocks = {}
const loadedLasers = {}
const delayedBlocks = {}
const tempBlockDatas = {}
//Functions
/**
 * 
 * @param {string} types 
 * @param {mc.Container} container 
 */
function getItemsType(types = [], container) {
    const slots = []
    for (let i = 0; i < container.size; i++) {
        const slot = container.getSlot(i)
        if (slot.hasItem() && types.includes(slot.typeId)) {
            slots.push(slot)
        }
    }
    return slots
}
/**
 * 
 * @param {mc.ContainerSlot} slot 
 * @param {'+', '-'} mode 
 * @param {number} value
 */
function operateSlot(slot, mode, value, container) {

    if (typeof (slot) == "number") {
        slot = container.getSlot(slot)
    }
    const item = slot.getItem().clone()
    if (mode == "+") {
        var nextValue = item.amount + value
        item.amount = nextValue < item.maxAmount ? nextValue : item.amount
        slot.setItem(item)

    } else if (mode == "-") {
        var nextValue = item.amount - value
        item.amount = nextValue > 0 ? nextValue : 1
        slot.setItem(nextValue <= 0 ? undefined : item)
    }
    return slot
}
/**
 * 
 * @param {mc.Block} block 
 */
function getDirection(block) {
    const permutation = block.permutation
    const blockDirection = permutation.getState("minecraft:block_face") || permutation.getState("minecraft:cardinal_direction") || permutation.getState("minecraft:facing_direction")
    let direction = blockDirection
    if (tilesPowerDirection.inverted[block.typeId] && direction) {
        direction = tilesPowerDirection.inverted[block.typeId][direction]
    }
    if (tilesPowerDirection.blacklist.includes(block.typeId)) {
        direction = undefined
    }
    return direction
}
/**
 * 
 * @param {mc.Block} block 
 * @param {mc.Block} startBlock
 * @param {bool} sticky 
 */
function* doublePistonExtend(block, startBlock, sticky = false) {
    const dimension = block.dimension
    let permutation = block.permutation
    const facingDirection = permutation.getState("minecraft:facing_direction")
    const expandStage = permutation.getState("vtng_rt:expand_stage")
    const directionOrder = {
        "north": "north",
        "south": "south",
        "west": "west",
        "east": "east",
        "up": "down",
        "down": "up"
    }
    const directionOrderBack = {
        "north": "south",
        "south": "north",
        "west": "east",
        "east": "west",
        "up": "up",
        "down": "down"
    }
    /**
     * @type {GMath.Vector3}
     */
    const moveTo = directions[directionOrder[facingDirection]]
    /**
     * @type {GMath.Vector3}
     */
    const backwardTo = directions[directionOrderBack[facingDirection]]
    /**
     * @type {mc.Block[]}
     */
    let blocks = []
    let coordsValid = []
    let moveSucces = 0
    let state = "valid_active"
    const pistonData = {
        "sticky_types": [
            "minecraft:slime"
        ],
        "blacklist": [
            "minecraft:bedrock"
        ],
        "blacklist_tags": [
            "vtng_rt:not_movable"
        ]
    }
    const getAttached = (start, visiteds = []) => {
        const coord = `${start.x} ${start.y} ${start.z}`
        const isSolid = !(start.isAir || start.isLiquid)
        const inBlacklistType = (pistonData.blacklist.includes(start.typeId))
        const inBlacklistTag = (pistonData.blacklist_tags.find(tag => start.permutation.getTags().includes(tag)))

        if ((!inBlacklistType && !inBlacklistTag)) {

            if (visiteds.includes(coord) == false && isSolid && blocks.length < 12) {
                const selfSticky = (sticky && pistonData.sticky_types.includes(start.typeId))
                blocks.push(start)
                coordsValid.push(coord)
                visiteds.push(coord)
                const next = start.offset(moveTo)
                const nextCoord = `${next.x} ${next.y} ${next.z}`
                if (visiteds.includes(nextCoord) == false && (sticky == false || (selfSticky))) {
                    getAttached(next, visiteds)
                }
            }
        } else {
            if (!sticky) {
                state = "force_off"
                blocks = []
            }
        }

    }

    if (sticky == false) {
        getAttached(startBlock)
        blocks.forEach(blockT => {
            const targetBlock = blockT.offset(moveTo)
            const coordTarget = `${targetBlock.x} ${targetBlock.y} ${targetBlock.z}`
            const coord = `${blockT.x} ${blockT.y} ${blockT.z}`
            if (coordsValid.includes(coordTarget) || targetBlock.isAir) {
                moveSucces += 1
            }
        })
        if (moveSucces > 0 && moveSucces == blocks.length) {
            blocks.reverse().forEach(blockT => {
                const targetBlock = blockT.offset(moveTo)
                const coordTarget = `${targetBlock.x} ${targetBlock.y} ${targetBlock.z}`
                const coord = `${blockT.x} ${blockT.y} ${blockT.z}`
                if (coordsValid.includes(coordTarget) || targetBlock.isAir) {
                    dimension.runCommand(`clone ${coord} ${coord} ${coordTarget} masked move`)
                }
            })
        }
        if (state == "valid_active") {
            if (expandStage == 1) {
                dimension.playSound(`tile.piston.in`, block.location, { volume: 0.5 })
            }
            if (expandStage < 2) {
                permutation = permutation.withState("vtng_rt:expand_stage", expandStage + 1)
            } else {
                permutation = permutation.withState("vtng_rt:extended", true)
            }
            block.setPermutation(permutation)
            const add = GMath.clamp(expandStage, 1, 2)
            const tBlock = block.offset(new GMath.Vector3(add, add, add).multiply(moveTo))
            //tBlock.setPermutation(mc.BlockPermutation.resolve("minecraft:air"))
            if (tBlock.typeId == "minecraft:air") {
                tBlock.setPermutation(mc.BlockPermutation.resolve("vtng_rt:double_piston_arm_collision", { "minecraft:facing_direction": facingDirection, "vtng_rt:offset": 2 - expandStage }))
            }

        }
    } else {
        getAttached(startBlock)
        blocks.forEach(blockT => {
            const targetBlock = blockT.offset(backwardTo)
            const coordTarget = `${targetBlock.x} ${targetBlock.y} ${targetBlock.z}`
            const coord = `${blockT.x} ${blockT.y} ${blockT.z}`
            dimension.runCommand(`clone ${coord} ${coord} ${coordTarget} masked move`)
        })
        if (state == "valid_active") {
            if (expandStage == 1) {
                dimension.playSound(`tile.piston.out`, block.location, { volume: 0.5 })
            }
            if (expandStage > 0) {

                permutation = permutation.withState("vtng_rt:expand_stage", expandStage - 1)

            } else {

                permutation = permutation.withState("vtng_rt:extended", false)
            }
            block.setPermutation(permutation)
            const add = GMath.clamp(expandStage, 1, 2)
            const tBlock = block.offset(new GMath.Vector3(add, add, add).multiply(moveTo))
            if (tBlock.typeId == "vtng_rt:double_piston_arm_collision") {
                tBlock.setPermutation(mc.BlockPermutation.resolve("minecraft:air"))
            }

        }
    }
}
/**
 * 
 * @param {mc.Container} container 
 * @param {mc.ContainerSlot[]} inventory
 */
function InventorySorter(container, inventory, mode) {
    const sorted = [...inventory].sort((a, b) => {
        if (!a.hasItem() && b.hasItem()) return 1;
        if (a.hasItem() && !b.hasItem()) return -1;
        if (a.hasItem() && b.hasItem()) {
            const aName = a.typeId.split(":", 2)[1]
            const bName = b.typeId.split(":", 2)[1]
            switch (mode) {
                case "name":
                    return aName.localeCompare(bName);
                case "name_reverse":
                    return bName.localeCompare(aName);
                case "amount":
                    return b.amount - a.amount;
                case "amount_reverse":
                    return a.amount - b.amount;
                default:
                    return 0;
            }
        }
    });
    const items = sorted.map((slot) => {
        if (slot.getItem()) {
            return slot.getItem().clone()
        } else {
            return undefined
        }
    })
    sorted.forEach((slot, index) => {
        container.setItem(index, items[index])
    })

}
//Starter Components
class RedstoneActivation {
    /**
     * 
     * @param {mc.BlockComponentTickEvent} ev 
     */
    onTick(ev) {
        try {
            const block = ev.block
            const onState = block.permutation.getState("vtng_rt:on")
            const isOn = block.getRedstonePower() > 0
            const excludeTypes = [
                "minecraft:powered_repeater",
                "minecraft:unpowered_repeater",
                "minecraft:redstone_wire",
                "minecraft:powered_comparator",
                "minecraft:unpowered_comparator",
            ]
            const tileSound = tilesSound[block.typeId]
            let direction = getDirection(block)
            let cDirections = {}
            Object.keys(directions).forEach(dir => {
                if (dir != direction) {
                    cDirections[dir] = directions[dir]
                }
            })
            const blockActive = isOn == false ? Object.values(cDirections).map((e) => block.offset(e)).find(e => (e.getRedstonePower() > 0 && !excludeTypes.includes(e.typeId))) : undefined
            if (onState != undefined) {
                if (isOn != onState && !blockActive) {
                    if (isOn == true && tileSound && tileSound?.onActive) {
                        block.dimension.playSound(tileSound?.onActive.sound, block.location, tileSound?.onActive.options)
                    } else if (isOn == false && tileSound && tileSound?.onDisable) {
                        block.dimension.playSound(tileSound?.onDisable.sound, block.location, tileSound?.onDisable.options)
                    }
                    block.setPermutation(block.permutation.withState("vtng_rt:on", isOn))
                } else if (isOn == false && onState == false && blockActive && blockActive.isValid()) {
                    if (tileSound && tileSound?.onActive) {
                        block.dimension.playSound(tileSound?.onActive.sound, block.location, tileSound?.onActive.options)
                    }

                    block.setPermutation(block.permutation.withState("vtng_rt:on", true))
                }
            }
        } catch (e) {
            // mc.world.sendMessage(`${e}`)
        }
    }
}
//Block Components
class PistonBreak {
    /**
    * 
    * @param {mc.BlockComponentOnPlaceEvent} ev 
    */
    onPlace(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const previousBlock = ev.previousBlock
        if (previousBlock.type.id == "minecraft:moving_block") {
            dimension.runCommand(`setblock ${block.x} ${block.y} ${block.z} air [] destroy`)
        }
    }
    /**
    * @param {mc.BlockComponentTickEvent} ev 
    */
    onTick(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const permutation = block.permutation
        const facingDirection = permutation.getState("minecraft:facing_direction")
        const offsetV = permutation.getState("vtng_rt:offset")
        let breakBlock = false
        if (offsetV != 1) { return; }
        if (facingDirection == "north") {
            const offsetBlock = block.offset(new GMath.Vector3(0, 0, offsetV))
            if (offsetBlock.permutation.hasTag("vtng_rt:piston")) {
                const permutation = offsetBlock.permutation
                const facing = permutation.getState("minecraft:facing_direction")
                if (facing != facingDirection) {
                    breakBlock = true
                }
            } else {
                breakBlock = true
            }
        } else if (facingDirection == "south") {
            const offsetBlock = block.offset(new GMath.Vector3(0, 0, offsetV * -1))
            if (offsetBlock.permutation.hasTag("vtng_rt:piston")) {
                const permutation = offsetBlock.permutation
                const facing = permutation.getState("minecraft:facing_direction")
                if (facing != facingDirection) {
                    breakBlock = true
                }
            } else {
                breakBlock = true
            }
        } else if (facingDirection == "west") {
            const offsetBlock = block.offset(new GMath.Vector3(offsetV, 0, 0))
            if (offsetBlock.permutation.hasTag("vtng_rt:piston")) {
                const permutation = offsetBlock.permutation
                const facing = permutation.getState("minecraft:facing_direction")
                if (facing != facingDirection) {
                    breakBlock = true
                }
            } else {
                breakBlock = true
            }
        } else if (facingDirection == "east") {
            const offsetBlock = block.offset(new GMath.Vector3(offsetV * -1, 0, 0))
            if (offsetBlock.permutation.hasTag("vtng_rt:piston")) {
                const permutation = offsetBlock.permutation
                const facing = permutation.getState("minecraft:facing_direction")
                if (facing != facingDirection) {
                    breakBlock = true
                }
            } else {
                breakBlock = true
            }
        }
        if (breakBlock) {
            console.warn(offsetV)
            block.setType("minecraft:air")
        }
    }
}
class Springboard {
    /**
     * 
     * @param {mc.BlockComponentEntityFallOnEvent} ev 
     */
    onEntityFallOn(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const permutation = block.permutation
        const springState = permutation.getState('vtng_rt:spring_on')
        const withRedstone = permutation.getState('vtng_rt:on')
        if (withRedstone && springState) {
            dimension.playSound("tile.piston.in", block)
            block.setPermutation(permutation.withState('vtng_rt:spring_on', false))
        }
    }
    /**
     * 
     * @param {mc.BlockComponentTickEvent} ev 
     */
    onTick(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const permutation = block.permutation
        const direction = permutation.getState("minecraft:cardinal_direction")
        const springState = permutation.getState('vtng_rt:spring_on')
        const withRedstone = permutation.getState('vtng_rt:on')

        if (withRedstone && springState == false) {
            const entities = dimension.getEntitiesAtBlockLocation(block.bottomCenter()).filter((e) => e.typeId == "minecraft:item")
            dimension.playSound("tile.piston.out", block)
            block.setPermutation(permutation.withState('vtng_rt:spring_on', true))
            entities.forEach((entity) => {
                entity.teleport(block.center())
                entity.applyImpulse(new GMath.Vector3(0, 1, 0).add(directions[direction]).multiplyScalar(0.5))
            })

        } else if (!withRedstone) {
            if (springState) {
                dimension.playSound("tile.piston.in", block)
            }
            block.setPermutation(permutation.withState('vtng_rt:spring_on', false))
        }
    }
}
class Breeder {
    /**
    * 
    * @param {mc.BlockComponentTickEvent} ev 
    */
    onTick(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const permutation = block.permutation
        const withRedstone = permutation.getState('vtng_rt:on')
        const containerBlock = block.above().getComponent("inventory") ? block.above() : undefined
        const blockCoord = `${block.x} ${block.y} ${block.z}`
        const validBreeding = {
            "minecraft:cow": {
                "spawn_type": "minecraft:cow",
                "grow_event": "minecraft:ageable_grow_up",
                "spawn_event": "minecraft:entity_born",
                "food": "minecraft:wheat"
            },
            "minecraft:chicken": {
                "spawn_type": "minecraft:chicken",
                "grow_event": "minecraft:ageable_grow_up",
                "spawn_event": "minecraft:entity_born",
                "food": "minecraft:seeds"
            },
            "minecraft:sheep": {
                "spawn_type": "minecraft:sheep",
                "grow_event": "minecraft:ageable_grow_up",
                "spawn_event": "minecraft:entity_born",
                "food": "minecraft:wheat"
            },
            "minecraft:pig": {
                "spawn_type": "minecraft:pig",
                "grow_event": "minecraft:ageable_grow_up",
                "spawn_event": "minecraft:entity_born",
                "food": "minecraft:carrot"
            }
        }
        if (tempBlockDatas[blockCoord] == undefined) {
            tempBlockDatas[blockCoord] = {
                breeded: []
            }
        }
        if (containerBlock && withRedstone) {
            let targets = dimension.getEntities({ location: block.below().center(), volume: new GMath.Vector3(0, 2, 0) })
            let breederType = ""
            let breeding = []
            let babies = []
            let babyTarget = undefined
            babies = targets.filter(e => e.typeId in validBreeding && e.getComponent("is_baby"))
            targets = targets.filter(e => e.typeId in validBreeding && !e.getComponent("is_baby"))
            if (babies.length > 0 && breederType == "") {

                breederType = babies[0].typeId
                babyTarget = babies[0]

            } else {
                targets.forEach(e => {
                    if (breederType == "" && !tempBlockDatas[blockCoord].breeded.includes(e.id)) {
                        breederType = e.typeId
                        breeding[0] = e
                    } else if (breederType == e.typeId && breeding[0] && !tempBlockDatas[blockCoord].breeded.includes(e.id)) {
                        breeding[1] = e
                    }
                })
            }

            if (breeding.length < 2 && babyTarget == undefined) { return; }
            const breedData = validBreeding[breederType]
            const container = containerBlock.getComponent("inventory").container
            const inventory = getItemsType([breedData.food], container)
            if (inventory.length > 0) {
                const slotTarget = inventory.find(e => (babyTarget == undefined && e.amount >= 2) || (babyTarget && e.amount >= 4))
                if (!slotTarget) { return; }

                tempBlockDatas[blockCoord].breeded = []
                if (babyTarget == undefined) {
                    operateSlot(slotTarget, "-", 2)
                    const baby = dimension.spawnEntity(breedData.spawn_type, breeding[0].location)
                    breedData.spawn_event ? baby.triggerEvent(breedData.spawn_event) : null
                    dimension.playSound("random.eat", block.location)
                    dimension.spawnParticle("minecraft:heart_particle", breeding[0].getHeadLocation())
                    dimension.spawnParticle("minecraft:heart_particle", breeding[1].getHeadLocation())
                    tempBlockDatas[blockCoord].breeded[0] = breeding[0].id
                    tempBlockDatas[blockCoord].breeded[1] = breeding[1].id
                } else {

                    operateSlot(slotTarget, "-", 4)
                    babyTarget.triggerEvent(breedData.grow_event)
                    dimension.playSound("random.eat", block.location)
                    dimension.spawnParticle("minecraft:crop_growth_emitter", babyTarget.getHeadLocation())
                }
            }
        }
    }
}
class PreStove {
    /**
     * 
     * @param {mc.BlockComponentPlayerPlaceBeforeEvent} ev 
     */
    beforeOnPlayerPlace(ev) {
        const block = ev.block
        const blockAbove = block.above()
        const dimension = ev.dimension
        const permutationToPlace = ev.permutationToPlace
        if (!blockAbove.isAir) {
            ev.cancel = true
            return;
        }
        dimension.spawnEntity("vtng_rt:stove_container", block.bottomCenter())
        blockAbove.setPermutation(mc.BlockPermutation.resolve(permutationToPlace.type.id, { "vtng_rt:placed": true, "vtng_rt:upper_bit": true }))
    }
    /**
     * 
     * @param {mc.BlockComponentOnPlaceEvent} ev 
     */
    onPlace(ev) {
        const block = ev.block
        const previousBlock = ev.previousBlock
        const placed = block.permutation.getState("vtng_rt:placed")
        if (placed == false) {
            block.setPermutation(block.permutation.withState("vtng_rt:placed", true))
        }

    }
}
class Stove extends PreStove {
    /**
    * 
    * @param {mc.BlockComponentTickEvent} ev 
    */
    onTick(ev) {
        try {
            const block = ev.block
            const dimension = ev.dimension
            const permutation = block.permutation
            const withRedstone = permutation.getState('vtng_rt:on')
            const blockCoord = `${block.x} ${block.y} ${block.z}`
            let hoppers = Object.values(directions).map((pos, i) => {
                const blockT = block.offset(pos)
                const permutation = blockT.permutation
                const reverseValue = Object.keys(directions).indexOf(Object.values(directionsReverse)[i])
                if (blockT && blockT.typeId == "minecraft:hopper") {
                    if (i > 1 && reverseValue != -1) {
                        if (permutation.getState("facing_direction") == reverseValue) {
                            const container = blockT.getComponent("inventory").container
                            if (container) {
                                const items = getItemsType(Object.keys(blocksConfig.stove.items), container)
                                if (items.length > 0) {
                                    return blockT
                                }
                            }
                        }
                    }
                }

                return undefined
            })
            hoppers = hoppers.filter(e => e != undefined)
            const entity = dimension.getEntitiesAtBlockLocation(block.bottomCenter()).find(e => e.typeId == 'vtng_rt:stove_container')
            if (!entity) { return; }
            if (hoppers.length > 0) {
                const hopperTarget = hoppers[0]
                const container = hopperTarget.getComponent("inventory").container
                const items = getItemsType(Object.keys(blocksConfig.stove.items), container)
                const item = items[0]
                /**@type {mc.Container} */
                const inventory = entity.getComponent("inventory").container
                const inventorySlot = inventory.getSlot(0)
                if (item && item.hasItem() && inventorySlot.isValid() && !inventorySlot.hasItem()) {
                    inventorySlot.setItem(new mc.ItemStack(item.typeId))
                    operateSlot(item, "-", 1)

                }
            }
        } catch (e) { }
    }
    /**
       * 
       * @param {mc.BlockComponentPlayerInteractEvent} ev 
       */
    onPlayerInteract(ev) {
        try {
            const block = ev.block
            const dimension = ev.dimension
            const permutation = block.permutation
            const withRedstone = permutation.getState('vtng_rt:on')
            /**
             * @type {mc.ContainerSlot}
             */
            const mainhand = ev.player.getComponent("equippable").getEquipmentSlot("Mainhand")
            const entity = dimension.getEntitiesAtBlockLocation(block.bottomCenter()).find(e => e.typeId == 'vtng_rt:stove_container')
            if ((!entity) || (!mainhand.hasItem() || (mainhand.hasItem() && (mainhand.typeId in blocksConfig.stove.items) == false))) { return; }
            /**
             * @type {mc.Container}
             */
            const inventory = entity.getComponent("inventory").container
            const inventorySlot = inventory.getSlot(0)
            /**
             * @type {{
             *  uv: mc.Vector2,
             * duration: number,
             * stages: number,
             * drop_items: {type: string, amount:number}[]
             * }}
             */
            if (!inventorySlot.hasItem()) {
                operateSlot(mainhand, "-", 1)
                inventorySlot.setItem(new mc.ItemStack(mainhand.typeId))
            }
        } catch (e) { }
    }
}
class BlockRotator {
    /**
     * 
     * @param {mc.BlockComponentTickEvent} ev 
     */
    onTick(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const permutation = block.permutation
        const facingDirection = permutation.getState('minecraft:block_face')
        const withRedstone = permutation.getState('vtng_rt:on')
        const rotated = permutation.getState('vtng_rt:rotated')
        const rotateBy = permutation.getState('vtng_rt:rotate_by')
        const blockCoord = `${block.x}_${block.y}_${block.z}`
        let blockTarget = undefined
        if (withRedstone && rotated == false) {
            blockTarget = block.offset(directions[facingDirection])
            if (blockTarget && blockTarget.permutation.hasTag("vtng_rt:unrotable") == false) {
                const structure = mc.world.structureManager.createFromWorld(`vtng_rt:${blockCoord}`, dimension, blockTarget, blockTarget, { "includeEntities": false, "saveMode": mc.StructureSaveMode.Memory })
                mc.world.structureManager.place(structure, dimension, blockTarget, { rotation: rotateBy })
                mc.world.structureManager.delete(structure)
                block.setPermutation(permutation.withState("vtng_rt:rotated", true))
            }
        } else if (!withRedstone) {
            block.setPermutation(permutation.withState("vtng_rt:rotated", false))
        }
    }
    /**
  * 
  * @param {mc.BlockComponentPlayerInteractEvent} ev 
  */
    onPlayerInteract(ev) {
        const block = ev.block
        const permutation = block.permutation
        const rotateBy = permutation.getState('vtng_rt:rotate_by')
        const player = ev.player
        const ui = new mcui.ModalFormData()
        const options = {
            text: [
                { rawtext: [{ translate: "vtng_rt.block_rotator_90.ui" }] },
                { rawtext: [{ translate: "vtng_rt.block_rotator_180.ui" }] },
                { rawtext: [{ translate: "vtng_rt.block_rotator_270.ui" }] }
            ],
            value: [
                "Rotate90",
                "Rotate180",
                "Rotate270"
            ]
        }
        ui.title({ rawtext: [{ translate: 'vtng_rt.block_rotator_title.ui' }] })
        ui.dropdown({ rawtext: [{ translate: 'vtng_rt.block_rotator_option.ui' }] }, options.text)
        ui.submitButton({ rawtext: [{ translate: 'vtng_rt.save.ui' }] })
        ui.show(player).then(form => {
            if (form.canceled == false) {
                const option = options.value[form.formValues[0]]
                block.setPermutation(permutation.withState("vtng_rt:rotate_by", option))
            }
        })
    }
}
class BearTrap {
    /**
    * 
    * @param {mc.BlockComponentStepOnEvent} ev 
    */
    onStepOn(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const permutation = block.permutation
        const trappedState = permutation.getState('vtng_rt:trapped')
        const withRedstone = permutation.getState('vtng_rt:on')
        const blockCoord = `${block.x} ${block.y} ${block.z}`
        const entity = ev.entity
        if (trappedState == "unlocked") {
            ///console.warn("Capturado")
            tempBlockDatas[blockCoord] = entity
            dimension.playSound("mob.evocation_fangs.attack", block)
            block.setPermutation(permutation.withState('vtng_rt:trapped', "trapped"))
        }
    }
    /**
    * 
    * @param {mc.BlockComponentStepOnEvent} ev 
    */
    onStepOff(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const permutation = block.permutation
        const trappedState = permutation.getState('vtng_rt:trapped')
        const withRedstone = permutation.getState('vtng_rt:on')
        const blockCoord = `${block.x} ${block.y} ${block.z}`
        const entity = ev.entity
        const trapEntity = tempBlockDatas[blockCoord]
        if (trappedState == "trapped") {
            if (trapEntity && entity.id == trapEntity.id) {
                tempBlockDatas[blockCoord] = undefined
            }
        }
    }
    /**
     * 
     * @param {mc.BlockComponentTickEvent} ev 
     */
    onTick(ev) {
        const block = ev.block
        const dimension = ev.dimension
        let permutation = block.permutation
        const withRedstone = permutation.getState('vtng_rt:on')
        const trappedState = permutation.getState('vtng_rt:trapped')
        const pulsed = permutation.getState('vtng_rt:pulsed')
        const blockCoord = `${block.x} ${block.y} ${block.z}`
        /**
         * @type {mc.Entity}
         */
        const entity = tempBlockDatas[blockCoord]
        if (pulsed == false && withRedstone && (trappedState == "trapped" || trappedState == "locked")) {
            ///console.warn("Destravado")
            dimension.playSound("close.iron_trapdoor", block)
            tempBlockDatas[blockCoord] = undefined
            permutation = permutation.withState("vtng_rt:trapped", "unlocked")
            block.setPermutation(permutation)

        } else if (trappedState == "trapped") {
            if (!entity || (entity && !entity.isValid())) {
                tempBlockDatas[blockCoord] = undefined
            } else {
                if (mc.system.currentTick % (20 * 2) === 0) {
                    entity.applyDamage(1, { "cause": mc.EntityDamageCause.freezing })
                }
                entity.addEffect("slowness", 4, { amplifier: 6, showParticles: false })
            }

        }
        if (withRedstone && pulsed == false) {
            permutation = permutation.withState("vtng_rt:pulsed", true)
            block.setPermutation(permutation)
        } else if (!withRedstone && pulsed) {
            permutation = permutation.withState("vtng_rt:pulsed", false)
            block.setPermutation(permutation)
        }
    }
}
class Camera {
    /**
     * @param {mc.BlockComponentOnPlaceEvent} ev
     */
    onPlace(ev) {
        const block = ev.block
        const previousPerm = ev.previousBlock
        const permutation = block.permutation
        const blockCoord = `${block.x} ${block.y} ${block.z}`
        const blockFace = permutation.getState("minecraft:block_face")
        if (previousPerm.type.id == "minecraft:moving_block") {
            block.dimension.runCommand(`setblock ${blockCoord} air destroy`)
        } else {
            block.setPermutation(permutation.withState("vtng_rt:placed", true))
            const camera = block.dimension.spawnEntity(`vtng_rt:camera`, block.bottomCenter())
            camera.setProperty("vtng_rt:rot_y", directionsRot[permutation.getState("minecraft:block_face")])
        }
    }
    /**
   * 
   * @param {mc.BlockComponentPlayerInteractEvent} ev 
   */
    onPlayerInteract(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const permutation = block.permutation
        const player = ev.player
        const cameraEntity = dimension.getEntitiesAtBlockLocation(block.center())[0]
        if (cameraEntity.getDynamicProperty("owner") == "" || cameraEntity.getDynamicProperty("owner") == undefined) {
            const ui = new mcui.ModalFormData()
            ui.title({ rawtext: [{ translate: `vtng_rt.camera_edit.ui` }] })
            ui.textField({ rawtext: [{ translate: `vtng_rt.camera_name.ui` }] }, "", "Camera")
            ui.submitButton({ rawtext: [{ translate: `vtng_rt.save.ui` }] })
            ui.show(player).then((form) => {
                if (form.canceled == false) {
                    const name = form.formValues[0] == "" ? `Camera` : form.formValues[0]
                    cameraEntity.setDynamicProperty("owner", player.id)
                    cameraEntity.setDynamicProperty("name", name)
                    player.sendMessage({ rawtext: [{ translate: `vtng_rt.camera_saved.warn`, with: [name] }] })
                }
            })
        }
    }
}
class Turret {
    /**
     * @param {mc.BlockComponentOnPlaceEvent} ev
     */
    onPlace(ev) {
        const block = ev.block
        const previousPerm = ev.previousBlock
        const permutation = block.permutation
        const blockCoord = `${block.x} ${block.y} ${block.z}`
        const blockFace = permutation.getState("minecraft:block_face")
        if (previousPerm.type.id == "minecraft:moving_block") {
            block.dimension.runCommand(`setblock ${blockCoord} air destroy`)
        } else {
            block.setPermutation(permutation.withState("vtng_rt:placed", true))
            const entity = block.dimension.spawnEntity(`vtng_rt:turret`, block.bottomCenter())
            entity.setProperty("vtng_rt:rot_y", directionsRot[permutation.getState("minecraft:cardinal_direction")])
        }
    }
}
class BlockConveyor {
    /**
    * @param {mc.BlockComponentTickEvent} ev 
    */
    onTick(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const permutation = block.permutation
        const blockFace = permutation.getState("minecraft:block_face")
        const optionsDirection = {
            "z": [
                "up",
                "down"
            ],
            "x": [
                "up",
                "down"
            ],
            "y": [
                "north",
                "south",
                "west",
                "east"
            ]
        }
        const options = {
            "north": {
                "direction": "z",
                "face": "north"
            },
            "south": {
                "direction": "z",
                "face": "south"
            },
            "east": {
                "direction": "x",
                "face": "east"
            },
            "west": {
                "direction": "x",
                "face": "west"
            },
            "up": {
                "direction": "y",
                "face": "up"
            },
            "down": {
                "direction": "y",
                "face": "down"
            }
        }
        const coord = `${block.x} ${block.y} ${block.z}`
        const directionData = options[blockFace]
        /**@type {mc.Block[]} */
        let blocks = optionsDirection[directionData.direction].map(face => block.offset(directions[face]))
        const goToDirection = directions[directionData.face]
        let nextConveyor = block.offset(goToDirection)
        nextConveyor = nextConveyor.typeId == "vtng_rt:block_conveyor"
        try {
            blocks = blocks.filter(blockT => {
                const inBlackList = blockConveyorData.blacklist.includes(blockT.typeId)
                const inBlacklistTag = blockConveyorData.blacklist_tags.find(e => blockT.permutation.hasTag(e))
                return (!inBlackList && !inBlacklistTag && (blockT.isAir && block.isLiquid) == false)
            })
            blocks.forEach((blockT) => {
                const nextBlock = blockT.offset(goToDirection)
                if (nextBlock.isAir) {
                    mc.system.runTimeout(() => {
                        dimension.runCommand(`clone ${blockT.x} ${blockT.y} ${blockT.z} ${blockT.x} ${blockT.y} ${blockT.z} ${nextBlock.x} ${nextBlock.y} ${nextBlock.z} masked move`)
                    }, 2)
                }
            })


        } catch (e) { }
    }
}
class DoublePiston {
    /**
     * 
     * @param {mc.BlockComponentOnPlaceEvent} ev 
     */
    onPlace(ev) {
        const block = ev.block
        const previousBlock = ev.previousBlock
        const permutation = block.permutation
        const facingDirection = permutation.getState("minecraft:facing_direction")
        const placed = block.permutation.getState("vtng_rt:placed")
        if (placed == false) {
            if (previousBlock.type.id != "minecraft:moving_block") {
                const entity = block.dimension.spawnEntity("vtng_rt:double_piston_arm", block.bottomCenter())
                if (facingDirection == "up" || facingDirection == "down") {
                    entity.setProperty("vtng_rt:rot_x", facingDirection == "up" ? 90 : -90)
                } else {
                    entity.setProperty("vtng_rt:rot_y", directionsRot[facingDirection])
                }

            }
            block.setPermutation(block.permutation.withState("vtng_rt:placed", true))
        }
    }
    /**
    * @param {mc.BlockComponentTickEvent} ev 
    */

    onTick(ev) {
        const block = ev.block
        const dimension = ev.dimension
        let permutation = block.permutation
        const withRedstone = permutation.getState("vtng_rt:on")
        const extended = permutation.getState("vtng_rt:extended")
        const expandStage = permutation.getState("vtng_rt:expand_stage")
        const facingDirection = permutation.getState("minecraft:facing_direction")
        const directionOrder = {
            "north": "north",
            "south": "south",
            "west": "west",
            "east": "east",
            "up": "down",
            "down": "up"
        }
        const pistonArm = dimension.getEntitiesAtBlockLocation(block.bottomCenter())[0]
        const moveTo = directions[directionOrder[facingDirection]]
        if (pistonArm) {
            if (withRedstone && expandStage < 2 && extended == false) {
                const add = GMath.clamp(expandStage, 1, 2)
                const startBlock = block.offset(new GMath.Vector3(add, add, add).multiply(moveTo))
                mc.system.runJob(doublePistonExtend(block, startBlock))

            } else if (!withRedstone && expandStage > 0) {
                if (permutation.hasTag("vtng_rt:piston_sticky")) {
                    const add = GMath.clamp(expandStage, 1, 2)
                    const startBlock = block.offset(new GMath.Vector3(1 + add, 1 + add, 1 + add).multiply(moveTo))
                    mc.system.runJob(doublePistonExtend(block, startBlock, true))
                } else {
                    const add = GMath.clamp(expandStage, 1, 2)
                    const startBlock = block.offset(new GMath.Vector3(add, add, add).multiply(moveTo))

                    if (expandStage > 0) {
                        permutation = permutation.withState("vtng_rt:expand_stage", expandStage - 1)
                        if (expandStage == 1) {
                            dimension.playSound(`tile.piston.out`, block.location, { volume: 0.5 })
                        }
                    }
                    startBlock.setPermutation(mc.BlockPermutation.resolve("minecraft:air"))
                }
                block.setPermutation(permutation)
            } else if (withRedstone && expandStage >= 2) {
                const add = GMath.clamp(expandStage, 0, 1)
                const startBlock = block.offset(new GMath.Vector3(1 + add, 1 + add, 1 + add).multiply(moveTo))

                if (!(startBlock.typeId == "vtng_rt:double_piston_arm_collision" && startBlock.permutation.getState("minecraft:facing_direction") == facingDirection) && expandStage == 2) {
                    dimension.runCommand(`setblock ${block.x} ${block.y} ${block.z} air [] destroy`)
                }

            }

        }

    }
}
class ContainerSorter {
    /**
   * @param {mc.BlockComponentTickEvent} ev 
   */
    onTick(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const permutation = block.permutation
        const sortMode = permutation.getState("vtng_rt:sort_mode")
        const coord = `${block.x} ${block.y} ${block.z}`
        const waitMode = permutation.getState("vtng_rt:wait_mode")
        const withRedstone = permutation.getState("vtng_rt:on")
        if (withRedstone && waitMode == "none") {
            /**@type {mc.Block[]} */
            const blockContainers = []
            Object.values(directions).map((offset) => {
                const blockT = block.offset(offset)
                if (blockT && blockT.isValid() && blockT.getComponent("inventory")) {
                    blockContainers.push(blockT)
                }
            })
            if (blockContainers.length > 0) {
                blockContainers.forEach((blockT) => {
                    if (blockT != undefined) {
                        /**@type {mc.Container} */
                        const container = blockT.getComponent("inventory").container
                        const slots = []
                        Array(container.size).fill("i").map((_, i) => {
                            const slot = container.getSlot(i)
                            if (slot) {
                                slot.slotIndex = i
                                if (slot.hasItem()) {
                                    slot.name = slot.typeId.split(":", 2)[1]
                                }
                                slots.push(slot)
                            }
                        })
                        InventorySorter(container, slots, sortMode)
                    }
                })
            }
            block.setPermutation(permutation.withState("vtng_rt:wait_mode", "ready"))
        } else if (!withRedstone) {
            block.setPermutation(permutation.withState("vtng_rt:wait_mode", "none"))
        }

    }
    /**
   * 
   * @param {mc.BlockComponentPlayerInteractEvent} ev 
   */
    onPlayerInteract(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const permutation = block.permutation
        const sortMode = permutation.getState("vtng_rt:sort_mode")
        const coord = `${block.x} ${block.y} ${block.z}`
        const player = ev.player
        const options = [
            "name",
            "name_reverse",
            "amount",
            "amount_reverse"
        ]

        const ui = new mcui.ModalFormData()
        const optionsText = [
            { rawtext: [{ translate: "vtng_rt.alphabetical_order.ui" }] },
            { rawtext: [{ translate: "vtng_rt.decreasing_alphabetical_order.ui" }] },
            { rawtext: [{ translate: "vtng_rt.quantity_order.ui" }] },
            { rawtext: [{ translate: "vtng_rt.decreasing_quantity_order.ui" }] },
        ]
        ui.title({ rawtext: [{ translate: "vtng_rt.container_sorter_title.ui" }] })
        ui.dropdown({ rawtext: [{ translate: "vtng_rt.sort_mode.ui" }] }, optionsText, options.indexOf(sortMode))
        ui.submitButton({ rawtext: [{ translate: "vtng_rt.save.ui" }] })
        ui.show(player).then(form => {
            if (form.canceled == false) {
                const option = options[form.formValues[0]]
                block.setPermutation(permutation.withState("vtng_rt:sort_mode", option))
            }
        })
    }
}
class Laser {
    /**
   * @param {mc.BlockComponentTickEvent} ev 
   */
    onTick(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const permutation = block.permutation
        /**@type {String} */
        const blockFace = permutation.getState("minecraft:block_face")
        const coord = `${block.x} ${block.y} ${block.z}`
        const withRedstone = permutation.getState("vtng_rt:on")
        const noDamageTypes = [
            "minecraft:item",
            "minecraft:armor_stand",
            "minecraft:minecart",
            "minecraft:hopper_minecart",
            "minecraft:command_block_minecart"
        ]
        const directionReverted = {
            "north": "south",
            "south": "north",
            "west": "east",
            "east": "west",
            "up": "down",
            "down": "up"
        }
        if (delayedBlocks[coord] > 0) {
            delayedBlocks[coord] -= 1
        }
        if (withRedstone && !delayedBlocks[coord]) {
            delayedBlocks[coord] = 4
            let targetPermutation = permutation
            targetPermutation = targetPermutation.withState("minecraft:block_face", directionReverted[blockFace])
            const laserTarget = dimension.getBlockFromRay(block.center(), directions[blockFace], { maxDistance: 12, includeLiquidBlocks: false, includePassableBlocks: false, excludePermutations: [permutation] })
            if (laserTarget) {
                const blockT = laserTarget.block
                const tPermutation = blockT.permutation
                const targetCoord = `${blockT.x} ${blockT.y} ${blockT.z}`
                if (blockT && blockT.typeId == block.typeId && tPermutation.getState("minecraft:block_face") == directionReverted[blockFace] && tPermutation.getState("vtng_rt:on") == true) {
                    const faceLoc = new GMath.Vector3(blockT).add(laserTarget.faceLocation)
                    const pos = new GMath.Vector3(block.center()).add(directions[blockFace])
                    const distanceVec = new GMath.Vector3(faceLoc.x - pos.x, faceLoc.y - pos.y, faceLoc.z - pos.z)
                    const step = 0.2
                    let distMin = -1
                    let distMax = 0
                    let extraDist = 1
                    let directionLaser = new GMath.Vector3(0,0,0)
                    directionLaser = distanceVec.normalizeOne()
                    if (blockFace == "north" || blockFace == "south") {
                        distMax = Math.abs(distanceVec.z)
                    } else if (blockFace == "west" || blockFace == "east") {
                        distMax = Math.abs(distanceVec.x)
                    } else if (blockFace == "up" || blockFace == "down") {
                        distMax = Math.abs(distanceVec.y)
                    }
                    if(loadedLasers[coord] == undefined){
                        let searchedActivation = false
                        Object.keys(loadedLasers).forEach(key=>{
                            const coordT = loadedLasers[key]
                            if(coordT == coord){
                                searchedActivation = true
                            }
                        })
                       if(searchedActivation == false){
                        loadedLasers[coord] = targetCoord
                       }
                    }else if(loadedLasers[coord] == targetCoord){
                        for (let i = distMin; i <= distMax; i += step) {
                            try {
                                const posV = new GMath.Vector3(i, i, i).multiply(directionLaser).add(pos)
                                const entities = dimension.getEntities({ location: posV, maxDistance: 1 })
                                entities.forEach((e) => {
                                    e.applyDamage(1, { "cause": mc.EntityDamageCause.fireTick })
                                })
                                dimension.spawnParticle("vtng_rt:laser_3d", posV)
                            } catch (e) { }
                        }
                    }
                    
                }
            }
        }
    }
}
class FakePlate {
    types = {
        "minecraft:dirt": {
            i: 1,
            c: 0
        },
        "minecraft:stone": {
            i: 2,
            c: 0
        },
        "minecraft:andesite": {
            i: 3,
            c: 0
        },
        "minecraft:granite": {
            i: 4,
            c: 0
        },
        "minecraft:diorite": {
            i: 5,
            c: 0
        },
        "minecraft:netherrack": {
            i: 6,
            c: 0
        },
        "minecraft:end_stone": {
            i: 7,
            c: 0
        },
        "minecraft:oak_planks": {
            i: 0,
            c: 1
        },
        "minecraft:birch_planks": {
            i: 1,
            c: 1
        },
        "minecraft:spruce_planks": {
            i: 2,
            c: 1
        },
        "minecraft:dark_oak_planks": {
            i: 3,
            c: 1
        },
        "minecraft:jungle_planks": {
            i: 4,
            c: 1
        },
        "minecraft:acacia_planks": {
            i: 5,
            c: 1
        },
        "minecraft:cherry_planks": {
            i: 6,
            c: 1
        },
        "minecraft:mangrove_planks": {
            i: 7,
            c: 1
        },
        "minecraft:sand": {
            i: 0,
            c: 2
        },
        "minecraft:grass_block": {
            i: 1,
            c: 2
        }
    }
    constructor() {
        mc.world.beforeEvents.itemUseOn.subscribe(data => {
            const item = data.itemStack
            const block = data.block
            if (block.typeId.startsWith("vtng_rt:fake_plate")) {
                let permutation = block.permutation
                if (item.typeId in this.types) {
                    data.cancel = true
                    mc.system.run(() => {
                        const texData = this.types[item.typeId]
                        permutation = permutation.withState("vtng_rt:tex_i", texData.i)
                        permutation = permutation.withState("vtng_rt:tex_c", texData.c)
                        block.setPermutation(permutation)
                    })
                }
            }
        })
    }
    /**
     * 
     * @param {mc.BlockComponentEntityFallOnEvent} ev 
     */
    onEntityFallOn(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const entity = ev.entity
        if (entity.typeId != "minecraft:item" && !entity.getComponent("projectile")) {
            if (block.hasTag("vtng_rt:explosive") == false) {
                dimension.runCommand(`setblock ${block.x} ${block.y} ${block.z} air [] destroy`)
            } else {
                dimension.createExplosion(block.bottomCenter(), 2, { "breaksBlocks": true, "causesFire": true, "allowUnderwater": false })
            }
        }
    }
    onStepOn(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const entity = ev.entity
        if (entity.typeId != "minecraft:item" && !entity.getComponent("projectile")) {
            if (entity.isSneaking == false) {
                if (block.hasTag("vtng_rt:explosive") == false) {
                    dimension.runCommand(`setblock ${block.x} ${block.y} ${block.z} air [] destroy`)
                } else {
                    dimension.createExplosion(block.bottomCenter(), 2, { "breaksBlocks": true, "causesFire": true, "allowUnderwater": false })
                }
            }

        }
    }
    onStepOff(ev) {
        const block = ev.block
        const dimension = ev.dimension
        const entity = ev.entity
        if (entity && entity.typeId != "minecraft:item" && !entity.getComponent("projectile")) {
            if (entity.isSneaking == false) {
                if (block.hasTag("vtng_rt:explosive") == false) {
                    dimension.runCommand(`setblock ${block.x} ${block.y} ${block.z} air [] destroy`)
                } else {
                    dimension.createExplosion(block.bottomCenter(), 2, { "breaksBlocks": true, "causesFire": true, "allowUnderwater": false })
                }
            }

        }

    }
    /**
   * 
   * @param {mc.BlockComponentPlayerInteractEvent} ev 
   */
    onPlayerInteract(ev) { }
}
//Register
blockComponents["vtng_rt:breeder"] = new Breeder()
blockComponents["vtng_rt:springboard"] = new Springboard()
blockComponents["vtng_rt:redstone_activator"] = new RedstoneActivation()
blockComponents["vtng_rt:stove"] = new Stove()
blockComponents["vtng_rt:pre_stove"] = new PreStove()
blockComponents["vtng_rt:block_rotator"] = new BlockRotator()
blockComponents["vtng_rt:bear_trap"] = new BearTrap()
blockComponents["vtng_rt:camera"] = new Camera()
blockComponents["vtng_rt:turret"] = new Turret()
blockComponents["vtng_rt:block_conveyor"] = new BlockConveyor()
blockComponents["vtng_rt:double_piston"] = new DoublePiston()
blockComponents["vtng_rt:piston_break"] = new PistonBreak()
blockComponents["vtng_rt:container_sorter"] = new ContainerSorter()
blockComponents["vtng_rt:laser"] = new Laser()
blockComponents["vtng_rt:fake_plate"] = new FakePlate()