import { world } from "@minecraft/server";
import { bottomCenter, sendAddonMessage, sendDebugMessage, vectorAdd } from "./JMmcUtils";
import { BS_books } from "./bookshelf";

const golemTypeBlocks = [
    ["oak_log", "oak_golem"],
    ["birch_log", "birch_golem"],
    ["spruce_log", "spruce_golem"],
    ["jungle_log", "jungle_golem"],
    ["acacia_log", "acacia_golem"],
    ["dark_oak_log", "dark_oak_golem"],
    ["cherry_log", "cherry_golem"],
    ["bamboo_block", "bamboo_golem"],
    ["warped_stem", "warped_golem"],
    ["crimson_stem", "crimson_golem"],
    ["red_mushroom_block", "mushroom_golem"],
    ["cobblestone", "cobblestone_golem"],
    ["gold_block", "gold_golem"],
    ["diamond_block", "diamond_golem"],
    ["netherite_block", "netherite_golem"],
    ["sponge", "sponge_golem"],
    ["wet_sponge", "sponge_golem"],
    ["slime", "slime_golem"],
    ["tnt", "tnt_golem"],
    ["glass", "glass_golem"],
    // ["sea_lantern", "sealantern_golem"],
    ["glowstone", "glowstone_golem"],
    ["magma", "magma_golem"],
    ["ice", "ice_golem"],
    ["bookshelf", "bookshelf_golem"],
    ["bone_block", "bone_golem"],
    ["bedrock", "bedrock_golem"],
    ["mangrove_log", "mangrove_golem"],
    ["target", "target_golem"],
    //*UPDTADE
    ["brick_block", "brick_golem"],
    ["stone_bricks", "stone_bricks_golem"],
    ["hay_block", "hay_golem"],
    ["honey_block", "honey_golem"],
    ["cauldron", "cauldron_golem"],
    ["redstone_block", "redstone_golem"],
    ["pale_oak_log", "pale_oak_golem"],
    ["amethyst_block", "amethyst_golem"],
    ["melon_block", "melon_golem"],
    ["lapis_block", "lapis_golem"],
    ["lightning_rod", "lightning_rod_golem"],
    ["hopper", "hopper_golem"],
    ["web", "cobweb_golem"]
];

function firstBookshelfData() {
    const property = "lb_ga:first_bookshelf_golem"
    if (!world.getDynamicPropertyIds().includes(property)) set(false)

    function get() {
        return JSON.parse(world.getDynamicProperty(property))
    }

    function set(data) {
        world.setDynamicProperty(property, JSON.stringify(data))
    }

    return {
        query: function () {
            return get()
        },
        trigger: function () {
            return set(true)
        }
    }
}
const firstBookshelf = firstBookshelfData()

function isGolemBlock(blockType) {
    for (let index = 0; index < golemTypeBlocks.length; index++) {
        if (blockType === golemTypeBlocks[index][0]) return { index: index } // weird formatting in case index = 0 still coerces to "true"
    }
    return false
}

const buildMasks = {
    // Any array with an odd-numbered width should work here.
    // If you change the number of masks, update the search order as well!
    // 1 = pumpkin position, 2 = everything else.  0s are ignored
    // e.g.:
    // MEGA: [
    //   [2, 0, 1, 0, 2],
    //   [2, 2, 2, 2, 2],
    //   [0, 2, 2, 2, 0],
    //   [0, 2, 0, 2, 0],
    //   [2, 2, 0, 2, 2]
    // ],
    large: [
        [0, 1, 0],
        [2, 2, 2],
        [0, 2, 0]
    ]//,
    // small: [
    //   [1],
    //   [2],
    //   [2]
    // ]
},
    directions = {
        up: { x: 0, y: 1, z: 0 },    //+y
        down: { x: 0, y: -1, z: 0 },   //-y
        north: { x: 0, y: 0, z: -1 },   //-z
        east: { x: 1, y: 0, z: 0 },    //+x
        south: { x: 0, y: 0, z: 1 },    //+z
        west: { x: -1, y: 0, z: 0 }    //-x
    },
    searchOrder = ["large"/*,"small"*/]

/**
 * searches each possible build axis of all 6 axes for the given mask.
 * @param {vector3} startLocation 
 * @param {dimension} dimension 
 * @param {Array} mask 
 */
function searchMask(startLocation, dimension, maskType, gameMode) {
    const stepStart = Math.floor(buildMasks[maskType][0].length / 2)
    // up/dn/n/e/s/w
    for (const direction in directions) {
        const transformation = directions[direction];
        // each direction can have two different (symmetrical) dimensions in which the 2d structure can be built
        // technically yes, the small structure gets searched twice, but meh - that's only 12 blocks total wasted
        axis: for (const axisComponent in transformation) {
            // a single x, y, or z component
            const axisValue = transformation[axisComponent];
            if (axisValue !== 0) continue
            let curLocation = JSON.parse(JSON.stringify(startLocation))
            const foundMask = []
            while (foundMask.length < buildMasks[maskType].length) foundMask.push([])
            // height. layer 0 = head height.
            for (let layer = 0; layer < buildMasks[maskType].length; layer++) {
                // stepping sideways through the mask at this layer
                for (let step = 0; step < buildMasks[maskType][0].length; step++) {
                    curLocation[axisComponent] = startLocation[axisComponent] - stepStart + step
                    if (curLocation.y > dimension.heightRange.max || curLocation.y < dimension.heightRange.min) continue axis
                    foundMask[layer].push({
                        type: dimension.getBlock(curLocation).typeId.split(":")[1],
                        location: JSON.parse(JSON.stringify(curLocation))
                    })
                }
                // move outward in the current search direction
                curLocation = vectorAdd(curLocation, transformation)
            }
            let result = compareMasks(foundMask, buildMasks[maskType])
            if (result) {
                sendDebugMessage(`found ${maskType} golem, built of ${result.type}, at ${JSON.stringify(startLocation)}`)
                if (result.type === "bedrock" && gameMode !== "creative") {
                    sendDebugMessage(`bedrock golem only available for building in creative mode`)
                    return true
                }
                result.blockLocations.forEach(location => dimension.getBlock(location).setType("air"))
                const entityString = "lb_ga:" + golemTypeBlocks[isGolemBlock(result.type).index][1] + (maskType === "small" ? "_small" : "")
                try {
                    const golem = dimension.spawnEntity(
                        entityString,
                        bottomCenter(getBottomMostPosition(result.blockLocations))
                    );
                    // if partially wet sponge || entirely wet sponge
                    if (result.special || result.type === "wet_sponge") {
                        let wetPercentage = 1
                        if (result.special) { // partial sponge
                            const data = result.special
                            wetPercentage = data.wet_sponge / (data.wet_sponge + data.sponge)
                        }
                        const waterCount = Math.floor(wetPercentage * 65)
                        sendDebugMessage(`wet percentage: ${wetPercentage * 100}%%, setting water count to ${waterCount}`)
                        golem.setProperty("lb_ga:water_count", waterCount)
                    } else if (result.type === "bookshelf") {
                        BS_books(golem).initialize()
                        if (!firstBookshelf.query()) {
                            sendAddonMessage(world.getAllPlayers(), "firstBookshelf")
                            firstBookshelf.trigger()
                        }
                    }
                } catch (error) {
                    sendDebugMessage(`§4Error spawning entity "${entityString}": ${error}`)
                }
                return true
            }
        }
    }
    return false
}

function getBottomMostPosition(positionArr) {
    let index = 0
    for (let position = 0; position < positionArr.length; position++) {
        if (positionArr[position].y < positionArr[index].y) index = position
    }
    return positionArr[index]
}

/**
 * If masks match, returns the blockType of the matching blocks
 * @param {Array} input array containing translation, position, and blockType data
 * @param {Array} master array of numbers, from buildMasks
 */
function compareMasks(input, master) {
    if (input.length !== master.length || input[0].length !== master[0].length) throw new Error(`§4error! Input and master masks are not comparable \n${JSON.stringify(input)}\n${JSON.stringify(master)}`)
    const blockList = {}
    for (let layer = 0; layer < input.length; layer++) {
        for (let step = 0; step < input[layer].length; step++) {
            const data = input[layer][step]
            const blockType = data.type
            switch (master[layer][step]) {
                case 0:
                    // ignore 0s
                    break
                case 1:
                    if (blockType !== 'carved_pumpkin' && blockType !== 'lit_pumpkin') {
                        // missing head???!
                        sendDebugMessage(`§4uh... this should be impossible`)
                        return false
                    }
                    var headLocation = data.location
                    break
                default:
                    // everything that isn't a 0 or the head position
                    if (!blockList[blockType]) {
                        blockList[blockType] = [data.location]
                        break
                    } else blockList[blockType].push(data.location)
            }
        }
    }
    const blockTypes = Object.keys(blockList)
    const blockLocations = []
    // sendDebugMessage(JSON.stringify(blockList))
    if (!isGolemBlock(blockTypes[0])) return false
    let mixedData
    if (blockTypes.length === 2 && blockTypes.includes("sponge") && blockTypes.includes("wet_sponge")) {
        mixedData = {}
        blockTypes.forEach(blockType => {
            mixedData[blockType] = blockList[blockType].length
        })
        blockList[blockTypes[1]].forEach(location => blockLocations.push(location))
        // sendDebugMessage(`mixedData: ${JSON.stringify(mixedData)}`)
    } else if (blockTypes.length !== 1) return false
    blockList[blockTypes[0]].push(headLocation)
    blockList[blockTypes[0]].forEach(location => blockLocations.push(location))
    return {
        type: blockTypes[0],
        blockLocations: blockLocations,
        special: mixedData
    }
}

world.afterEvents.playerPlaceBlock.subscribe((event) => {
    if (!event.block.permutation.matches("carved_pumpkin") && !event.block.permutation.matches("lit_pumpkin")) return;
    let golemType = 0, result
    while (!result && golemType < searchOrder.length) {
        result = searchMask(event.block.location, event.dimension, searchOrder[golemType], event.player.getGameMode())
        golemType++
    }
});