import { world, system, ItemStack, Player, Entity } from "@minecraft/server";

import { AGGRESSION_LEVEL } from "../watcher/events.js";
import { Mathm } from "../modules/mathm.js";
import { Vec3 } from "../utils/vec.js";
import { ScriptEventListener } from "../utils/scriptevent";

function playerNeedsWatcher(player) {
    return player.getDynamicProperty("mythicus_tw:watcher.isActive") === true && player.getDynamicProperty("mythicus_tw:watcher.isAlive") !== true;
}

ScriptEventListener.subscribe("mythicus_tw:spawnNearbyWatcher", () => {

    const player = world.getPlayers({ maxDistance: 128 }).find(plr => playerNeedsWatcher(plr));
    if (!player) return;

    player.addTag("mythicus_tw:w.iw")
    player.dimension.runCommand('summon "mythicus_tw:watcher" ~ ~ ~')

});

function spawnNearbyWatcher() {

    const player = world.getPlayers({ maxDistance: 128 }).find(plr => playerNeedsWatcher(plr));
    if (!player) return;

    player.addTag("mythicus_tw:w.iw")

}



const PLAYERS_WATCHED = new Set();

system.runInterval(() => {
    PLAYERS_WATCHED.clear();
}, 540);

ScriptEventListener.subscribe("mythicus_tw:updateWatcherPlayers", ({ sourceEntity }) => {
    if (sourceEntity?.isValid() !== true) return;
    for (const player of sourceEntity.dimension.getPlayers({ location: sourceEntity.location, maxDistance: 128 })) {
        PLAYERS_WATCHED.add(player.id)
    }

});

// When a watcher attempts to spawn in, check if nearby players are already being watched
ScriptEventListener.subscribe("mythicus_tw:attemptWatcherSpawn", ({ sourceEntity }) => {

    if (!sourceEntity) return;

    if (sourceEntity.getProperty("mythicus_tw:is_spawned")) return;

    // get nearby players
    const players = sourceEntity.dimension.getPlayers({ location: sourceEntity.location, maxDistance: 128 });

    // remove if no nearby players
    if (players.length === 0) {
        sourceEntity.remove();
        return;
    }

    // remove if nearby player is already being watched
    for (const player of players) {
        if (PLAYERS_WATCHED.has(player.id)) {
            sourceEntity.remove();
            return;
        }
    }

    // double check to ensure no active watchers
    if (sourceEntity.dimension.getEntities({ location: sourceEntity.location, maxDistance: 320, type: "mythicus_tw:watcher" }).length > 1) {
        sourceEntity.remove();
        return;
    }

    // check if watcher spawn height is near a player
    const watcherLoc = sourceEntity.location;
    let isPlayerHeight = false;
    let nearbyPlayerUnderground = false;
    let nearbyPlayerAboveground = false;
    for (const player of players) {
        if (Math.abs(watcherLoc.y - player.location.y) <= 20) isPlayerHeight = true;
        if (player.dimension.getTopmostBlock({ x: player.location.x, z: player.location.z })?.y - 5 >= player.location.y) {
            nearbyPlayerUnderground = true;
        } else {
            nearbyPlayerAboveground = true;
        }
    }

    // high chance to remove if watcher is not near player height
    if (!isPlayerHeight) {

        // set removal chance
        let removalChance = 0.5;
        if (sourceEntity.dimension.getTopmostBlock({ x: watcherLoc.x, z: watcherLoc.z })?.y - 3 >= watcherLoc.y) {
            if (nearbyPlayerUnderground) {
                removalChance = 0.7;
            } else {
                removalChance = 0.9;
            }
        } else {
            if (nearbyPlayerAboveground) {
                removalChance = 0.5;
            } else {
                removalChance = 0.75;
            }
        }

        // remove entity
        if (Math.random() < removalChance) {
            sourceEntity.remove();
            return;
        }

    }

    // random chance to despawn based on aggression
    //if (Math.random() < (0.6 - 0.2 * AGGRESSION_LEVEL)) {
    //    sourceEntity.remove();
    //    return;
    //}

    sourceEntity.setProperty("mythicus_tw:is_spawned", true);

});


const PLAYER_LOCATIONS = {};
function playersBase() {

    world.getAllPlayers().forEach((player) => {

        const pid = player.id;
        const playerLocation = player.location;

        const locations = PLAYER_LOCATIONS[pid];

        // store only X amount of locations?
        if (locations) {
            locations.append(playerLocation);

            // trim amount of stored locations
            if (locations.length > 16) {
                locations.pop(0);
            }

            for (let i = 0; i < locations.length; i++) {

                const loc = locations[i];
                const closeLocs = [loc];
                for (let j = i + 1; j < locations.length; j++) {
                    if (Vec3.distance(loc, locations[j]) <= 128) {
                        closeLocs.append(locations[j]);
                    }
                }

                if (closeLocs.length <= 1) continue;

                const avgLoc = { x: 0, y: 0, z: 0 };
                for (const cLoc of closeLocs) {
                    Vec3.add(avgLoc, cLoc);
                }

                Vec3.multiply(avgLoc, 1 / closeLocs.length);

                locations[i] = avgLoc;

            }

        } else {
            PLAYER_LOCATIONS[pid] = [playerLocation];
        }

    });

}




function getRandomWeighted(arr) {

    // Calculate total weight and generate a random number between 0 and totalWeight
    const totalWeight = arr.reduce((sum, element) => sum + element.weight, 0);
    let random = Math.random() * totalWeight;

    // Loop through the arr and select the one that matches the random weight
    for (let i = 0; i < arr.length; i++) {
        random -= arr[i].weight;
        if (random <= 0) {
            return arr[i];
        }
    }

}




ScriptEventListener.subscribe("mythicus_tw:illusion.noLimit", ({ sourceEntity }) => {
    world.setDynamicProperty("mythicus_tw:illusion.noLimit", true);
});

ScriptEventListener.subscribe("mythicus_tw:illusion.addLimit", ({ sourceEntity }) => {
    world.setDynamicProperty("mythicus_tw:illusion.noLimit", false);
});


ScriptEventListener.subscribe("mythicus_tw:illusion.spawn", ({ sourceEntity }) => {

    if (sourceEntity) {
        createIllusion(sourceEntity);
    }

});


let ILLUSION_SPAWN_TIME = 0;
let RUNNER_SPAWN_TIME = 0;
let CEILING_SPAWN_TIMER = 0;

/**
 * 
 * @param {Entity} entity - Entity.
*/
function createIllusion(entity) {

    const state = entity.getProperty("mythicus_tw:illusion_state");
    switch (state) {
        case 1:
            if (CEILING_SPAWN_TIMER >= system.currentTick || (Math.random() > (0.6 + 0.2 * AGGRESSION_LEVEL))) {
                entity.remove();
            } else if (entity.dimension.getEntities({ location: entity.location, maxDistance: 50, type: "mythicus_tw:watcher" }).length > 0) {
                entity.remove();
            } else {
                entity.setProperty("mythicus_tw:is_spawned", true);
            }
            return;

        case 2:
            entity.setProperty("mythicus_tw:is_spawned", true);
            entity.runCommand("scriptevent mythicus_tw:illusion.crawlerSpawnTimer");
            return;

        case 9:
            entity.setProperty("mythicus_tw:is_spawned", true);

            if ((checkDirectHeight(entity) ?? 3) === 2) {
                entity.triggerEvent("mythicus_tw:behind_crouch");
            }

            return;
    }

    // random despawn chance
    if (Math.random() < (0.5 - 0.1 * AGGRESSION_LEVEL)) {

        // random chance to trigger timer
        if (Math.random() < 0.3) {
            ILLUSION_SPAWN_TIME = system.currentTick + (400 - 50 * AGGRESSION_LEVEL);
        }

        entity.remove();
        return;
    }

    const types = [
        {
            "weight": (system.currentTick > RUNNER_SPAWN_TIME ? AGGRESSION_LEVEL * 3 : 0),
            "type": "mythicus_tw:runner"
        },
        {
            "weight": 2 + AGGRESSION_LEVEL * 1,
            "type": "mythicus_tw:walker"
        },
        {
            "weight": 8,
            "type": "mythicus_tw:crouch"
        },
        //{
        //    "weight": 6,
        //    "type": "mythicus_tw:mirage"
        //},
    ];

    const totalWeight = types.reduce((sum, element) => sum + element.weight, 0);
    types.push(
        {
            "weight": 100 - totalWeight,
            "type": "mythicus_tw:default"
        }
    );

    let type = (entity.getProperty("mythicus_tw:illusion_state") ?? 0) !== 0 ? "none" : getRandomWeighted(types).type;
    //world.sendMessage(`type: ${type}`)

    // check if global illusion timer has passed
    const isTimed = !(type === "mythicus_tw:runner" || type === "mythicus_tw:walker");
    if (isTimed && !(world.getDynamicProperty("mythicus_tw:illusion.noLimit") ?? false)) {
        //if ((system.currentTick < ILLUSION_SPAWN_TIME) && !(AGGRESSION_LEVEL > 1 && world.getTimeOfDay() < 13000)) {
        const player = entity.dimension.getPlayers({ location: entity.location, closest: 1 })?.[0];
        const fear = player ? (player.getDynamicProperty("mythicus_tw:fear.amount") ?? 0) : 0;
        if (system.currentTick < ILLUSION_SPAWN_TIME && (fear < 100 ? true : (Math.random() < 0.3))) {
            entity.remove();
            return;
        }
    }
    
    ILLUSION_SPAWN_TIME = system.currentTick + (400 - 50 * AGGRESSION_LEVEL);

    // change default to crouching if height check not met
    if (type === "mythicus_tw:default" && (checkDirectHeight(entity) ?? 3) === 2) {
        type = "mythicus_tw:crouch";
    }

    // mark entity as spawned
    entity.setProperty("mythicus_tw:is_spawned", true);

    // set rotation
    //entity.teleport(entity.location, { facingLocation: });

    // run event
    if (type && type !== "none") entity.triggerEvent(type);

}




//ScriptEventListener.subscribe("mythicus_tw:illusion.ceilingSpawnCheck", ({ sourceEntity }) => {

//    if (sourceEntity?.isValid() !== true) return;

//    //if (CEILING_SPAWN_TIMER >= system.currentTick || (Math.random() > (0.1 + 0.02 * AGGRESSION_LEVEL))) {
//    if (CEILING_SPAWN_TIMER >= system.currentTick || (Math.random() > (0.6 + 0.2 * AGGRESSION_LEVEL))) {
//        sourceEntity.remove();
//    }

//});

ScriptEventListener.subscribe("mythicus_tw:illusion.ceilingSpawnTimer", ({ sourceEntity }) => {

    // check if another entity has already been placed
    if (sourceEntity?.isValid() === true && CEILING_SPAWN_TIMER >= system.currentTick) {
        sourceEntity.remove();
        return;
    }

    CEILING_SPAWN_TIMER = system.currentTick + 1000;

});

ScriptEventListener.subscribe("mythicus_tw:illusion.ceilingSpawnReset", () => {
    CEILING_SPAWN_TIMER = 0;
});

/**
 * @param {Entity} entity - Entity.
*/
function checkDirectHeight(entity) {
    let height = 0;
    try {
        const block = entity.dimension.getBlockFromRay(entity.location, { x: 0, y: 1, z: 0 }, { includeLiquidBlocks: false, includePassableBlocks: false, maxDistance: 2, excludeTags: ["one_way_collidable"] });
        if (block?.block == undefined) height = 3;
        else height = block.block.y - Math.floor(entity.location.y);
    } finally {
        return height;
    }
}

ScriptEventListener.subscribe("mythicus_tw:watcher.despawn", ({ sourceEntity: entity }) => {

    if (entity?.isValid() !== true) return;
    
    if (entity.getProperty("mythicus_tw:behavior") === 2) {
        entity.runCommand("scriptevent mythicus_tw:chase.stop");
        return;
    }

    entity.remove();

});