import {world, system, BlockPermutation} from "@minecraft/server"
import { cabooseFamily, locomotiveFamily } from "./trainsAddOn"
import { queryBlock } from "./JM_Utils"

// structure is [[car1, [[block, age], [block2, age]]], [car2, [[block, age], [block2, age]]], ... ]
const lightBlockLocations = [],
// structure of [Block, Block2, ... ]
    ignoreBlockArray = []

world.afterEvents.playerPlaceBlock.subscribe(event => {
    // loop through ALL cars' light block arrays to see if this block is in one
    for (let l = 0; l < lightBlockLocations.length; l++) {
        for (let i = 0; i < lightBlockLocations[l][1].length; i++) {
            const block = queryBlock(lightBlockLocations[l][1][i][0],event.dimension)
            if (!block) continue
            if (block.x == event.block.x && block.y == event.block.y && block.z == event.block.z) {
                ignoreBlockArray.push(block)
                // console.warn("block found in array, removing...")
                lightBlockLocations[l][1].splice(i,1)
                // console.warn(JSON.stringify(ignoreBlockArray))
            }
        }
    }
})

function flashlight(car, maxDist = 20, brightness = 20, offset = 3) {
// set different brightnesses/distances for different train cars here?

    // const maxDist = 20
    // let brightness = 20

    // find car if they exist in lightBlockLocations
    let carBLIndex = lightBlockLocations.findIndex(element => element[0].id == car.id)
    if (carBLIndex == -1) {
        // since they don't exist in the array, add them
        carBLIndex = lightBlockLocations.push([car, []]) - 1
    }

    const lookDir = car.getViewDirection(),
        headLoc = car.location
        headLoc.y += 1
        headLoc.x += (lookDir.x * offset)
        headLoc.z += (lookDir.z * offset)

    const stepSize = 0.5
    let curBlock = queryBlock(headLoc,car.dimension),
        lightCount = 0
    // search
    for (let i = 1; i < (maxDist/stepSize); i++) {
        if (!curBlock) return
        if (!(curBlock.isAir || curBlock.permutation.matches("light_block")) || brightness <= 0) return
        // let curBlock
        try {
            curBlock = car.dimension.getBlock({
                x:headLoc.x + (lookDir.x * stepSize * i),
                y:headLoc.y + (lookDir.y * stepSize * i),
                z:headLoc.z + (lookDir.z * stepSize * i)
            })   
        } catch (error) {
            console.warn(`error while trying to step through flashlight block placements! ${error}`)
        }
        if (!curBlock) {
            // ray hit unloaded chunks
            break
        } else if (ignoreBlockArray.findIndex(element => element.x == curBlock.x && element.y == curBlock.y && element.z == curBlock.z) == -1) {
            // success!
            // lookBlock = curBlock
            if (curBlock.isAir || curBlock.permutation.matches("light_block")) {
                // set light block
                curBlock.setPermutation(BlockPermutation.resolve("light_block",{"block_light_level":Math.min(brightness,15)}))
                lightCount++
                // decrement light level
                if (lightCount > (maxDist / 10)) {
                    brightness-= 1
                    lightCount = 0
                }
                // check storage array for this block
                const blockIndex = lightBlockLocations[carBLIndex][1].findIndex(element => element[0].x == curBlock.x && element[0].y == curBlock.y && element[0].z == curBlock.z)
                if (blockIndex == -1) {
                    // it doesn't exist in the array, so add it
                    lightBlockLocations[carBLIndex][1].push([curBlock.location,0])
                } else {
                    // reset age of this block
                    lightBlockLocations[carBLIndex][1][blockIndex][1] = 0
                }
            }
        } else {
            // I have no idea why this was done, but I haven't found any issues with it being here, so it's staying.
            // WHY IS THIS HERE????
            ignoreBlockArray.splice(0,ignoreBlockArray.length)
        }
    }
}

world.beforeEvents.entityRemove.subscribe(event => {
    // find car in array
    const carIndex = lightBlockLocations.findIndex((element) => element[0].id == event.removedEntity.id)
    // if they exist in the array
    if (carIndex >= 0) {
        const saveBlocks = world.getDynamicPropertyIds().includes("lifeboat_ta:deleteTheseNow") ? JSON.parse(world.getDynamicProperty("lifeboat_ta:deleteTheseNow")) : new Array(0)
        const dim = event.removedEntity.dimension
        // remove all of their blocks next tick
        // save all all blocks of this car to array to be deleted
        for (let j = 0; j < lightBlockLocations[carIndex][1].length; j++) {
            // save the light block data
            const block = queryBlock(lightBlockLocations[carIndex][1][j][0],dim)
            if (!block) continue
            saveBlocks.push({
                location: block.location,
                dimension: dim.id
            })
        }
        world.setDynamicProperty("lifeboat_ta:deleteTheseNow",JSON.stringify(saveBlocks))
        // delete this car's entry in the array
        lightBlockLocations.splice(carIndex,1)
    }
})

world.afterEvents.playerSpawn.subscribe(() => {
    if (world.getAllPlayers().length > 1) return
    // list of blocks from the last game that need to be deleted
    const deleteTheseNow = (world.getDynamicPropertyIds().includes("lifeboat_ta:deleteTheseNow") ? JSON.parse(world.getDynamicProperty("lifeboat_ta:deleteTheseNow")) : new Array(0))
    deleteTheseNow.forEach(element => {
        world.getDimension(element.dimension).getBlock(element.location).setPermutation(BlockPermutation.resolve("air"))
    });
    world.setDynamicProperty("lifeboat_ta:deleteTheseNow")
})

const dimensions = [
    "overworld",
    "nether",
    "the_end"
]

system.runInterval(function tick() {
    try {
        if (world.getAllPlayers().length >= 1) {
            // list of blocks from the last game that need to be deleted
            const deleteTheseNow = (world.getDynamicPropertyIds().includes("lifeboat_ta:deleteTheseNow") ? JSON.parse(world.getDynamicProperty("lifeboat_ta:deleteTheseNow")) : new Array(0))
            deleteTheseNow.forEach(element => {
                world.getDimension(element.dimension).getBlock(element.location).setPermutation(BlockPermutation.resolve("air"))
            });
            world.setDynamicProperty("lifeboat_ta:deleteTheseNow")
        }
    } catch (error) {
        // this errors when a player starts the world back up after leaving while someone was using the flashlight, it tries to delete the blocks but the world isn't loaded yet
    }

    // Light block fade:
    for (let i = 0; i < lightBlockLocations.length; i++) {
        for (let j = 0; j < lightBlockLocations[i][1].length; j++) {
            try {
                // THIS CAN ERROR IF THE BLOCK IS IN A CHUNK THAT HAS BEEN UNLOADED
                const block = lightBlockLocations[i][0].dimension.getBlock(lightBlockLocations[i][1][j][0])                
                const ignore = (element) => element[0].x == block.x && element[0].y == block.y && element[0].z == block.z
                if (lightBlockLocations[i][1].some(ignore)) {
                    // let lightLevel = block.permutation.getState("block_light_level")
                    // check block age
                    if (lightBlockLocations[i][1][j][1] > 0) {
                        // block is too old, remove it
                        lightBlockLocations[i][1].splice(j,1)
                        block.setPermutation(BlockPermutation.resolve("air"))
                    } else {
                        // age + 1
                        lightBlockLocations[i][1][j][1]++
                    }
                } else {
                    console.warn("ignoring block " + JSON.stringify(block.location))
                }
            } catch ({name,message}) {
                // oh well ¯\_(ツ)_/¯
        
                // haha fr though this only happens if a player has left the game, which is handled by the playerLeave beforeEvent handler,
                // but unfortunately we cannot remove the player from the lightBlockLocations array until the next tick.
                // So, this is always attempted just once when a player leaves

                // if block is outside world boundaries:
                if (message.endsWith("which is outside of the world boundaries.")) {
                    console.warn(`removed ${JSON.stringify(lightBlockLocations[i][1][j][0])} from light block array, as it was outside of world boundaries`)
                    lightBlockLocations[i][1].splice(j,1)
                }
            }
        }
    }

    dimensions.forEach(element => {
        const dimension = world.getDimension(element)
        dimension.getEntities({families:[locomotiveFamily]}).forEach(car => {
            if (car.getProperty("lifeboat_ta:light_on")) flashlight(car,undefined, undefined, car.typeId === "lifeboat_ta:drill" ? 0: undefined)
        })
        dimension.getEntities({families:[cabooseFamily]}).forEach(car => {
            if (car.getProperty("lifeboat_ta:light_on")) flashlight(car,1,12,-3)
        })
        dimension.getEntities({type:"lifeboat_ta:aqua_trainvagon"}).forEach(car => {
            if (car.getProperty("lifeboat_ta:liquid_type") === 2) flashlight(car,3,12,-1)
            
        })
    })
})