import {
    BlockVolume,
    ButtonState,
    Dimension,
    EasingType,
    Entity,
    EntityComponentTypes,
    EntityHealthComponent,
    EntityIsTamedComponent,
    EntityRideableComponent,
    EntityRidingComponent,
    HudElement,
    HudVisibility,
    InputButton,
    Player,
    system,
    world,
} from "@minecraft/server";
import { Bosses } from "../data/bosses";
import { DynamicProperties, SpellDynamicProperties } from "../data/dynamic_properties";
import { Entities, EntitiesArray, EntitiesMap } from "../data/entities";
import { LightSources } from "../data/light_sources";
import { NegativePotionEffects } from "../data/potion_effects";
import { Menus } from "../menus/menus";
import {
    applyArmorSetEffects,
    applyKnockback,
    CameraPresets,
    getArmor,
    getKilledBoss,
    getRandomBetween,
    isBoss,
    isDayTime,
    isEntityLoaded,
    isHoldingLightSource,
    isNightTime,
    isPlayerLogged,
    isUnderground,
    repelEntities,
    sequenceAttack,
} from "../utils/utils";

import { ArmorSet } from "../data/armor_sets";
import { Particles, SpawnTime } from "../data/particles";
import { Sounds, Time } from "../data/sounds";
import "./biome_detector";

world.afterEvents.entityHurt.subscribe((event) => {
    const { damage, damageSource, hurtEntity } = event;

    const damagingEntity = damageSource.damagingEntity;
    if (damagingEntity) {
        if (damagingEntity instanceof Player) {
            /**
             * @type {EntityHealthComponent}
             */
            const health = damagingEntity.getComponent(EntityComponentTypes.Health);

            if (health) {
                const currentHealth = health.currentValue;
                const maxHealth = health.effectiveMax;

                if (
                    hurtEntity &&
                    hurtEntity?.getDynamicProperty(DynamicProperties.vampiric_mark) == true
                ) {
                    const healAmount = Math.round((damage / 100) * 50);

                    damagingEntity.playSound("mob.wanderingtrader.drink_milk");

                    hurtEntity.setDynamicProperty(DynamicProperties.vampiric_mark, false);

                    health.setCurrentValue(Math.min(maxHealth, currentHealth + healAmount));
                }
            }

            if (isBoss(hurtEntity.typeId, Bosses)) {
                hurtEntity.setDynamicProperty("last_hit_by", damagingEntity.id ?? 0);
            }
        }
        if (damagingEntity.typeId == "ns_rh:kid_the_clown") {
            let view = hurtEntity.getViewDirection();
            hurtEntity.applyKnockback(1 * -view.x, 1 * -view.z, 1.5, 0.5);
        }
    }
});

world.afterEvents.effectAdd.subscribe((event) => {
    const { effect, entity } = event;

    if (entity instanceof Player) {
        //REMOVE PLAYER NEGATIVE EFFECTS WHILE GRACE PERIOD SPELL IS UP
        if (entity.getDynamicProperty(DynamicProperties.spell_grace_period) == true) {
            if (NegativePotionEffects.includes(effect.typeId)) {
                entity.removeEffect(effect.typeId);
            }
        }
    }
});

world.afterEvents.entityDie.subscribe((event) => {
    const { damageSource, deadEntity } = event;

    const damagingEntity = damageSource.damagingEntity;

    if (isBoss(deadEntity.typeId, Bosses)) {
        let dimension = deadEntity.dimension;
        const location = deadEntity.location;

        if (!dimension) {
            dimension = world.getAllPlayers()[0].dimension;
        }

        const boss = getKilledBoss(deadEntity.typeId, Bosses);

        const nearbyPlayers = dimension.getPlayers({
            location: location,
            maxDistance: 24,
        });

        for (let player of nearbyPlayers) {
            if (boss.prop) {
                player.setDynamicProperty(boss.prop, true);
            }
        }
    }
});

world.afterEvents.entityHitEntity.subscribe((event) => {
    const { damagingEntity, hitEntity } = event;

    //HANDLE ENTITY KNOCKBACK
    if (EntitiesArray.some((e) => e[1].typeId == damagingEntity.typeId)) {
        const entity = EntitiesArray.find((e) => e[1].typeId == damagingEntity.typeId);

        if (entity) {
            if (entity[1].on_attack.apply_knockback) {
                applyKnockback(
                    damagingEntity,
                    hitEntity,
                    2,
                    entity[1].on_attack.knockback_strength
                );
            }
        }

        switch (damagingEntity.typeId) {
            case Entities.dracula.typeId: {
                damagingEntity.setDynamicProperty("targetId", hitEntity.id);

                break;
            }

            default:
                break;
        }
    }

    //...

    switch (damagingEntity.typeId) {
        case Entities.mrbear.typeId: {
            const attackProp = damagingEntity.getProperty("self:attack");

            if (attackProp && attackProp == "bite") {
                hitEntity.addEffect("poison", 10 * 20, {
                    amplifier: 0,
                    showParticles: true,
                });
            }
            break;
        }
        case Entities.frankenstein.typeId: {
            hitEntity.addEffect("hunger", 10 * 20, {
                amplifier: 0,
                showParticles: true,
            });
            hitEntity.addEffect("slowness", 10 * 20, {
                amplifier: 0,
                showParticles: true,
            });
            break;
        }
        case Entities.black_widow.typeId: {
            hitEntity.addEffect("slowness", 10 * 20, {
                amplifier: 0,
                showParticles: true,
            });
            break;
        }
        case Entities.scp_049.typeId: {
            damagingEntity?.dimension?.spawnParticle("ns_rh:plague_smoke", {
                x: hitEntity.location.x,
                y: hitEntity.location.y + 1.5,
                z: hitEntity.location.z,
            });
            break;
        }
    }
});

world.afterEvents.entityHurt.subscribe((event) => {
    const { damage, damageSource, hurtEntity } = event;

    const damagingEntity = damageSource.damagingEntity;

    if (damagingEntity) {
        switch (damagingEntity.typeId) {
            case Entities.dracula.typeId: {
                /**
                 * @type {EntityHealthComponent}
                 */
                const healthComponent = damagingEntity.getComponent(EntityComponentTypes.Health);

                const currentHealth = healthComponent.currentValue;
                const maxHealth = healthComponent.effectiveMax;

                const stage = damagingEntity.getProperty("self:stage");

                const entityDamage = 12;

                if (damagingEntity.getProperty("self:healing")) {
                    const healAmount = entityDamage * stage * 3;
                    const healPercentage = 0.2;

                    healthComponent.setCurrentValue(
                        Math.min(maxHealth, currentHealth + healAmount * healPercentage)
                    );

                    damagingEntity.setProperty("self:healing", false);
                }
                break;
            }

            default:
                break;
        }
    }
});

world.afterEvents.projectileHitBlock.subscribe((event) => {
    const { dimension, projectile, source, hitVector } = event;

    const blockHit = event.getBlockHit();

    if (projectile.typeId == "ns_rh:black_widow_web") {
        const block = dimension.getBlock({
            x: blockHit.block.location.x + hitVector.x,
            y: blockHit.block.location.y + 2 + hitVector.y,
            z: blockHit.block.location.z + hitVector.z,
        });

        if (block.isAir) {
            dimension.setBlockType(
                {
                    x: blockHit.block.location.x + hitVector.x,
                    y: blockHit.block.location.y + 2 + hitVector.y,
                    z: blockHit.block.location.z + hitVector.z,
                },
                "minecraft:web"
            );
        }
    }
});

world.afterEvents.projectileHitEntity.subscribe((event) => {
    const { dimension, hitVector, projectile, source } = event;

    const entityHit = event.getEntityHit();

    if (projectile.typeId == "ns_rh:black_widow_web") {
        const block = dimension.getBlock({
            x: entityHit.entity.location.x + hitVector.x,
            y: entityHit.entity.location.y + hitVector.y,
            z: entityHit.entity.location.z + hitVector.z,
        });

        if (block.isAir) {
            dimension.setBlockType(
                {
                    x: entityHit.entity.location.x + hitVector.x,
                    y: entityHit.entity.location.y + hitVector.y,
                    z: entityHit.entity.location.z + hitVector.z,
                },
                "minecraft:web"
            );
        }
    }
});

const BOSSES_WITH_CHESTS = {
    "ns_rh:scarecrow": {
        chest: "ns_rh:chest_barn",
    },
    "ns_rh:the_smiler": {
        chest: "ns_rh:chest_smilers",
    },
    "ns_rh:mrbear": {
        chest: "ns_rh:chest_mrbear",
    },
    "ns_rh:nightmare": {
        chest: "ns_rh:chest_lich",
    },
    "ns_rh:kid_the_clown": {
        chest: "ns_rh:chest_kid_the_clown",
    },
    "ns_rh:dracula": {
        chest: "ns_rh:chest_dracula",
    },
    "ns_rh:scp_049": {
        chest: "ns_rh:chest_doctor",
    },
    "ns_rh:madre": {
        chest: "ns_rh:chest_doll_house",
    },
};

world.afterEvents.dataDrivenEntityTrigger.subscribe((event) => {
    const { entity, eventId } = event;

    //KEEP ENTITY IN DEATH LOCATION
    if (eventId == "self:dying") {
        if (EntitiesArray.some((e) => e[1].typeId == entity.typeId)) {
            entity.setDynamicProperty("dieLocation", entity.location);
        }

        if (isBoss(entity.typeId, Bosses)) {
            let dimension = entity.dimension;
            const location = entity.location;

            if (!dimension) {
                dimension = world.getAllPlayers()[0].dimension;
            }

            const boss = getKilledBoss(entity.typeId, Bosses);

            const nearbyPlayers = dimension.getPlayers({
                location: location,
                maxDistance: 24,
            });

            for (let player of nearbyPlayers) {
                if (boss && boss.prop) {
                    player.setDynamicProperty(boss.prop, true);
                }
            }
        }
    }

    if (eventId == "self:die") {
        if (BOSSES_WITH_CHESTS[entity.typeId]) {
            const chestType = BOSSES_WITH_CHESTS[entity.typeId].chest;

            entity.dimension.spawnEntity(chestType, entity.location);
        }
    }

    if (entity.typeId == Entities.the_shadow.typeId) {
        switch (eventId) {
            case "self:become_visible": {
                const nearbyPlayers = entity.dimension.getPlayers({
                    location: entity.location,
                    maxDistance: 6,
                });

                for (let player of nearbyPlayers) {
                    const playerConfigurations = getPlayerConfigurations(player);

                    //JUMPSCARE
                    if (
                        playerConfigurations.jumpscare_cam == true &&
                        player.jumpscareCooldown == 0
                    ) {
                        player.jumpscareCooldown = 10 * 20;

                        const cameraDistance = 2;

                        if (player.cameraAction == false) {
                            player.cameraAction = true;
                            player.camera.setCamera(CameraPresets.free, {
                                easeOptions: {
                                    easeTime: 0.0,
                                    easeType: EasingType.Linear,
                                },
                                facingEntity: entity,
                                location: {
                                    x:
                                        entity.location.x +
                                        entity.getViewDirection().x * cameraDistance,
                                    y: entity.getHeadLocation().y,
                                    z:
                                        entity.location.z +
                                        entity.getViewDirection().z * cameraDistance,
                                },
                            });

                            system.runTimeout(() => {
                                if (isPlayerLogged(player)) {
                                    player.cameraAction = false;
                                    player?.camera?.clear();
                                }
                            }, 2 * 20);
                        }
                    }
                }
                break;
            }
        }
    }
    if (entity.typeId == Entities.scp_173.typeId) {
        switch (eventId) {
            case "self:jumpscare": {
                const nearbyPlayers = entity.dimension.getPlayers({
                    location: entity.location,
                    maxDistance: 6,
                });

                for (let player of nearbyPlayers) {
                    const playerConfigurations = getPlayerConfigurations(player);

                    if (playerConfigurations.jumpscare_cam == true) {
                        const cameraDistance = 2;

                        if (player.cameraAction == false) {
                            player.cameraAction = true;
                            player.camera.setCamera(CameraPresets.free, {
                                easeOptions: {
                                    easeTime: 0.0,
                                    easeType: EasingType.Linear,
                                },
                                facingEntity: entity,
                                location: {
                                    x:
                                        entity.location.x +
                                        entity.getViewDirection().x * cameraDistance,
                                    y: entity.getHeadLocation().y,
                                    z:
                                        entity.location.z +
                                        entity.getViewDirection().z * cameraDistance,
                                },
                            });

                            system.runTimeout(() => {
                                if (isPlayerLogged(player)) {
                                    player.cameraAction = false;
                                    player?.camera?.clear();
                                }
                            }, 2 * 20);
                        }
                    }
                }
                break;
            }
        }
    }
    if (entity.typeId == Entities.the_pecker.typeId) {
        switch (eventId) {
            case "self:immobile": {
                const nearbyPlayer = entity.dimension.getPlayers({
                    location: entity.location,
                    maxDistance: 3,
                })[0];

                if (nearbyPlayer) {
                    system.runTimeout(() => {
                        if (isEntityLoaded(entity)) {
                            /**
                             * @type {EntityRideableComponent}
                             */
                            const rideableComponent = entity.getComponent(
                                EntityComponentTypes.Rideable
                            );

                            if (rideableComponent) {
                                const hasRider = rideableComponent.getRiders().length > 0;

                                if (!hasRider) {
                                    /**
                                     * @type {EntityRidingComponent}
                                     */
                                    const playerRidingComponent = nearbyPlayer.getComponent(
                                        EntityComponentTypes.Riding
                                    );

                                    const playerIsRiding = playerRidingComponent?.entityRidingOn;

                                    if (playerIsRiding) {
                                        return;
                                    }

                                    rideableComponent.addRider(nearbyPlayer);

                                    nearbyPlayer.inputPermissions.movementEnabled = false;
                                    nearbyPlayer.onScreenDisplay.setHudVisibility(
                                        HudVisibility.Hide,
                                        [HudElement.HorseHealth, HudElement.TouchControls]
                                    );

                                    system.runTimeout(() => {
                                        if (isPlayerLogged(nearbyPlayer)) {
                                            nearbyPlayer.inputPermissions.movementEnabled = true;
                                            nearbyPlayer.onScreenDisplay.setHudVisibility(
                                                HudVisibility.Reset
                                            );
                                        }
                                    }, 3 * 20);

                                    //...
                                }
                            }
                        }
                    }, 0.1 * 20);
                } else {
                    entity.triggerEvent("self:mobile");
                }
                break;
            }
        }
    }
    if (entity.typeId == Entities.mrbear.typeId) {
        switch (eventId) {
            case "self:attack_sequence": {
                sequenceAttack(entity, [
                    { time: 1.0, damage: 12 },
                    { time: 1.5, damage: 12 },
                ]);
                break;
            }
        }
    }
    if (entity.typeId == Entities.nightmare.typeId) {
        switch (eventId) {
            case "self:attack2": {
                sequenceAttack(entity, [
                    { time: 0.5, damage: 20 },
                    { time: 1.2, damage: 20 },
                    { time: 1.5, damage: 20 },
                ]);
                break;
            }
        }
    }

    if (entity.typeId == Entities.kid_the_clown.typeId) {
        switch (eventId) {
            case "self:attack2": {
                sequenceAttack(entity, [
                    { time: 1, damage: 20 },
                    { time: 1.5, damage: 20 },
                    { time: 2.5, damage: 20 },
                ]);
                break;
            }
        }
    }
    if (entity.typeId == Entities.dracula.typeId) {
        switch (eventId) {
            case "self:sword_attack": {
                sequenceAttack(entity, [
                    { time: 0.5, damage: 16 },
                    { time: 1.0, damage: 16 },
                    { time: 1.5, damage: 16 },
                ]);
                break;
            }
            case "self:teleport_to_target": {
                const target = entity.dimension
                    .getEntities()
                    .find((e) => e.id == entity.getDynamicProperty("targetId"));

                if (target) {
                    system.runTimeout(() => {
                        entity?.teleport(target?.location);
                    }, 2.7 * 20);
                }
                break;
            }
        }
    }
    if (entity.typeId == "ns_rh:portal") {
        switch (eventId) {
            case "self:teleport_player": {
                // entity.remove();

                system.runTimeout(() => {
                    //PLAYER RETURNS FROM THE DUNGEONS

                    if (!isEntityLoaded(entity)) return;

                    const nearbyPlayers = entity?.dimension?.getPlayers({
                        location: entity.location,
                        maxDistance: 4,
                    });

                    const closestPortal = entity?.dimension?.getEntities({
                        location: entity.location,
                        type: "ns_rh:portal",
                        minDistance: 1,
                        closest: 1,
                    })[0];

                    const topmostBlock = entity?.dimension?.getTopmostBlock(
                        { x: entity.location.x, z: entity.location.z },
                        entity.location.y > 64 ? entity.location.y - 100 : entity.location.y
                    ).location.y;

                    if (entity.getProperty("self:back_from") == "coliseum") {
                        if (
                            closestPortal &&
                            closestPortal != entity &&
                            closestPortal.getDynamicProperty("arena_location") != undefined
                        ) {
                            if (closestPortal?.getProperty("self:teleport_to") == "coliseum") {
                                const arena_location =
                                    closestPortal.getDynamicProperty("arena_location");

                                for (let player of nearbyPlayers) {
                                    player.teleport({
                                        x: closestPortal.location.x,
                                        y: closestPortal.location.y + 2,
                                        z: closestPortal.location.z,
                                    });

                                    system.runTimeout(() => {
                                        world.structureManager.place(
                                            "ns_rh:empty_structure",
                                            entity.dimension,
                                            arena_location
                                        );

                                        entity.remove();

                                        // player.removeTag("can_teleport");

                                        closestPortal.remove();
                                    }, 1 * 20);
                                }
                            }
                        }
                    }

                    if (entity.getProperty("self:back_from") == "backrooms") {
                        if (closestPortal && closestPortal != entity) {
                            if (closestPortal?.getProperty("self:teleport_to") == "backrooms") {
                                for (let player of nearbyPlayers) {
                                    player.teleport({
                                        x: closestPortal.location.x,
                                        y: closestPortal.location.y + 2,
                                        z: closestPortal.location.z,
                                    });

                                    player.removeTag("can_teleport");

                                    system.runTimeout(() => {
                                        entity.remove();
                                        closestPortal.remove();
                                    }, 1 * 20);
                                }
                            }
                        }
                    }
                    //...

                    if (
                        entity.getProperty("self:teleport_to") == "backrooms" &&
                        entity.getProperty("self:disabled") == false
                    ) {
                        const backroomsLocation = {
                            x: 100000,
                            y: -40,
                            z: 100000,
                        };

                        //generate the structure
                        const structure = world.structureManager.place(
                            "ns_rh:backrooms",
                            entity.dimension,
                            backroomsLocation
                        );
                        //...

                        for (let player of nearbyPlayers) {
                            player.teleport({
                                x: backroomsLocation.x + 3,
                                y: backroomsLocation.y + 4,
                                z: backroomsLocation.z + 27,
                            });

                            player.removeTag("can_teleport");
                        }

                        // entity.setProperty("self:disabled", true);
                    }
                }, 0.5 * 20);

                break;
            }
        }
    }
});

world.afterEvents.playerSpawn.subscribe((event) => {
    const { player } = event;

    player.inputPermissions.cameraEnabled = true;
    player.inputPermissions.movementEnabled = true;

    const playerWarned = player.getDynamicProperty("Warned");

    if (playerWarned == undefined) {
        player.setDynamicProperty("Warned", false);
    }

    player.dimension.runCommand(
        `tickingarea add ${100000} ${4} ${100000} ${100031} ${0} ${100031} backrooms`
    );
});

world.afterEvents.entitySpawn.subscribe((event) => {
    const { cause, entity } = event;

    switch (entity.typeId) {
        case Entities.the_smiler.typeId: {
            break;
        }
    }
});

world.afterEvents.entityHealthChanged.subscribe((event) => {
    const { oldValue, newValue, entity } = event;

    //console.warn(oldValue + " " + newValue + " " + entity.getProperty("self:health_lost"));
    //console.warn(" " + entity.getProperty("self:is_copy"));
});

world.afterEvents.entityDie.subscribe((event) => {
    try {
        const deadEntity = event.deadEntity;
        const { location, dimension } = deadEntity;

        switch (deadEntity.typeId) {
            case "ns_rh:nightmare": {
                const nearbyPlayer = dimension.getPlayers({
                    location: location,
                    maxDistance: 16,
                })[0];

                if (!nearbyPlayer) {
                    spawnPortal(
                        dimension,
                        {
                            x: location.x + getRandomBetween(-2, 2),
                            y: location.y + 1,
                            z: location.z + getRandomBetween(-2, 2),
                        },
                        { property_name: "self:back_from", value: "coliseum" }
                    );
                } else {
                    spawnPortal(
                        dimension,
                        {
                            x: nearbyPlayer.location.x,
                            y: nearbyPlayer.location.y + 1,
                            z: nearbyPlayer.location.z,
                        },
                        { property_name: "self:back_from", value: "coliseum" }
                    );
                }

                break;
            }

            case "ns_rh:the_smiler": {
                const nearbyPlayer = dimension.getPlayers({
                    location: location,
                    maxDistance: 16,
                })[0];

                if (!nearbyPlayer) {
                    spawnPortal(
                        dimension,
                        {
                            x: location.x + getRandomBetween(-2, 2),
                            y: location.y + 1,
                            z: location.z + getRandomBetween(-2, 2),
                        },
                        { property_name: "self:back_from", value: "backrooms" }
                    );
                } else {
                    spawnPortal(
                        dimension,
                        {
                            x: nearbyPlayer.location.x,
                            y: nearbyPlayer.location.y + 1,
                            z: nearbyPlayer.location.z,
                        },
                        { property_name: "self:back_from", value: "backrooms" }
                    );
                }
                break;
            }

            default:
                break;
        }
    } catch (error) {}

    /*  const {
        deadEntity: { location, dimension },
    } = event; */
});

const runLoop = true;

const MAIN_LOOP = system.runInterval(() => {
    const players = world.getAllPlayers();
    const entities = world.getDimension("overworld").getEntities();
    for (let player of players) {
        if (runLoop) {
            //SPELL EFFECTS HENDLER
            handleSpellEffects(player);

            //DISPLAY SPELLS REMAINING TIME IN ACTIONBAR
            displaySpellsRemainingTime(player);

            //DYNAMIC LIGHTING
            dynamicLighting(player);

            //ARMOR EFFECTS
            applyArmorSetEffects(player, getArmor(player));

            //APPLY MOB EFFECTS
            applyMobEffects(player);

            //...
            disableEntityDieMovement();

            //KEEP MOBS DISTANCE FROM PLAYER COLLISION BOX TO AVOID BUGS
            handleMobDistance(player, 1.2);

            //DETECT PLAYER BIOME
            detectPlayerBiome(player);

            //PLAY BIOME SOUNDS
            playBiomeSounds(player, system.currentTick);

            //SPAWN BIOME PARTICLES
            spawnBiomeParticles(player, system.currentTick);

            //WARN PLAYER
            warnPlayer(player);

            //SHOW TELEPORT COOLDOWN
            showTeleportCooldown(player);

            //HANDLE BOSS FIGHT MUSIC
            playBossFightMusic(player);

            //HANDLE JUMPSCARE COOLDOWN
            handleJumpscareCooldown(player);

            //HANDLE PLAYER FLY
            handlePlayerFly(player);
        }
    }
    for (let entity of entities) {
        if (runLoop) {
            //REMOVE SUMMONED ENTITIES
            removeSummonedEntities(entity);
        }
    }
});

const spellRejectionTime = 8 * 20;
const spellGracePeriodTime = 60 * 20;

Player.prototype.spellRejectionDuration = spellRejectionTime;
Player.prototype.spellGracePeriodDuration = spellGracePeriodTime;
Player.prototype.cameraAction = false;
Player.prototype.menuOpen = false;
Player.prototype.ambientSoundCooldown = 0;
Player.prototype.portalTeleportCooldown = 60;
Player.prototype.bossFightMusicPlaying = false;
Player.prototype.bossFightMusicDuration = 0;
Player.prototype.currentBossFightMusic = "";
Player.prototype.jumpscareCooldown = 0;

Entity.prototype.lifetime = 0;

/**
 *
 * @param {Player} player
 */
function warnPlayer(player) {
    const warnInterval = 1;
    if (system.currentTick % (warnInterval * 20) === 0) {
        const playerWarnProperty = player.getDynamicProperty("Warned");

        if (!playerWarnProperty || playerWarnProperty == false) {
            Menus.warning_menu(player);
        }
    }
}

/**
 *
 * @param {Player} player
 */
function showTeleportCooldown(player) {
    const cooldown = 60;

    const currentCooldown = player.portalTeleportCooldown;

    const portal = player.dimension.getEntities({
        location: player.location,
        minDistance: 0,
        maxDistance: 2,
        closest: 1,
        type: "ns_rh:portal",
    })[0];

    if (portal) {
        if (currentCooldown > 0) {
            player.portalTeleportCooldown = Math.max(0, currentCooldown - 1);

            player.onScreenDisplay.setActionBar(
                `Teleporting in ${Math.round(player.portalTeleportCooldown / 20)}...`
            );
        }
        if (currentCooldown == 0) {
            system.runTimeout(() => {
                player.addTag("can_teleport");
            }, 1.5 * 20);
            system.runTimeout(() => {
                player.portalTeleportCooldown = cooldown;
            }, 2.5 * 20);

            player.onScreenDisplay.setActionBar(`Teleporting...`);
        }
    } else {
        player.removeTag("can_teleport");

        player.portalTeleportCooldown = cooldown;
    }
}

/**
 *
 * @param {Player} player
 */
function playBossFightMusic(player) {
    const { dimension, location } = player;

    if (player.bossFightMusicDuration > 0) {
        player.bossFightMusicDuration--;
    }

    // console.warn(player.bossFightMusicDuration);

    const playRange = 64;

    const nearbyBoss = dimension
        .getEntities({
            location,
            minDistance: 1,
            maxDistance: playRange,
        })
        .find((e) => Bosses.has(e.typeId));

    if (nearbyBoss) {
        /**
         * @type {boolean}
         */
        const isRessurectedBoss = nearbyBoss.getProperty("self:ressurected");

        if (!isRessurectedBoss) {
            const boss = Bosses.get(nearbyBoss.typeId);

            const { sound_track } = boss;

            if (sound_track) {
                const music_id = sound_track.music_id;
                const duration = sound_track.duration;

                if (player.bossFightMusicDuration == 0) {
                    player.bossFightMusicDuration = duration * 20;
                    player.currentBossFightMusic = music_id;

                    player.runCommand("stopsound @s");

                    player.bossFightMusicPlaying = true;

                    player.playSound(music_id, {
                        location: player.location,
                        pitch: 1.0,
                        volume: 1.0,
                    });
                }
            }
        } else {
            if (player.bossFightMusicDuration > 0) {
                player.bossFightMusicDuration = 0;

                player.runCommand("stopsound @s");
            }
        }
    }
}

/**
 *
 * @param {Entity} entity
 */
function removeSummonedEntities(entity) {
    entity.lifetime++;

    const isNotVanilla = EntitiesArray.find((obj) => obj[1].typeId == entity.typeId);

    if (isNotVanilla) {
        //  console.warn(entity.lifetime);
        if (entity.lifetime >= 40 * 20 && entity.hasTag("summoned")) {
            entity.remove();
        }
    }
}

/**
 *
 * @param {Player} player
 */
function handleSpellEffects(player) {
    //REJECTION SPELL

    if (player.getDynamicProperty(SpellDynamicProperties.spell_rejection) == true) {
        player.spellRejectionDuration--;

        if (player.spellRejectionDuration > 0) {
            repelEntities(player, 5, 1, 0.4);
        } else if (player.spellRejectionDuration <= 0) {
            player.setDynamicProperty(SpellDynamicProperties.spell_rejection, false);
            player.spellRejectionDuration = spellRejectionTime;
        }
    }

    //GRACE PERIOD SPELL

    if (player.getDynamicProperty(SpellDynamicProperties.spell_grace_period) == true) {
        player.spellGracePeriodDuration--;

        if (player.spellGracePeriodDuration <= 0) {
            player.setDynamicProperty(SpellDynamicProperties.spell_grace_period, false);
            player.spellGracePeriodDuration = spellGracePeriodTime;
        }
    }
}

/**
 *
 * @param {Player} player
 */
function applyMobEffects(player) {
    const configurations = getPlayerConfigurations(player);

    const entities = player.dimension.getEntities();

    const prefix = "ns_rh:";

    for (let entity of entities) {
        const mob = EntitiesMap.get(entity.typeId.replace(prefix, ""));

        /**
         * @type {EntityIsTamedComponent}
         */
        const isTamed = entity.getComponent(EntityComponentTypes.IsTamed);

        if (mob && !isTamed) {
            const nearbyEntities = entity.dimension.getEntities({
                location: entity.location,
                maxDistance: mob.effect_range,
            });

            nearbyEntities.forEach((e) => {
                if (e != entity) {
                    const effects = mob?.area_effects;

                    if (e instanceof Player) {
                        if (effects && effects.length > 0) {
                            for (let effect of effects) {
                                if (
                                    effect.name == "nausea" &&
                                    configurations.enable_mobs_cause_nausea == false
                                ) {
                                    //...
                                } else {
                                    e.addEffect(effect.name, effect.duration, {
                                        amplifier: effect.amplifier,
                                        showParticles: true,
                                    });
                                }
                            }
                        }
                    } else {
                        if (effects && effects.length > 0) {
                            for (let effect of effects) {
                                e.addEffect(effect.name, effect.duration, {
                                    amplifier: effect.amplifier,
                                    showParticles: true,
                                });
                            }
                        }
                    }
                }
            });
        }
    }
}

/**
 *
 * @param {Player} player
 */
function displaySpellsRemainingTime(player) {
    const spells = [
        {
            spell_name: "Rejection",
            spell_prop: SpellDynamicProperties.spell_rejection,
            timer: player.spellRejectionDuration,
        },
        {
            spell_name: "Grace Period",
            spell_prop: SpellDynamicProperties.spell_grace_period,
            timer: player.spellGracePeriodDuration,
        },
    ];

    const activeSpells = [];

    spells.forEach((spell) => {
        if (player.getDynamicProperty(spell.spell_prop) == true) {
            const remainingTime = Math.round(spell.timer / 20);
            const stringFormat = `${spell.spell_name}: ${remainingTime}s`;
            activeSpells.push(stringFormat);
        }
    });

    const actionBarMessage = `${activeSpells.join("\n")}`;

    if (actionBarMessage.length > 0) {
        player.onScreenDisplay.setActionBar(actionBarMessage);
    }
}

/**
 *
 * @param {Player} player
 * @param {Number} lightLevel
 */
function dynamicLighting(player, lightLevel = 14) {
    const configurations = getPlayerConfigurations(player);
    try {
        const clearLocations = Array.from(
            new BlockVolume(
                {
                    x: player.location.x - 2,
                    y: player.location.y - 2,
                    z: player.location.z - 2,
                },
                {
                    x: player.location.x + 2,
                    y: player.location.y + 2,
                    z: player.location.z + 2,
                }
            ).getBlockLocationIterator()
        );

        const lightBlock = `minecraft:light_block_${lightLevel}`;

        if (configurations.dynamic_light) {
            if (isHoldingLightSource(player, LightSources)) {
                const fillLocations = player.dimension.fillBlocks(
                    new BlockVolume(
                        {
                            x: player.location.x,
                            y: player.location.y + 1,
                            z: player.location.z,
                        },
                        {
                            x: player.location.x,
                            y: player.location.y + 1,
                            z: player.location.z,
                        }
                    ),
                    lightBlock,
                    {
                        blockFilter: {
                            includeTypes: ["minecraft:air"],
                        },
                    }
                );

                clearLocations.forEach((location) => {
                    const playerLocation = JSON.stringify(
                        player.dimension.getBlock({
                            x: player.location.x,
                            y: player.location.y + 1,
                            z: player.location.z,
                        }).location
                    );
                    const blockLocation = JSON.stringify(location);

                    if (playerLocation != blockLocation) {
                        const block = player.dimension.getBlock(location);

                        if (block && block.typeId == lightBlock) {
                            block.setType("minecraft:air");
                        }
                    }
                });
            } else {
                clearLocations.forEach((location) => {
                    const playerLocation = JSON.stringify(
                        player.dimension.getBlock({
                            x: player.location.x,
                            y: player.location.y + 1,
                            z: player.location.z,
                        })?.location
                    );
                    const blockLocation = JSON.stringify(location);

                    if (playerLocation != blockLocation) {
                        const block = player.dimension.getBlock(location);

                        if (block && block.typeId == lightBlock) {
                            block.setType("minecraft:air");
                        }
                    }
                });
            }
        } else {
            clearLocations.forEach((location) => {
                const playerLocation = JSON.stringify(
                    player.dimension.getBlock({
                        x: player.location.x,
                        y: player.location.y + 1,
                        z: player.location.z,
                    })?.location
                );
                const blockLocation = JSON.stringify(location);

                if (playerLocation != blockLocation) {
                    const block = player.dimension.getBlock(location);

                    if (block && block.typeId == lightBlock) {
                        block.setType("minecraft:air");
                    }
                }
            });
        }
    } catch (error) {}
}

function disableEntityDieMovement() {
    const Dimensions = ["overworld", "nether", "the_end"];

    for (let dimension of Dimensions) {
        const currentDimension = world.getDimension(dimension);

        const entities = currentDimension.getEntities({ excludeFamilies: ["player"] });

        for (let entity of entities) {
            if (EntitiesArray.some((e) => e[1].typeId == entity.typeId)) {
                const dieLocation = entity.getDynamicProperty("dieLocation");

                if (dieLocation) {
                    if (Object.values(entity.location) != Object.values(dieLocation)) {
                        entity.clearVelocity();
                        entity.teleport(dieLocation, { keepVelocity: false });
                    }
                }
            }
        }
    }
}

/**
 *
 * @param {Player} player
 * @param {number} distance
 */
function handleMobDistance(player, distance) {
    const entities = player.dimension.getEntities({
        excludeFamilies: ["player"],
        location: player.location,
        maxDistance: distance,
    });

    /**
     * @type {EntityRidingComponent}
     */
    const ridingComponent = player.getComponent(EntityComponentTypes.Riding);

    const horizontalStrength = 0.2;

    for (let entity of entities) {
        if (EntitiesArray.some((e) => e[1].typeId == entity.typeId)) {
            if (ridingComponent?.isValid()) {
                const isRiding = ridingComponent.entityRidingOn;

                if (isRiding != entity) {
                    entity.applyImpulse({
                        x:
                            entity.location.x < player.location.x
                                ? -horizontalStrength
                                : horizontalStrength,
                        y: 0.0,
                        z:
                            entity.location.z < player.location.z
                                ? -horizontalStrength
                                : horizontalStrength,
                    });
                }
            } else {
                entity.applyImpulse({
                    x:
                        entity.location.x < player.location.x
                            ? -horizontalStrength
                            : horizontalStrength,
                    y: 0.0,
                    z:
                        entity.location.z < player.location.z
                            ? -horizontalStrength
                            : horizontalStrength,
                });
            }
        }
    }
}

let biomeDetectTimer = 10;
/**
 *
 * @param {Player} player
 */
function detectPlayerBiome(player) {
    //console.warn(biomeDetectTimer);

    if (biomeDetectTimer > 0) {
        biomeDetectTimer--;
    }
    if (biomeDetectTimer == 0) {
        biomeDetectTimer = 10;

        const direction = player.getViewDirection();
        const hasNearbyDetectors = player.dimension.getEntities({
            location: {
                x: player.location.x,
                y: player.location.y,
                z: player.location.z,
            },
            type: "ns_rh:biome_detector2",
            maxDistance: 10,
        });

        if (hasNearbyDetectors.length > 0) return;

        try {
            const biomeDetector = player.dimension.spawnEntity("ns_rh:biome_detector2", {
                x: player.location.x - direction.x * 1.5,
                y: player.location.y + 8,
                z: player.location.z - direction.z * 1.5,
            });

            biomeDetector.setDynamicProperty("owner", player.id);
        } catch (error) {}
    }
}

/**
 *
 * @param {Player} player
 */
export function getPlayerBiome(player) {
    /**
     * @type {string|undefined}
     */
    const biomeTag = player.getDynamicProperty("in_biome");

    if (biomeTag) {
        const biome = biomeTag.replace("biome_tag:", "");

        const isColdBiome = biome.includes("ice") || biome.includes("cold");
        const underground = isUnderground(player);

        if (biome.includes("plains") && !isColdBiome) {
            return underground ? "caves" : "plains";
        } else if (biome.includes("swamp") && !isColdBiome) {
            return underground ? "caves" : "swamp";
        } else if (biome.includes("cherry") && !isColdBiome) {
            return underground ? "caves" : "cherry";
        } else if (biome.includes("forest") && !isColdBiome) {
            return underground ? "caves" : "forest";
        } else if (biome.includes("desert") || (biome.includes("mesa") && !isColdBiome)) {
            return underground ? "caves" : "desert";
        } else if (biome.includes("hills") && !isColdBiome) {
            return underground ? "caves" : "hills";
        } else if (biome.includes("jungle") && !isColdBiome) {
            return underground ? "caves" : "jungle";
        } else if (biome.includes("savanna") && !isColdBiome) {
            return underground ? "caves" : "savanna";
        } else if (isColdBiome) {
            return underground ? "caves" : "cold";
        } else {
            return underground ? "caves" : "default";
        }
    }
}

const ConfigBase = {
    jumpscare_cam: true,
    dynamic_light: true,
    enable_fog: true,
    enable_mobs_cause_nausea: false,
    enable_biome_visual_effects: true,
    enable_biome_sound_effects: true,
};

/**
 *
 * @param {Player} player
 * @param {ConfigBase} configs
 */
export function updatePlayerConfigurations(player, configs) {
    const configurationsID = "ns_rh_configurations";

    const currentConfig = getPlayerConfigurations(player);

    const newConfig = {
        ...currentConfig,
        ...configs,
    };

    player.setDynamicProperty(configurationsID, JSON.stringify(newConfig));

    // console.warn(JSON.stringify(newConfig));
}

/**
 *
 * @param {Player} player
 * @param {string} configsID
 * @returns {ConfigBase}
 */
export function getPlayerConfigurations(player) {
    const configurationsID = "ns_rh_configurations";

    if (player.getDynamicProperty(configurationsID) == undefined) {
        player.setDynamicProperty(configurationsID, JSON.stringify(ConfigBase));
    }

    const configurations = player.getDynamicProperty(configurationsID);

    return JSON.parse(configurations);
}

/**
 *
 * @param {Player} player
 * @param {number} currentTick
 */
function playBiomeSounds(player, currentTick) {
    player.ambientSoundCooldown = Math.max(0, player.ambientSoundCooldown - 1);

    const configurations = getPlayerConfigurations(player);

    if (configurations.enable_biome_sound_effects == false) return;
    // console.warn(`Remaining Time: ${player.ambientSoundCooldown / 20} Seconds`);

    const biome = getPlayerBiome(player);

    if (biome) {
        const sounds = Sounds.get(biome);

        if (sounds) {
            for (let sound of sounds.sounds) {
                const { volume } = sound.options;
                if (sound.loop) {
                    if (currentTick % (sound.interval * 20) === 0) {
                        player.stopMusic();
                        player.playMusic(sound.name, {
                            loop: false,
                            volume,
                            fade: 0.5,
                        });
                    }
                } else {
                    if (player.ambientSoundCooldown == 0) {
                        const playSound = getRandomBetween(0, 100) <= sound.probability;
                        if (playSound && currentTick % (sound.interval * 20) === 0) {
                            if (sound.play_time == Time.Any) {
                                player.playSound(sound.name, {
                                    location: player.location,
                                    volume,
                                });

                                player.ambientSoundCooldown = getRandomBetween(20, 40) * 20;
                            } else if (sound.play_time == Time.Night && isNightTime()) {
                                player.playSound(sound.name, {
                                    location: player.location,
                                    volume,
                                });

                                player.ambientSoundCooldown = getRandomBetween(20, 40) * 20;
                            } else if (sound.play_time == Time.Day && isDayTime()) {
                                player.playSound(sound.name, {
                                    location: player.location,
                                    volume,
                                });

                                player.ambientSoundCooldown = getRandomBetween(20, 40) * 20;
                            }
                        }
                    }
                }
            }
        }
    }
}

/**
 *
 * @param {Player} player
 * @param {number} currentTick
 */
function spawnBiomeParticles(player, currentTick) {
    const configurations = getPlayerConfigurations(player);

    if (configurations.enable_biome_visual_effects == false) return;

    const biome = getPlayerBiome(player);

    const underground = isUnderground(player);

    if (biome) {
        const particles = Particles.get(biome);

        if (particles) {
            for (let particle of particles.particles) {
                const { offset, interval, name, probability, isNebline } = particle;
                const randomX = getRandomBetween(-offset.x, offset.x);
                const randomZ = getRandomBetween(-offset.z, offset.z);

                try {
                    if (isNebline) {
                        const topmostLocation = player.dimension.getTopmostBlock({
                            x: player.location.x + randomX,
                            z: player.location.z + randomZ,
                        }).location.y;

                        const particleSpawnLocation = {
                            x: player.location.x + randomX,
                            y: underground ? player.location.y : topmostLocation + offset.y,
                            z: player.location.z + randomZ,
                        };
                        if (currentTick % (interval * 20) === 0) {
                            if (particle.spawn_time == SpawnTime.Any) {
                                player.dimension.spawnParticle(name, particleSpawnLocation);
                            } else if (particle.spawn_time == SpawnTime.Night && isNightTime()) {
                                player.dimension.spawnParticle(name, particleSpawnLocation);
                            } else if (particle.spawn_time == SpawnTime.Day && isDayTime()) {
                                player.dimension.spawnParticle(name, particleSpawnLocation);
                            }
                        }
                    } else {
                        const topmostLocation = player.dimension.getTopmostBlock({
                            x: player.location.x + randomX,
                            z: player.location.z + randomZ,
                        }).location.y;

                        const particleSpawnLocation = {
                            x: player.location.x + randomX,
                            y: underground ? player.location.y : topmostLocation + offset.y,
                            z: player.location.z + randomZ,
                        };
                        if (currentTick % (interval * 20) === 0) {
                            if (particle.spawn_time == SpawnTime.Any) {
                                player.dimension.spawnParticle(name, particleSpawnLocation);
                            } else if (particle.spawn_time == SpawnTime.Night && isNightTime()) {
                                player.dimension.spawnParticle(name, particleSpawnLocation);
                            } else if (particle.spawn_time == SpawnTime.Day && isDayTime()) {
                                player.dimension.spawnParticle(name, particleSpawnLocation);
                            }
                        }
                    }
                } catch (e) {}
            }
        }
    }
}

/**
 *
 * @param {Dimension} dimension
 * @param {Location} location
 * @param {{property_name:string, value:string|number|boolean}} portalProp
 */
function spawnPortal(dimension, location, portalProp) {
    const portal = dimension.spawnEntity("ns_rh:portal", location);

    portal.setProperty(portalProp.property_name, portalProp.value);
}

/**
 *
 * @param {Player} player
 */
function handleJumpscareCooldown(player) {
    // console.warn(player.jumpscareCooldown);
    if (player.jumpscareCooldown > 0) {
        player.jumpscareCooldown = Math.max(0, player.jumpscareCooldown - 1);
    }
}

/**
 *
 * @param {Player} player
 */
function handlePlayerFly(player) {
    const equippedArmorParts = getArmor(player);

    const equippedArmor = Array.from(ArmorSet.entries()).find((obj) =>
        obj[1].parts.every((part) => equippedArmorParts.includes(part))
    );

    if (!equippedArmor) return;

    const equippedArmorName = equippedArmor[0];

    if (equippedArmorName == "lich") {
        const playerMovementVector = player.inputInfo.getMovementVector();

        const playerRotation = player.getRotation();

        const playerViewDirection = player.getViewDirection();

        const playerJumpButtonState = player.inputInfo.getButtonState(InputButton.Jump);

        const playerHeight = player.location.y;

        const topmostBlock =
            player.dimension.getTopmostBlock(
                {
                    x: player.location.x,
                    z: player.location.z,
                },
                playerHeight
            ).location.y + 1;

        const canFly = playerHeight >= topmostBlock + 1.0;

        // console.warn(playerHeight, topmostBlock, canFly);

        if (player.isOnGround) return;

        if (canFly && playerJumpButtonState == ButtonState.Pressed) {
            const applyHorizontalSpeed =
                playerMovementVector.y >= 1 || playerMovementVector.y <= -1;

            const movingFoward = playerMovementVector.y >= 1;
            const movingBackward = playerMovementVector.y <= -1;

            const horizontalSpeed = 1.0;
            const verticalSpeed = 0.5;

            if (movingFoward) {
                player.applyKnockback(
                    applyHorizontalSpeed ? playerViewDirection.x : 0,
                    applyHorizontalSpeed ? playerViewDirection.z : 0,
                    applyHorizontalSpeed ? horizontalSpeed : 0,
                    applyHorizontalSpeed ? verticalSpeed * playerViewDirection.y : verticalSpeed
                );
            } else {
                player.applyKnockback(
                    applyHorizontalSpeed ? -playerViewDirection.x : 0,
                    applyHorizontalSpeed ? -playerViewDirection.z : 0,
                    applyHorizontalSpeed ? horizontalSpeed : 0,
                    applyHorizontalSpeed ? verticalSpeed * playerViewDirection.y : verticalSpeed
                );
            }
        }

        if (!player.isOnGround && player.isFalling) {
            player.addEffect("slow_falling", 1 * 20, {
                amplifier: 1,
                showParticles: false,
            });
        }
    }
}
