import { BlockPermutation, MinecraftDimensionTypes, system, world } from '@minecraft/server';  
import { ModalFormData, ActionFormData } from '@minecraft/server-ui';
import Area_Names from "./Area_Names.js";

let globalWaypoints = [];
const distanceDivide = 128;

// Initialize Waypoints
system.runTimeout(() => {
    const waypointData = world.getDynamicProperty("waypoints");
    if (waypointData) {
        globalWaypoints = JSON.parse(waypointData);
    }
    world.sendMessage("§e[Simple Waystone] Addon Active");
    console.warn(`§e[Simple Waystone] Addon Active`)
}, 60);

// Set input permissions for newly joined players
world.afterEvents.playerJoin.subscribe(({ playerName }) => {
    const dimensions = [MinecraftDimensionTypes.overworld, MinecraftDimensionTypes.nether, MinecraftDimensionTypes.theEnd];
    for (const dimension of dimensions) {
        const player = world.getDimension(dimension).getEntities({ name: playerName })[0];
        if (!player) continue;
        system.runTimeout(() => {
            playerResetPermissions(player)
        }, 20);
    }
});

// Handle waypoint block breaking
world.afterEvents.playerBreakBlock.subscribe((eventData) => {
    const { brokenBlockPermutation, block, player, dimension } = eventData;
    const { location } = block;
    const blockName = brokenBlockPermutation.type.id;

    if (!blockName.includes("sw:waystone")) return;

    updateWaypointsAfterBreak(blockName, location, dimension);

    const newLocation = { x: location.x, y: location.y, z: location.z };
    const index = findWaypointIndex(blockName, newLocation);
    if (index === -1) return;

    globalWaypoints.splice(index, 1);
    updateWorldWaypoints();
});

// Handle Gate Token Use
world.afterEvents.itemUse.subscribe(({source : player, itemStack}) => { 
    if(itemStack.typeId == "sw:gate_token"){
        const playerLocation = player.location
        handleWaypointMenu(player,{name:"Gate Token"},playerLocation, true)
    }
})

system.afterEvents.scriptEventReceive.subscribe(({id, sourceEntity})=>{
    const idArr = id.split(":")
    const cmd = `!${idArr[0]} ${idArr[1]}`
    command(cmd, sourceEntity)
})

// Handle Addon Commands
function command(message, player){
    const playerMsg = message.toLowerCase()
    const msgArgs = playerMsg.split(" ")
    if(msgArgs[0].includes("!sw")){
        switch(msgArgs[1]){
            case "delete":
                player.setDynamicProperty("waypoints", "[]");
                player.sendMessage(`§e[Simple Waystone] Cleared Personal Waypoints.`)

            case "diagnose":
                switch(msgArgs[2]){
                    case "world":
                        player.sendMessage(`§eTotal waypoints in world: ${globalWaypoints.length}, World Property Character Limit: ${JSON.stringify(globalWaypoints).length}/32767`)
                        for(let wpEntry of JSON.parse(world.getDynamicProperty("waypoints"))){
                            player.sendMessage("§a" + JSON.stringify(wpEntry))
                        }
                    case "me":
                        player.sendMessage(`§eTotal waypoints registered: ${JSON.parse(player.getDynamicProperty("waypoints")).length}, Own Property Character Limit: ${player.getDynamicProperty("waypoints").length}/32767`)
                        for(let wpEntry of JSON.parse(player.getDynamicProperty("waypoints"))){
                            player.sendMessage("§a" + JSON.stringify(wpEntry))
                        }
                    default:
                }
            default:
        }
    }
}

function updateWaypointsAfterBreak(blockName, location, dimension) {
    const air = BlockPermutation.resolve("minecraft:air");
    if (blockName.includes("bot")) {
        dimension.getBlock(location).setPermutation(air);
        dimension.getBlock({ x: location.x, y: location.y + 1, z: location.z }).setPermutation(air);
    } else {
        dimension.getBlock(location).setPermutation(air);
        dimension.getBlock({ x: location.x, y: location.y - 1, z: location.z }).setPermutation(air);
    }
}

function findWaypointIndex(blockName, location) {
    return globalWaypoints.findIndex((wp) => {
        const wpLocation = blockName.includes("bot") ? wp.locationBot : wp.locationTop;
        return wpLocation.x === location.x && wpLocation.y === location.y && wpLocation.z === location.z;
    });
}

function updateWorldWaypoints() {
    const waypointString = JSON.stringify(globalWaypoints);
    world.setDynamicProperty("waypoints", waypointString);
    globalWaypoints = JSON.parse(world.getDynamicProperty("waypoints"));
}

// Handle player interactions with waystones
system.runInterval(() => {
    const playersWithTags = world.getPlayers({ tags: ["open_waystone"] });
    for (const player of playersWithTags) {
        player.removeTag("open_waystone");
        handleWaypointInteraction(player);
    }
}, 2);

function handleWaypointInteraction(player) {
    const waypointRaycast = player.getBlockFromViewDirection({ maxDistance: 16 });
    if (!waypointRaycast) return;

    const waypointBlock = waypointRaycast.block;
    const waypointLocation = waypointBlock.location;
    const waypointBlockName = waypointBlock.typeId;

    if (!waypointBlockName.includes("sw:waystone")) return;

    processWaypointInteraction(player, waypointBlockName, waypointLocation);
}

function processWaypointInteraction(player, blockName, location) {
    for (const wp of globalWaypoints) {
        const oldLocation = blockName.includes("bot") ? wp.locationBot : wp.locationTop;
        if (JSON.stringify(oldLocation) === JSON.stringify(location)) {
            handleWaypointMenu(player, wp, location);
            return;
        }

        if (getDistance(oldLocation, location) <= 2) {
            player.sendMessage("Waypoint can't be near to other waypoints");
            return;
        }
    }

    showWaypointSetupForm(player, blockName, location);
}

function handleWaypointMenu(player, wp, location, itemToken = false) {
    let playerWaypoints = getPlayerWaypoints(player);
    if(!itemToken){
        if (!isWaypointRegistered(playerWaypoints, wp)) {
            registerWaypoint(player, wp);
            playerWaypoints = getPlayerWaypoints(player); //Update playWaypoints after register
            player.sendMessage("§eWaypoint Registered!");
            player.playSound("random.levelup");
        }
    }

    validatePlayerWaypoints(player, playerWaypoints);
    playerWaypoints = getPlayerWaypoints(player); //Update playerWaypoints after validate
    showWaypointListForm(player, wp, playerWaypoints, location);
}

function getPlayerWaypoints(player) {
    const publicWaypoints = getPublicWaypoints()
    const playerWaypointData = player.getDynamicProperty("waypoints");
    if (playerWaypointData) {
        return JSON.parse(playerWaypointData).concat(publicWaypoints);
    } else {
        player.setDynamicProperty("waypoints", "[]");
        return [];
    }
}

function getPublicWaypoints(){
    const publicWaypoints = globalWaypoints.filter((waypointData) => {
        const isPublic = waypointData.public
        if(isPublic){
            return true
        }
        return false
    })
    return publicWaypoints
}

function isWaypointRegistered(playerWaypoints, waypoint) {
    return playerWaypoints.some(wp => JSON.stringify(wp.id) === JSON.stringify(waypoint.id));
}

function registerWaypoint(player, waypoint) {
    const playerWaypoints = getPlayerWaypoints(player);
    playerWaypoints.push(waypoint);
    try {
        player.setDynamicProperty("waypoints", JSON.stringify(playerWaypoints));
    } catch (e) {
        player.sendMessage(`§cMaximum waystone reached! (Current Waystones:${playerWaypoints.length})`);
        playerWaypoints.pop();
    }
}

function validatePlayerWaypoints(player, playerWaypoints) {
    const validWaypoints = playerWaypoints.filter(wp => {
        if(wp.public) return false
        return globalWaypoints.some(globalWp => JSON.stringify(wp.id) === JSON.stringify(globalWp.id));
    });
    player.setDynamicProperty("waypoints", JSON.stringify(validWaypoints));
}

function showWaypointListForm(player, currentWaypoint, playerWaypoints, location) {
    system.runTimeout(() => {
        const waypointList = new ActionFormData()
            .title("Waypoint List")
            .body(`Current Waypoint: ${currentWaypoint.name}`);
        const xpNeeded = getXpNeededList(player, currentWaypoint, playerWaypoints, location, waypointList);

        waypointList.show(player).then(response => {
            if (response && response.selection !== undefined) {
                handleWaypointSelection(player, xpNeeded, response.selection, playerWaypoints, location);
            }
        }).catch(error => {
            player.sendMessage(`Error showing waypoint list: ${error.message}`);
        });
    }, 1);
}

function getXpNeededList(player, currentWaypoint, playerWaypoints, location, waypointList) {
    const xpNeeded = [];
    for (const wp of playerWaypoints) {
        if (JSON.stringify(currentWaypoint.id) === JSON.stringify(wp.id)) {
            addButtonForCurrentWaypoint(waypointList, wp);
            xpNeeded.push(0);
        } else {
            const xpRequirement = calculateXpRequirement(wp, location);
            addButtonForOtherWaypoint(waypointList, wp, xpRequirement);
            xpNeeded.push(xpRequirement);
        }
    }
    return xpNeeded;
}

function addButtonForCurrentWaypoint(waypointList, waypoint) {
    const buttonText = `§e§l>> §r${waypoint.name} §l§e<<\nYour Here`;
    waypointList.button(buttonText);
}

function calculateXpRequirement(waypoint, location) {
    const distance = getDistance(waypoint.locationTop, location);
    return Math.min(Math.floor(distance / distanceDivide), 16);
}

function addButtonForOtherWaypoint(waypointList, waypoint, xpRequirement) {
    const dimensionColor = getDimensionColor(waypoint.dimension);
    const buttonText = `§r${dimensionColor}${waypoint.name} §2§l${xpRequirement}`;
    waypointList.button(buttonText);
}

function getDimensionColor(dimension) {
    switch (dimension) {
        case "overworld":
            return "";
        case "nether":
            return "§4";
        case "end":
            return "§5";
        default:
            return "";
    }
}

function handleWaypointSelection(player, xpNeeded, selection, playerWaypoints, location) {
    if (xpNeeded[selection] <= player.level) {
        teleportPlayerToWaypoint(player, playerWaypoints[selection], xpNeeded[selection], location);
    } else {
        player.sendMessage("§cNot Enough Levels to teleport.");
    }
}

function teleportPlayerToWaypoint(player, waypoint, xpRequired, location) {
    const waypointDimension = world.getDimension(waypoint.dimension)
    const targetLocation = waypoint.locationBot
    //const targetLocation = getFreeArea(waypoint.dimension, waypoint.locationBot);
    
    if (targetLocation) {
        player.addLevels(-xpRequired);
        player.playSound("mob.shulker.teleport");
        let removedToken = removeGateToken(player)        
        system.runTimeout(() => {
            playerResetPermissions(player)
            player.teleport(targetLocation, { dimension: waypointDimension });
        }, removedToken ? 19 : 8);
    } else {
        player.sendMessage("§cWaypoint is Obstructed.");
    }
}

function removeGateToken(player){
    const playerContainer = player.getComponent("inventory").container;
    const itemStack = playerContainer.getItem(player.selectedSlotIndex);
    if(itemStack?.typeId == "sw:gate_token"){
        if(itemStack.amount == 1){
            player.getComponent("inventory").container.setItem(player.selectedSlotIndex, undefined)
        }else{
            itemStack.amount -= 1
            player.getComponent("inventory").container.setItem(player.selectedSlotIndex, itemStack)
        }
        player.runCommandAsync(`inputpermission set @s camera disabled`)
        player.runCommandAsync(`inputpermission set @s movement disabled`)
        player.runCommandAsync(`execute as @s at @s rotated as @s run summon sw:token ~~0.65~ ~ ~`)
        return true
    }
    return false
}

function playerResetPermissions(player){
    player.runCommandAsync(`inputpermission set @s camera enabled`)  
    player.runCommandAsync(`inputpermission set @s movement enabled`)
}

function showWaypointSetupForm(player, blockName, location) {
    const setupForm = new ModalFormData()
        .title("Waystone")
        .textField("Waystone Name", "Name", Area_Names.generateName(globalWaypoints))
        .toggle("Public",false);
    setupForm.show(player).then(formData => {
        if (formData.canceled) return;

        const waypointName = formData.formValues[0];
        const waypointPublicToggle = formData.formValues[1];
        if (blockName.includes("bot")) {
            setBottomWaypoint(player, blockName, location, waypointName, waypointPublicToggle);
        } else {
            setTopWaypoint(player, blockName, location, waypointName, waypointPublicToggle);
        }
    }).catch(error => {
        player.sendMessage(`Error showing waypoint setup form: ${error.message}`);
    });
}

function setBottomWaypoint(player, blockName, location, name, waypointPublicToggle) {
    const dimension = player.dimension.id.split(":")[1];
    const newWaypoint = {
        id: Date.now() + globalWaypoints.length,
        name,
        locationBot: location,
        locationTop: { x: location.x, y: location.y + 1, z: location.z },
        dimension,
        public: waypointPublicToggle
    };
    globalWaypoints.push(newWaypoint);
    updateWorldWaypoints();
    player.sendMessage("§eWaypoint Added!");
}

function setTopWaypoint(player, blockName, location, name, waypointPublicToggle) {
    const dimension = player.dimension.id.split(":")[1];
    const newWaypoint = {
        id: Date.now() + globalWaypoints.length,
        name,
        locationBot: { x: location.x, y: location.y - 1, z: location.z },
        locationTop: location,
        dimension,
        public: waypointPublicToggle
    };
    globalWaypoints.push(newWaypoint);
    updateWorldWaypoints();
    player.sendMessage("§eWaypoint Added!");
}

function getFreeArea(dimension, location) {
    const blocks = [
        dimension.getBlock(location),
        dimension.getBlock({ x: location.x, y: location.y + 1, z: location.z }),
        dimension.getBlock({ x: location.x, y: location.y + 2, z: location.z })
    ];
    return blocks.every(block => block.typeId.includes("air")) ? location : false;
}

function getDistance(loc1, loc2) {
    const dx = loc1.x - loc2.x;
    const dy = loc1.y - loc2.y;
    const dz = loc1.z - loc2.z;
    return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
