import * as mc from "@minecraft/server"
import { Vector3, Vector2 } from "./lib/vector"
/**
 * Gera um número aleatório entre min e max (inclusive).
 * Evita retornar o número `exception`, se ele for fornecido.
 *
 * @param {number} min - O valor mínimo do intervalo.
 * @param {number} max - O valor máximo do intervalo.
 * @param {number} [exception] - (Opcional) Um número a ser evitado.
 * @returns {number} Um número aleatório entre min e max, excluindo a exceção se ela for fornecida.
 */
export function random(min, max, exception = null) {
    let randomNumber;
    do {
        randomNumber = Math.floor(Math.random() * (max - min + 1)) + min;
    } while (exception !== null && randomNumber === exception);
    return randomNumber;
}
export function anchor(dir, pos, offset2 = { x: 0, y: 0, z: 0 }) {

    const offset = { x: !offset2.x ? 0 : offset2.x, y: !offset2.y ? 0 : offset2.y, z: !offset2.z ? 0 : offset2.z };
    const dir_length = Math.sqrt(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z);
    const normalizedDir = { x: dir.x / dir_length, y: dir.y / dir_length, z: dir.z / dir_length };
    const loc_vec = { x: -normalizedDir.z, y: 0, z: normalizedDir.x };

    //normalize to avoid different offset depending on the direction
    const loc_length = Math.sqrt(loc_vec.x * loc_vec.x + loc_vec.y * loc_vec.y + loc_vec.z * loc_vec.z);
    const norm_loc = { x: loc_vec.x / loc_length, y: loc_vec.y / loc_length, z: loc_vec.z / loc_length };

    return {
        x: pos.x + norm_loc.x * offset.x + normalizedDir.x * offset.z,
        y: pos.y + norm_loc.y * offset.x + offset.y,
        z: pos.z + norm_loc.z * offset.x + normalizedDir.z * offset.z
    };
}

/**
 * Get all entities in the same dimension as the given entity within a certain range.
 * @param {mc.Entity} entity
 * @param {number} [range=40] range in blocks
 * @returns {mc.Entity[]} array of entities
 */
export function getEntities(entity, range) {
    return entity.dimension.getEntities({
        excludeFamilies: excludeFamilies,
        excludeTypes: excludeTypes,
        maxDistance: range,
        location: entity.location
    })
}
export const excludeTypes = [
    "minecraft:item",
    "minecraft:armor_stand",
    "minecraft:xp_orb",
    "minecraft:arrow"
]
export const excludeFamilies = [
    "ld_eb:none"
]

export function locator(entity, offset = { x: 0, y: 0, z: 0 }, fixRotation = true, fixY = true) {
    const locator = entity.dimension.spawnEntity(
        "ld_eb:none",
        anchor(
            fixY
                ? entity.getViewDirection()
                : { x: entity.getViewDirection().x, y: 0, z: entity.getViewDirection().z },
            entity.location,
            {
                x: !offset.x ? 0 : offset.x,
                y: !offset.y ? 0 : offset.y,
                z: !offset.z ? 0 : offset.z
            }
        )
    )
    fixRotation ? locator.setRotation(entity.getRotation()) : null
    return locator
}
export function locatorEntity(entity, offset = { x: 0, y: 0, z: 0 }, fixRotation = true, fixY = true) {
    const locator = entity.dimension.spawnEntity(
        "ld_eb:none_entity",
        anchor(
            fixY
                ? entity.getViewDirection()
                : { x: entity.getViewDirection().x, y: 0, z: entity.getViewDirection().z },
            entity.location,
            {
                x: !offset.x ? 0 : offset.x,
                y: !offset.y ? 0 : offset.y,
                z: !offset.z ? 0 : offset.z
            }
        )
    )
    fixRotation ? locator.setRotation(entity.getRotation()) : null
    return locator
}
export function locatorEntitya(entity, offset = { x: 0, y: 0, z: 0 }, fixRotation = true, fixY = true) {
    const locator = entity.dimension.spawnEntity(
        "armor_stand",
        anchor(
            fixY
                ? entity.getViewDirection()
                : { x: entity.getViewDirection().x, y: 0, z: entity.getViewDirection().z },
            entity.location,
            {
                x: !offset.x ? 0 : offset.x,
                y: !offset.y ? 0 : offset.y,
                z: !offset.z ? 0 : offset.z
            }
        )
    )
    fixRotation ? locator.setRotation(entity.getRotation()) : null
    return locator
}

export function spell(id, entity, offset = { x: 0, y: 0, z: 0 }, rotation = { x: 0, y: 0, z: 0 }, fixRotation = true, fixY = true) {
    const spell = entity.dimension.spawnEntity(
        id,
        anchor(
            fixY
                ? entity.getViewDirection()
                : { x: entity.getViewDirection().x, y: 0, z: entity.getViewDirection().z },
            entity.location,
            {
                x: !offset.x ? 0 : offset.x,
                y: !offset.y ? 0 : offset.y,
                z: !offset.z ? 0 : offset.z
            }
        )
    )
    fixRotation ? spell.setRotation(fixY ? entity.getRotation() : { x: entity.getRotation().x, y: entity.getRotation().y, z: 0 }) : null
    spell.setProperty("ld_eb:x_rotation", !rotation.x ? 0 : rotation.x)
    spell.setProperty("ld_eb:y_rotation", !rotation.y ? 0 : rotation.y)
    spell.setProperty("ld_eb:z_rotation", !rotation.z ? 0 : rotation.z)
    return spell
}

/**
 * Returns the distance between two points.
 * @param {mc.BlockLocation} a 
 * @param {mc.BlockLocation} b 
 * @returns {number} distance between the two points
 */
export function getDistance(a, b) {
    return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2) + Math.pow(a.z - b.z, 2));
}
/**
 * 
 * @param {mc.Entity} entity 
 * @param {mc.Vector3} location 
 * @param {Number} speed 
 */
export function ProjectileShoot(entity, speed = {}) {
    if (!entity) return;
    /**@type {mc.EntityProjectileComponent} */
    let projectile = entity.getComponent(mc.EntityProjectileComponent.componentId)
    if (!projectile) return
    projectile.gravity = 0
    projectile.owner = entity
    projectile.shoot({
        x: entity.getViewDirection().x * (!speed.x ? 0 : speed.x),
        y: entity.getViewDirection().y * (!speed.y ? 0 : speed.y),
        z: entity.getViewDirection().z * (!speed.z ? 0 : speed.z)
    })
}
/**
 * 
 * @param {mc.Entity} entity 
 * @param {mc.Vector3} location 
 * @param {Number} speed 
 */
export function ProjectileShootLocation(entity, location, speed = 1) {
    if (!entity) return;
    /**@type {mc.EntityProjectileComponent} */
    let projectile = entity.getComponent(mc.EntityProjectileComponent.componentId)
    if (!projectile) return
    projectile.gravity = 0
    let newlocation = {
        x: location.x - projectile.entity.location.x,
        y: location.y - projectile.entity.location.y,
        z: location.z - projectile.entity.location.z
    }
    projectile.owner = entity
    let distance = Math.hypot(newlocation.x, newlocation.y, newlocation.z)
    if (distance <= 0) return
    let velocity = {
        x: (newlocation.x / distance) * speed,
        y: (newlocation.y / distance) * speed,
        z: (newlocation.z / distance) * speed
    }
    projectile.shoot(velocity)
}
/**
 * Retorna uma opção aleatória com base nas chances definidas,
 * evitando repetições recentes com base em um histórico configurável.
 * 
 * @param {Array<{id: any, chance: number}>} options - Array de objetos com a opção e sua chance.
 * @param {Array<any>} history - Histórico das últimas opções selecionadas.
 * @param {number} historySize - Tamanho máximo do histórico para evitar repetições.
 * @returns {any} A opção selecionada aleatoriamente com base nas chances.
 */
export function randomWithChances(options, history = [], historySize = 2) {
    // Calcula o total de chances
    const totalChance = options.reduce((sum, item) => sum + item.chance, 0);

    // Filtra opções que estão no histórico
    const filteredOptions = options.filter(item => !history.includes(item.id));

    // Se todas as opções estão no histórico, reinicia o histórico
    const pool = filteredOptions.length > 0 ? filteredOptions : options;

    // Gera um número aleatório entre 0 e o total de chances
    const random = Math.random() * totalChance;

    // Determina qual opção corresponde ao número aleatório
    let accumulatedChance = 0;
    for (const item of pool) {
        accumulatedChance += item.chance;
        if (random < accumulatedChance) {
            // Atualiza o histórico
            if (history.length >= historySize) {
                history.shift(); // Remove o item mais antigo do histórico
            }
            history.push(item.id);

            return item.id;
        }
    }

    // Fallback (deve ser impossível alcançar este ponto se as chances forem configuradas corretamente)
    throw new Error("As chances estão configuradas incorretamente.");
}

export function middleImpulse(entity, spell, x, y) {
    const directionX = spell.location.x - entity.location.x;
    const directionZ = spell.location.z - entity.location.z;

    const distance = Math.sqrt(directionX ** 2 + directionZ ** 2);

    if (distance > 0) {
        const normalizedX = directionX / distance;
        const normalizedZ = directionZ / distance;

        try { entity.applyKnockback(normalizedX, normalizedZ, x, y) } catch {}
    }
}
export function middleImpulse2(entity, location, x, y) {
    const directionX = location.x - entity.location.x;
    const directionZ = location.z - entity.location.z;

    const distance = Math.sqrt(directionX ** 2 + directionZ ** 2);

    if (distance > 0) {
        const normalizedX = directionX / distance;
        const normalizedZ = directionZ / distance;

        try { entity.applyKnockback(normalizedX, normalizedZ, x, y) } catch {}
    }
}
/**
 * Faz a entidade olhar para o alvo apenas no eixo Y.
 *
 * @param {mc.Entity} entity - A entidade que irá olhar para o alvo.
 * @param {mc.Entity} targetEntity - A entidade alvo.
 */
export function lookAtTargetOnY(entity, targetEntity) {
    const dx = targetEntity.location.x - entity.location.x;
    const dz = targetEntity.location.z - entity.location.z;

    // Calcula o ângulo de rotação (Yaw) entre a posição da entidade e o alvo
    const yaw = Math.atan2(dz, dx) * (180 / Math.PI); // Converte de radianos para graus

    // Aplica a rotação, definindo apenas o eixo Y (yaw) da entidade
    entity.setRotation({ x: 0, y: yaw }); // Faz a entidade olhar no plano XZ (apenas eixo Y)
}

/** @param { mc.Player } player */
export const animation = (player, animation, typeId, id, securtyItem = false) => {
    if (!securtyItem) {
        player.playAnimation(
            animation,
            {
                blendOutTime: 0.2,
                controller: `ld_eb:${typeId}_${id}_securty`,
                nextState: `none`
            }
        )
        return
    }
    player.playAnimation(
        animation,
        {
            blendOutTime: 0.2,
            controller: `ld_eb:${typeId}_${id}`,
            nextState: `none`,
            stopExpression: `!q.is_item_name_any('slot.weapon.mainhand', 0, '${typeId}')`
        }
    )
}
/** @param { mc.Player } player */
export const animationFilled = (player, animation, typeId, id, securtyItem = false) => {
    if (!securtyItem) {
        player.playAnimation(
            animation,
            {
                blendOutTime: 0.2,
                controller: `controller_name1`,
                nextState: `none`
            }
        )
        return
    }
    player.playAnimation(
        animation,
        {
            blendOutTime: 0.2,
            controller: `controller_name1`,
            nextState: `none`,
            stopExpression: `!q.is_item_name_any('slot.weapon.mainhand', 0, '${typeId}')`
        }
    )
}


export function rayCast(player, width, distance, comando, ignore = []) {
    for (let i = 0; i <= distance; i++) {
        try {
            const owner = player.typeId === "minecraft:player" ? player : player.owner
            const entity = owner.dimension.spawnEntity("ld_eb:none", anchor(owner.getViewDirection(), owner.location, { x: 0, y: 0, z: i * width }))
            entity.nameTag = owner.nameTag + "_ray"
            const entitiesFromRay = entity.dimension.getEntities({
                location: entity.location,
                maxDistance: width,
                excludeNames: [owner.nameTag, owner.nameTag + "_ray"],
                excludeFamilies: excludeFamilies,
                excludeTypes: excludeFamilies
            }).filter(e => !ignore.includes(e))
            entitiesFromRay.forEach(target => {
                comando(target)
            })
            mc.system.runTimeout(() => { entity.remove() }, 0.2 * 20)
        } catch (e) {}
    }
}


export const damage = {
    "baixo": 5,
    "moderado": 10,
    "alto": 20,
    "extremo": 40
}

export const camera = (player, position = { x: 0, y: 0, z: 0 }, focal = { x: 0, y: 0, z: 0 }) => {
    player.camera.setCamera("minecraft:free", {
        "location": anchor({ x: player.getViewDirection().x, y: 0, z: player.getViewDirection().z }, player.location, {
            x: !position.x ? 0 : position.x,
            y: !position.y ? 0 : position.y,
            z: !position.z ? 0 : position.z
        }),
        "facingLocation": anchor({ x: player.getViewDirection().x, y: 0, z: player.getViewDirection().z }, player.location, {
            x: !focal.x ? 0 : focal.x,
            y: !focal.y ? 0 : focal.y,
            z: !focal.z ? 0 : focal.z
        })
    })
}
/**
 * Sets the camera of the target entity to a free mode, positioning it relative to the player entity.
 *
 * @param {mc.Player} target - The entity whose camera will be set.
 * @param {mc.Player} player - The player entity used as the reference for positioning.
 * @param {Object} position - The relative position offset for the camera.
 * @param {number} position.x - The x-axis offset from the player's position.
 * @param {number} position.y - The y-axis offset from the player's position.
 * @param {number} position.z - The z-axis offset from the player's position.
 * @param {Object} focal - The relative focal point offset for the camera.
 * @param {number} focal.x - The x-axis offset for the camera's focal point.
 * @param {number} focal.y - The y-axis offset for the camera's focal point.
 * @param {number} focal.z - The z-axis offset for the camera's focal point.
 * @param {number} blend - The blend factor for transitioning the camera view.
 */

export const cameraToPlayer = (target, player, position = { x: 0, y: 0, z: 0 }, focal = { x: 0, y: 0, z: 0 }, blend = 0, ease = "Linear") => {
    if (!mc.world.getDynamicProperty("ld_eb:camera")) return
    target.camera.setCamera("minecraft:free", {
        "location": anchor({ x: player.getViewDirection().x, y: 0, z: player.getViewDirection().z }, player.location, {
            x: !position.x ? 0 : position.x,
            y: !position.y ? 0 : position.y,
            z: !position.z ? 0 : position.z
        }),
        "facingLocation": anchor({ x: player.getViewDirection().x, y: 0, z: player.getViewDirection().z }, player.location, {
            x: !focal.x ? 0 : focal.x,
            y: !focal.y ? 0 : focal.y,
            z: !focal.z ? 0 : focal.z
        }),
        "easeOptions": {
            "easeTime": blend,
            "easeType": ease
        }
    })
}
export const fade = (player, color, time = [0, 0, 0]) => {
    const basicColors = [
        { name: "Red", rgb: [255, 0, 0] },
        { name: "Green", rgb: [0, 255, 0] },
        { name: "Blue", rgb: [0, 0, 255] },
        { name: "Cyan", rgb: [0, 255, 255] },
        { name: "Magenta", rgb: [255, 0, 255] },
        { name: "Yellow", rgb: [255, 255, 0] },
        { name: "black", rgb: [0, 0, 0] },
        { name: "white", rgb: [255, 255, 255] },
        { name: "Gray", rgb: [128, 128, 128] },
        { name: "Orange", rgb: [255, 165, 0] }
    ]
    const finalColor = basicColors.find(c => c.name === color)
    player.camera.fade({
        fadeColor: { red: finalColor.rgb[0] / 255, green: finalColor.rgb[1] / 255, blue: finalColor.rgb[2] / 255 },
        fadeTime: {
            fadeInTime: time[0],
            holdTime: time[1],
            fadeOutTime: time[2]
        }
    })
}



export function aboveBlock(location, dimension) {
    let blocoMaisAlto = null;

    // Percorre de cima (altura atual da entidade) até o valor mínimo
    for (let y = Math.floor(location.y); y >= dimension.heightRange.min; y--) {
        const posicaoBloco = new Vector3(location.x, y, location.z);
        const bloco = dimension.getBlock(posicaoBloco);

        // Verifica se o bloco não é ar e retorna o bloco mais alto encontrado
        if (!bloco.isAir) {
            blocoMaisAlto = y + 1; // Retorna o bloco "above" encontrado
            break; // Encontrado, encerra o loop
        }
    }
    return blocoMaisAlto;
}


export const startUlt = (player, owner) => {
    const isOwner = player === owner
    player.setDynamicProperty("ld_eb:is_in_ult", true)
    !isOwner ? null: //console1-console.warn(player.nameTag)
    !isOwner ? null: player.teleport(player.location, {
        rotation: new Vector2(0, player.getRotation().y)
    })
    !mc.world.getDynamicProperty("ld_eb:camera") ? null: player.addEffect("slowness", 9999, {
        amplifier: 255,
        showParticles: false
    })
    const location = new Vector3(player.location.x, aboveBlock(player.location, player.dimension), player.location.z)
    !isOwner ? null: player.teleport(location?.y ? location : player.location)
    !isOwner ? null: player.inputPermissions ? player.inputPermissions.cameraEnabled = false: null
    !isOwner ? null: player.inputPermissions ? player.inputPermissions.movementEnabled = false: null
    !mc.world.getDynamicProperty("ld_eb:camera") ? null: player.onScreenDisplay.hideAllExcept()
    fade(player, "black", [0, 0.4, 0.2])
}
export const finalUlt = (player, owner) => {
    const isOwner = player === owner
    player.setDynamicProperty("ld_eb:is_in_ult", false)
    player.camera.clear()
    player.removeEffect("slowness")
    !isOwner ? null: player.inputPermissions ? player.inputPermissions.cameraEnabled = true: null
    !isOwner ? null: player.inputPermissions ? player.inputPermissions.movementEnabled = true: null
    player.onScreenDisplay.resetHudElements()
}

export function shakeBlend(player, { power = 5.0, holdTime = 0.1, outTime = 0.1, steps = 6 } = {}) {
    const outInterval = outTime / steps;

    // Hold with subtle oscillation
    for (let i = 0; i < steps; i++) {
        mc.system.runTimeout(() => {
            const oscillation = power * Math.sin(i / steps * Math.PI * 4) * 0.02;
            player.runCommand(`camerashake add @s ${power + oscillation} ${holdTime / steps} positional`);
        }, i * holdTime * 20 / steps);
    }

    // Smooth ease-out
    for (let i = 0; i < steps; i++) {
        mc.system.runTimeout(() => {
            const smoothFactor = Math.sin((i / steps) * Math.PI / 2);
            const currentPower = power * (1 - smoothFactor);
            if (currentPower > 0) {
                player.runCommand(`camerashake add @s ${currentPower} ${outInterval} positional`);
            }
        }, holdTime * 20 + i * outInterval * 20);
    }
}

export const spawnBox = (entity, radius) => {
    entity.setDynamicProperty("ld_eb:radius", radius)
    entity.setDynamicProperty("ld_eb:skill_type", "ld_eb:ld_eb_frostbound_glaive.glacial_cataclysm")
    entity.runCommand(`fill ~${radius} ~${radius} ~${radius} ~-${radius}~-${radius}~-${radius} barrier replace air`)
    entity.runCommand(`fill ~${radius-1} ~${radius-1} ~${radius-1} ~-${radius-1}~-${radius-1}~-${radius-1} air replace barrier`)
}
export const despawnBox = (entity) => {
    const radius = entity.getDynamicProperty("ld_eb:radius")
    entity.runCommand(`fill ~${radius+5} ~${radius+5} ~${radius+5} ~-${radius+5}~-${radius+5}~-${radius+5} air replace barrier`)
}