import { BlockPermutation, GameMode, Player, World, world } from "@minecraft/server";
export var MobState;
(function(MobState) {
    MobState[MobState["Default"] = 0] = "Default";
    MobState[MobState["Pathfollowing"] = 1] = "Pathfollowing";
    MobState[MobState["WaitingForNextNode"] = 2] = "WaitingForNextNode";
})(MobState || (MobState = {}));
export class CoroUtil {
    static getSpeed(entity) {
        return this.magnitude(entity.getVelocity());
    }
    static getSpeedXZ(entity) {
        return this.magnitudeXZ(entity.getVelocity());
    }
    /**
     * depending on the character, a char size can range between 1-4, we'll assume 4 to do a fast check, we dont need accuracy for what this is used for
     */ static getStringByteSizeWorstCase(text) {
        return text.length * 4;
    }
    static setPropSafe(target, prop, value) {
        if (typeof value === "string") {
            if (value.length >= this.propStringLengthLimit) {
                err("storage error: hit string limit length for " + prop);
                return;
            }
        }
        if (target instanceof World) {
            let byteSizeCutoff = this.byteLimitForWorld - this.byteSafetyBuffer;
            if (typeof value === "string") {
                byteSizeCutoff -= this.getStringByteSizeWorstCase(value);
            }
            if (target.getDynamicPropertyTotalByteCount() >= byteSizeCutoff) {
                err("storage error: about to hit safety limit for world, not setting value for " + prop);
                return;
            }
        }
        target.setDynamicProperty(prop, value);
    }
    static trySendMessage(entity, msg) {
        if (entity instanceof Player) {
            entity.sendMessage(msg);
        }
    }
    static dimVector3ToStringifiedDimPos(dim, pos) {
        return this.dimStringIDToNumberID(dim.id) + "_" + this.vector3ToString(pos);
    }
    static dimStringIDToNumberID(stringID) {
        if (!stringID.includes(":")) stringID = "minecraft:" + stringID;
        if (stringID == "minecraft:overworld") return 0;
        if (stringID == "minecraft:nether") return 1;
        if (stringID == "minecraft:the_end") return 2;
        return 0;
    }
    static dimNumberIDToStringID(numberID) {
        if (numberID == 0) return "minecraft:overworld";
        if (numberID == 1) return "minecraft:nether";
        if (numberID == 2) return "minecraft:the_end";
        return "minecraft:overworld";
    }
    static stringifiedDimPosToDimension(dimPos) {
        if (dimPos.includes("_")) {
            let dimString = this.dimNumberIDToStringID(+dimPos.split("_")[0]);
            if (dimString !== "minecraft:overworld" && dimString !== "minecraft:nether" && dimString !== "minecraft:the_end") {
                err("invalid dimString: " + dimString);
                //production safety fallback, will cause unintended behavior
                dimString = "minecraft:overworld";
                return undefined;
            }
            return world.getDimension(dimString);
        } else {
            err("invalid dimPos1: " + dimPos);
            //return world.getDimension("minecraft:overworld");
            return undefined;
        }
    }
    static stringifiedDimPosToVector3(dimPos) {
        if (dimPos.includes("_")) {
            let posString = dimPos.split("_")[1];
            return this.stringToVector3(posString);
        } else {
            err("invalid dimPos2: " + dimPos);
            //production safety fallback, will cause unintended behavior
            return undefined; //{x: 0, y: -999, z: 0};
        }
    }
    /* 

    dimToString(dim: Dimension): string {
        return dim.id;
    } */ static vector3ToString(pos) {
        return pos.x + "," + pos.y + "," + pos.z;
    }
    static stringToVector3(posString) {
        let pos = posString.split(",");
        return {
            x: +pos[0],
            y: +pos[1],
            z: +pos[2]
        };
    }
    static getMobState(entity) {
        if (entity.hasComponent("minecraft:mark_variant")) {
            let mark_variant = entity.getComponent("minecraft:mark_variant");
            if (mark_variant.value == 2) {
                return 1;
            } else if (mark_variant.value == 3) {
                return 2;
            } else {
                return 0;
            }
        } else {
            return 0;
        }
    }
    static setMobState(entity, mobstate) {
        if (entity.hasComponent("minecraft:mark_variant")) {
            let mark_variant = entity.getComponent("minecraft:mark_variant");
            if (mobstate == 0) {
                //log("set state default")
                mark_variant.value = 0;
            } else if (mobstate == 1) {
                //log("set state follow")
                mark_variant.value = 2;
            } else if (mobstate == 2) {
                //log("set state waiting")
                mark_variant.value = 3;
            }
        }
    }
    static printVector3(pos) {
        console.warn(pos.x + ", " + pos.y + ", " + pos.z);
    }
    /* static vector3ToString(pos: Vector3) {
    return pos.x + ", " + pos.y + ", " + pos.z;
  } */ static canSee(from, to) {
        return this.canSeeCoord(from.dimension, from.location, to.location);
    }
    static canSeeCoord(dim, from, to) {
        let direction = {
            x: to.x - from.x,
            y: to.y - from.y,
            z: to.z - from.z
        };
        let dist = this.magnitude(direction);
        let vec = {
            x: direction.x / dist,
            y: direction.y / dist,
            z: direction.z / dist
        };
        let curPos = from;
        //step of 0.5 it would still go through walls when diagonals are involved
        let step = 0.25;
        let stepCount = 0;
        while(dist > 0.5){
            stepCount++;
            let block = CoroUtil.getBlock(dim, {
                x: curPos.x,
                y: curPos.y,
                z: curPos.z
            });
            if (block == null || block === undefined) return false;
            if (!block?.permutation.matches("minecraft:air")) return false;
            //console.warn(block.typeId + " - dist: " + dist)
            //console.warn("curPos: " + curPos.x + ", " + curPos.y + ", " + curPos.z + " - dist: " + dist);
            //console.warn("stepCount: " + stepCount);
            curPos = {
                x: curPos.x + vec.x * step,
                y: curPos.y + vec.y * step,
                z: curPos.z + vec.z * step
            };
            dist = this.distToCoord(curPos, to);
            if (stepCount > 200) return false;
        }
        return true;
    }
    static magnitudeXZ(vector) {
        return Math.sqrt(vector.x * vector.x + vector.z * vector.z);
    }
    static magnitude(vector) {
        return Math.sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
    }
    static distToEntity(from, to) {
        return this.distToCoord(from.location, to.location);
    }
    static distToCoord(from, to) {
        let direction = {
            x: from.x - to.x,
            y: from.y - to.y,
            z: from.z - to.z
        };
        return Math.sqrt(direction.x * direction.x + direction.y * direction.y + direction.z * direction.z);
    }
    static distXZToCoord(from, to) {
        let direction = {
            x: from.x - to.x,
            y: from.y - to.y,
            z: from.z - to.z
        };
        return Math.sqrt(direction.x * direction.x + direction.z * direction.z);
    }
    static angleToEntity(from, to) {
        return this.angleToCoord(from.location, to.location);
    }
    static angleToCoord(from, to) {
        let direction = {
            x: from.x - to.x,
            y: from.y - to.y,
            z: from.z - to.z
        };
        let dist = Math.sqrt(direction.x * direction.x + direction.y * direction.y + direction.z * direction.z);
        return {
            x: direction.x / dist,
            y: direction.y / dist,
            z: direction.z / dist
        };
    }
    static getPlayerByName(name) {
        let players = world.getPlayers({
            name: name
        });
        if (players.length > 0) {
            return players[0];
        }
        return undefined;
    }
    //closest to what? lol
    static getClosestPlayer() {
        let overworld = CoroUtil.getOverworld();
        let players = overworld.getEntities({
            type: "minecraft:player",
            closest: 1
        });
        if (players.length > 0) {
            return players[0];
        }
        return undefined;
    }
    static getClosestPlayerToPos(pos) {
        return this.getClosestPlayerToPosWithCutoff(pos, 9999999);
    }
    static getClosestPlayerToPosWithCutoff(pos, maxDist) {
        let overworld = CoroUtil.getOverworld();
        let players = overworld.getEntities({
            type: "minecraft:player",
            location: pos,
            closest: 1,
            maxDistance: maxDist
        });
        if (players.length > 0) {
            return players[0];
        }
        return undefined;
    }
    static getClosestEntity(entity, entities) {
        if (entities.length > 0) {
            let closestDist = 999999;
            let closestEnt = null;
            for (const node of entities){
                //if (CoroUtil.canSee(entity, node)) {
                let dist = CoroUtil.distToEntity(entity, node);
                if (dist < closestDist) {
                    closestDist = dist;
                    closestEnt = node;
                }
            //}
            }
            return closestEnt;
        }
        return null;
    }
    static getOverworld() {
        let overworld = world.getDimension("overworld");
        return overworld;
    }
    static randRange(max) {
        return CoroUtil.rand(0, max);
    }
    static rand(min, max) {
        // Ensure that min is actually smaller than max
        if (min >= max) {
            throw new Error('Minimum value must be less than maximum value');
        }
        // Generate a random number within the specified range
        const randomNumber = Math.random() * (max - min) + min;
        // Return the rounded random number
        return Math.floor(randomNumber);
    }
    static entityIsInLoadedChunk(dim, pos) {
        var block = this.getBlock(dim, pos);
        if (block !== undefined) {
            return block.isValid();
        } else {
            return false;
        }
    }
    /**
   * Extra safety checks on a block position
   * As per Dimension.getBlock, returns undefined if outside of chunk area
   * but if the chunk unloads after the return value is used, the returned value could still throw a LocationInUnloadedChunkError
   * 
   * @param dim 
   * @param pos 
   * @returns 
   */ static getBlock(dim, pos) {
        if (!this.isLoaded(dim, pos)) return undefined;
        return dim.getBlock(pos);
    }
    static setBlock(block, blockID) {
        block.setPermutation(BlockPermutation.resolve(blockID));
    }
    static setBlockAt(dim, pos, blockID) {
        this.setBlockAtWithState(dim, pos, blockID, undefined, undefined);
    }
    static setBlockAtWithState(dim, pos, blockID, stateName, stateValue) {
        var block = this.getBlock(dim, pos);
        if (block !== undefined) {
            if (stateName !== undefined) {
                block.setPermutation(BlockPermutation.resolve(blockID).withState(stateName, stateValue));
            } else {
                block.setPermutation(BlockPermutation.resolve(blockID));
            }
        }
    }
    static setAir(block) {
        block.setPermutation(BlockPermutation.resolve("minecraft:air"));
    }
    static toRadians(val) {
        return val * 0.017453292519943295;
    }
    static toDegrees(val) {
        return val * 57.29577951308232;
    }
    static isLoaded(dim, pos) {
        if (pos.y < this.getMinWorldHeight(dim)) return false;
        if (pos.y > this.getMaxWorldHeight(dim)) return false;
        return dim.getBlock(pos) !== undefined;
    }
    /* */ /* static isNotSolid(dim: Dimension, pos: Vector3): boolean {
    if (pos.y < -64) pos.y = -64;
    var block: Block | undefined = dim.getBlock(pos);
    if (block !== undefined) {
      return block.isAir;
    }
    
  } */ static getMinWorldHeight(dim) {
        return dim.heightRange.min;
    }
    static getMaxWorldHeight(dim) {
        return dim.heightRange.max;
    }
    static up(pos, relativeY) {
        return {
            x: pos.x,
            y: pos.y + relativeY,
            z: pos.z
        };
    }
    static add(pos, x, y, z) {
        return {
            x: pos.x + x,
            y: pos.y + y,
            z: pos.z + z
        };
    }
    static addVector(pos, pos2) {
        return {
            x: pos.x + pos2.x,
            y: pos.y + pos2.y,
            z: pos.z + pos2.z
        };
    }
    static scaleVector(pos, scale) {
        return {
            x: pos.x * scale,
            y: pos.y * scale,
            z: pos.z * scale
        };
    }
    static centerXZ(pos) {
        return {
            x: pos.x + 0.5,
            y: pos.y,
            z: pos.z + 0.5
        };
    }
    static center(pos) {
        return {
            x: pos.x + 0.5,
            y: pos.y + 0.5,
            z: pos.z + 0.5
        };
    }
    static uncenter(pos) {
        return {
            x: pos.x - 0.5,
            y: pos.y - 0.5,
            z: pos.z - 0.5
        };
    }
    static floor(pos) {
        return {
            x: Math.floor(pos.x),
            y: Math.floor(pos.y),
            z: Math.floor(pos.z)
        };
    }
    static toStr(pos) {
        return pos.x + ", " + pos.y + ", " + pos.z;
    }
    //TODO: this doesnt actually work
    static isVector3(object) {
        return 'member' in object;
    }
    static getPropNumber(entity, name) {
        let value = entity.getDynamicProperty(name);
        if (typeof value === 'number') {
            return value;
        }
        return 0;
    }
    static getPropNumberOrUndefined(entity, name) {
        let value = entity.getDynamicProperty(name);
        if (typeof value === 'number') {
            return value;
        }
        return undefined;
    }
    static getPropBool(entity, name) {
        let value = entity.getDynamicProperty(name);
        if (typeof value === 'boolean') {
            return value;
        }
        return false;
    }
    static getPropBoolOrUndefined(entity, name) {
        let value = entity.getDynamicProperty(name);
        if (typeof value === 'boolean') {
            return value;
        }
        return undefined;
    }
    static getPropString(entity, name) {
        let value = entity.getDynamicProperty(name);
        if (typeof value === 'string') {
            return value;
        }
        return "";
    }
    static getPropStringOrUndefined(entity, name) {
        let value = entity.getDynamicProperty(name);
        if (typeof value === 'string') {
            return value;
        }
        return undefined;
    }
    static getPropVector3OrUndefined(entity, name) {
        let value = entity.getDynamicProperty(name);
        if (/* this.isVector3(value) */ value !== undefined && typeof value !== 'number' && typeof value !== 'boolean' && typeof value !== 'string') {
            return value;
        } else {
        //log("not a vector")
        }
        return undefined;
    }
    static hideText(input) {
        return `§` + input.split(``).join(`§`);
    }
    static unhideText(input) {
        return input.replace(new RegExp("§", "g"), ``);
    }
    static getGameMode(player) {
        let players = world.getPlayers({
            name: player.name,
            gameMode: GameMode.adventure
        });
        if (players.length > 0) return GameMode.adventure;
        players = world.getPlayers({
            name: player.name,
            gameMode: GameMode.creative
        });
        if (players.length > 0) return GameMode.creative;
        players = world.getPlayers({
            name: player.name,
            gameMode: GameMode.spectator
        });
        if (players.length > 0) return GameMode.spectator;
        players = world.getPlayers({
            name: player.name,
            gameMode: GameMode.survival
        });
        if (players.length > 0) return GameMode.survival;
        return GameMode.adventure;
    }
    static setSlot(player, itemStack, equipmentSlot) {
        const equipment = player.getComponent("equippable");
        equipment.setEquipment(equipmentSlot, itemStack);
    }
    static getSlot(player, equipmentSlot) {
        const equipment = player.getComponent("equippable");
        return equipment.getEquipment(equipmentSlot);
    }
    /**
   * Requires the entity have an event called "event:despawn"
   * 
   * @param entity 
   */ static despawnEntity(entity) {
        entity?.triggerEvent("event:despawn");
    }
    /**
   * Only usable for x and z
   * 
   * @param posCenter 
   * @param pos 
   */ static getAxisLockedScalarVectorForPositionRelativeToPosition(posCenter, pos) {
        let x = posCenter.x - pos.x;
        let z = posCenter.z - pos.z;
        let yaw = Math.atan2(x, z) + this.toRadians(45 + 180) // * 180 / Math.PI;
        ;
        let snapYaw = /* this.toRadians( */ Math.floor(yaw / this.toRadians(90)) * this.toRadians(90);
        if (this.toDegrees(snapYaw) == 360) {
            snapYaw = this.toRadians(0);
        }
        //log("snapyaw " + this.toDegrees(snapYaw));
        return {
            x: Math.round(Math.sin(snapYaw)),
            y: 0,
            z: Math.round(Math.cos(snapYaw))
        };
    }
    /**
   * Only usable for x and z
   * 
   * @param posCenter 
   * @param pos 
   */ static getAxisLockedScalarVectorBasedOnBlockState(dim, pos) {
        let block = this.getBlock(dim, pos);
        if (block === undefined) return {
            x: 1,
            y: 0,
            z: 0
        };
        let state = "minecraft:cardinal_direction";
        let dir = block.permutation.getState(state);
        //log("state: " + dir);
        if (dir == "north") return {
            x: 0,
            y: 0,
            z: 1
        };
        if (dir == "east") return {
            x: -1,
            y: 0,
            z: 0
        };
        if (dir == "south") return {
            x: 0,
            y: 0,
            z: -1
        };
        if (dir == "west") return {
            x: 1,
            y: 0,
            z: 0
        };
        return {
            x: 1,
            y: 0,
            z: 0
        };
    }
    /**
   * Only usable for x and z
   * 
   * returns snapped yaw in radians
   * 
   * @param posCenter 
   * @param pos 
   */ static getAxisLockedRotationRelativeToPosition(posCenter, pos) {
        let x = posCenter.x - pos.x;
        let z = posCenter.z - pos.z;
        let yaw = Math.atan2(x, z) + this.toRadians(45 + 180) // * 180 / Math.PI;
        ;
        let snapYaw = /* this.toRadians( */ Math.floor(yaw / this.toRadians(90)) * this.toRadians(90);
        if (this.toDegrees(snapYaw) == 360) {
            snapYaw = this.toRadians(0);
        }
        //log("snapyaw " + this.toDegrees(snapYaw));
        return snapYaw;
    }
    static getAxisLockedRotationBasedOnBlockState(dim, pos) {
        let block = this.getBlock(dim, pos);
        if (block === undefined) return 0;
        let state = "minecraft:cardinal_direction";
        let dir = block.permutation.getState(state);
        //log("state: " + dir);
        if (dir == "north") return this.toRadians(0);
        if (dir == "east") return this.toRadians(90);
        if (dir == "south") return this.toRadians(180);
        if (dir == "west") return this.toRadians(270);
        return 0;
    }
    static getEntityCardinalDirection(entity) {
        const cardinals = [
            "north",
            "northeast",
            "east",
            "southeast",
            "south",
            "southwest",
            "west",
            "northwest"
        ];
        const direction = Math.floor((entity.getRotation().y + 200.5) / 45) % 8;
        return cardinals[direction];
    }
    static getEntityCardinalDirection2(posCenter, pos) {
        let x = posCenter.x - pos.x;
        let z = posCenter.z - pos.z;
        let yaw = Math.atan2(x, z) * 180 / Math.PI;
        const cardinals = [
            "north",
            "northeast",
            "east",
            "southeast",
            "south",
            "southwest",
            "west",
            "northwest"
        ];
        const direction = Math.floor((yaw + 200.5) / 45) % 8;
        return cardinals[direction];
    }
    static customRaytrace(player, type, step, stepSearchRadius, maxDist) {
        let posHead = CoroUtil.up(player.getHeadLocation(), 0.1);
        for(let curDist = 0; curDist <= maxDist; curDist += step){
            let vec = this.scaleVector(player.getViewDirection(), curDist);
            let posCheck = CoroUtil.addVector(posHead, vec);
            //player.dimension.spawnParticle("razz_ew:point", posCheck);
            let entities = player.dimension.getEntities({
                type: type,
                location: posCheck,
                maxDistance: stepSearchRadius,
                closest: 1
            });
            //log(entities.length)
            if (entities.length > 0) {
                return entities;
            }
        }
        return [];
    }
    static copyVector3(pos) {
        return {
            x: pos.x,
            y: pos.y,
            z: pos.z
        };
    }
    static newVector() {
        return {
            x: 0,
            y: 0,
            z: 0
        };
    }
    static vec(x, y, z) {
        return {
            x: x,
            y: y,
            z: z
        };
    }
}
CoroUtil.propStringLengthLimit = 32767;
CoroUtil.byteLimitForWorld = 10000000;
CoroUtil.byteSafetyBuffer = 100;
//set by init of mod now
CoroUtil.outputDebug = false;
CoroUtil.outputLog = false;
CoroUtil.outputError = false;
export function debug(val) {
    if (CoroUtil.outputDebug) console.warn(val);
}
export function log(val) {
    if (CoroUtil.outputLog) console.warn(val);
}
/* export function warn(val: string | number) {
  if (outputWarn) console.warn(val);
} */ export function err(val) {
    if (CoroUtil.outputError) console.warn(val);
}
export function posToStr(pos) {
    return pos.x + ", " + pos.y + ", " + pos.z;
}
