import {
    Entity,
    EntityComponentTypes,
    EntityDamageCause,
    EntityEquippableComponent,
    EntityRidingComponent,
    EquipmentSlot,
    ItemCooldownComponent,
    ItemStack,
    Player,
    system,
    world,
} from "@minecraft/server";
import { ArmorSet } from "../data/armor_sets";

export const CameraPresets = {
    free: "minecraft:free",
    first_perseon: "minecraft:first_person",
    third_person: "minecraft:third_person",
};

/**
 *
 * @param {Player} player
 * @param {String} projectileId
 * @param {{power: Number, sound: String, offset: {x: Number, y: Number, z: Number}, gravity:number}} options
 */
export function shootProjectile(player, projectileId, options) {
    const location = player.location;
    const direction = player.getViewDirection();
    const rotation = player.getRotation();
    const { power, sound, uncertainty, offset, gravity } = options;

    let defaultOffset;

    if (!offset) {
        defaultOffset = { x: 0.5, y: 1.6, z: 0.5 };
    } else {
        defaultOffset = offset;
    }

    const projectile = player.dimension.spawnEntity(projectileId, {
        x: location.x + direction.x * defaultOffset.x,
        y: location.y + defaultOffset.y,
        z: location.z + direction.z * defaultOffset.z,
    });

    system.runTimeout(() => {
        projectile.setRotation({ x: rotation.x, y: rotation.y });

        const shootable = projectile.getComponent("minecraft:projectile");
        const velocity = {
            x: direction.x * power,
            y: direction.y * power,
            z: direction.z * power,
        };

        if (shootable) {
            shootable.owner = player;
            shootable.gravity = gravity ?? 0.0;

            // console.warn(shootable.owner.name)

            player.playSound(sound ?? "", location);
            //shootable.shoot(velocity, { uncertainty });
            // projectile.clearVelocity();
            projectile.setDynamicProperty("speed", {
                x: direction.x * power,
                y: direction.y * power,
                z: direction.z * power,
            });

            projectile.applyImpulse({
                x: direction.x * power,
                y: direction.y * power,
                z: direction.z * power,
            });

            applyMovement(projectile);
        }
    });
}

export function handleItemDurability(player, itemStack) {
    const equipment = player.getComponent("minecraft:equippable");

    const durability = itemStack.getComponent("minecraft:durability");

    const currentDurability = durability.damage;

    const maxDurability = durability.maxDurability;

    const newItem = itemStack.clone();

    if (currentDurability < maxDurability) {
        newItem.getComponent("minecraft:durability").damage = Math.min(
            maxDurability,
            currentDurability + 1
        );

        equipment.setEquipment(EquipmentSlot.Mainhand, newItem);
    } else {
        player.playSound("random.break");
        equipment.setEquipment(EquipmentSlot.Mainhand);
    }
}

/* system.runInterval(() => {
    const players = world.getAllPlayers();

    for (let player of players) {
        const projectiles = player.dimension.getEntities({
            families: ["projectile"],
        });

        for (let projectile of projectiles) {
            projectile.clearVelocity();
            const speed = projectile.getDynamicProperty("speed");
            if (speed) {
                projectile.applyImpulse(speed);
            }
        }
    }
}); */

/**
 *
 * @param {Entity} projectile
 */
export function applyMovement(projectile) {
    if (!projectile) return;

    projectile.clearVelocity();
    const speed = projectile.getDynamicProperty("speed");
    if (speed) {
        projectile.applyImpulse(speed);
    }
}

/**
 *
 * @param {Number} max
 * @param {Number} min
 * @returns
 */
export function getRandomBetween(min, max) {
    if (min > max) {
        const tempMin = Math.min(min, max);
        const tempMax = Math.max(min, max);

        return Math.round(Math.random() * (tempMax - tempMin) + tempMin);
    }
    return Math.round(Math.random() * (max - min) + min);
}

/**
 *
 * @param {ItemStack} item
 * @param {Player} player
 * @param {Function} callback
 */
export function startItemCooldownAfterUse(player, item, callback) {
    if (item) {
        /**
         * @type {ItemCooldownComponent}
         */
        const cooldown = item.getComponent("minecraft:cooldown");

        if (cooldown) {
            const cooldownTicks = cooldown?.cooldownTicks;
            const remainingCooldown = cooldown?.getCooldownTicksRemaining(player);

            if (remainingCooldown == cooldownTicks - 1) {
                cooldown.startCooldown(player);

                callback();
            }
        }
    }
}

/**
 *
 * @param {ItemStack} item
 * @param {Player} player
 * @param {Function} callback
 */
export function startItemCooldownAfterUseForWorldEvents(player, item, callback) {
    if (item) {
        /**
         * @type {ItemCooldownComponent}
         */
        const cooldown = item.getComponent("minecraft:cooldown");

        if (cooldown) {
            const cooldownTicks = cooldown?.cooldownTicks;
            const remainingCooldown = cooldown?.getCooldownTicksRemaining(player);

            //console.warn(remainingCooldown)

            if (remainingCooldown == 0 || remainingCooldown == cooldownTicks) {
                cooldown.startCooldown(player);

                callback();
            }
        }
    }
}

/**
 *
 * @param {Player} player
 */
export function resetPlayerProperties(player) {
    const properties = player.getDynamicPropertyIds();

    properties.forEach((prop) => {
        player.setDynamicProperty(prop);
    });
}

/**
 *
 * @param {Player} player
 * @returns {Array<String>}
 */
export function getArmor(player) {
    /**
     * @type {EntityEquippableComponent}
     */
    const equippable = player.getComponent(EntityComponentTypes.Equippable);

    if (equippable) {
        const helmet = equippable.getEquipment(EquipmentSlot.Head);
        const chestplate = equippable.getEquipment(EquipmentSlot.Chest);
        const leggings = equippable.getEquipment(EquipmentSlot.Legs);
        const boots = equippable.getEquipment(EquipmentSlot.Feet);

        const armor_set = [helmet, chestplate, leggings, boots].map(
            (part) => part?.typeId ?? "Empty"
        );

        return armor_set;
    }
}

/**
 *
 * @param {Player} player
 * @param {Array<String>} armor
 */
export function applyArmorSetEffects(player, armor) {
    ArmorSet.forEach((set) => {
        const isFullSet = armor.every((part) => set.parts.includes(part));
        if (isFullSet) {
            const armorSet = Array.from(ArmorSet.entries()).find((obj) =>
                obj[1].parts.every((part) => armor.includes(part))
            )[0];
            // console.warn(JSON.stringify(armorSet));

            for (let effectObj of set.effects) {
                const { effect, duration, amplifier } = effectObj;

                if (armorSet == "smiler") {
                    if (player.isSneaking) {
                        player.addEffect(effect, duration * 20, {
                            amplifier: amplifier,
                            showParticles: false,
                        });
                    }
                } else {
                    player.addEffect(effect, duration * 20, {
                        amplifier: amplifier,
                        showParticles: false,
                    });
                }
            }
        }
    });
}

/**
 *
 * @param {Player} player
 * @param {Number} damage
 * @param {Number} horizontalStrength
 * @param {Number} verticalStrength
 * @param {{affectPlayers:Boolean}} options
 */
export function knockbackEntity(player, damage, horizontalStrength, verticalStrength, options) {
    const hitEntity = player.getEntitiesFromViewDirection({ maxDistance: 4 })[0];
    const viewDirection = player.getViewDirection();
    const knockbackHorizontalStrength = 1.8;
    const knockbackVerticalStrength = 0.6;
    let defaultOptions = {
        affectPlayers: true,
    };

    if (options) {
        defaultOptions = options;
    }

    if (hitEntity) {
        const entity = hitEntity.entity;

        entity.applyDamage(damage, {
            cause: EntityDamageCause.entityAttack,
            damagingEntity: player,
        });

        if (entity instanceof Player) {
            if (defaultOptions.affectPlayers == true) {
                entity.applyKnockback(
                    viewDirection.x,
                    viewDirection.z,
                    knockbackHorizontalStrength,
                    knockbackVerticalStrength
                );
            }
        } else {
            entity.applyImpulse({
                x: viewDirection.x * knockbackHorizontalStrength,
                y: knockbackVerticalStrength,
                z: viewDirection.z * knockbackHorizontalStrength,
            });
        }
    }
}

/**
 *
 * @param {Player} player
 * @param {Number} damage
 * @param {{affectPlayers:Boolean, cause:EntityDamageCause}} options
 */
export function damageEntity(player, damage, options) {
    const hitEntity = player.getEntitiesFromViewDirection({ maxDistance: 4 })[0];

    let defaultOptions = {
        affectPlayers: true,
        cause: EntityDamageCause.entityAttack,
    };

    if (options) {
        defaultOptions = options;
    }

    if (hitEntity) {
        const entity = hitEntity.entity;

        if (entity instanceof Player) {
            if (defaultOptions.affectPlayers == true) {
                entity.applyDamage(damage, {
                    cause: defaultOptions.cause,
                    damagingEntity: player,
                });
            }
        } else {
            entity.applyDamage(damage, {
                cause: defaultOptions.cause,
                damagingEntity: player,
            });
        }
    }
}

/**
 *
 * @param {Player} player
 * @param {Array<{damage:Number,effects:Array<{effectType:String,duration:Number,amplifier:Number}>, callback:Function, delay:Number}>} attacks
 * @param {Number} [startIndex=0]
 */
export function attackSequence(player, attacks, startIndex = 0) {
    const attack = attacks[startIndex];
    const previousAttackDelay = startIndex > 0 ? attacks[startIndex - 1].delay : 0;

    const { delay, damage, effects } = attack;

    system.runTimeout(() => {
        const hitEntity = player.getEntitiesFromViewDirection({ maxDistance: 4 })[0];

        if (hitEntity && hitEntity.entity) {
            damageEntity(player, damage, {
                affectPlayers: true,
                cause: EntityDamageCause.entityAttack,
            });

            for (let effect of effects) {
                hitEntity?.entity?.addEffect(effect.effectType, effect.duration * 20, {
                    amplifier: effect.amplifier,
                    showParticles: false,
                });
            }

            attack.callback();

            if (startIndex + 1 < attacks.length) {
                attackSequence(player, attacks, startIndex + 1);
            }
        }
    }, (delay + previousAttackDelay) * 20);
}

/**
 *
 * @param {Player} player
 * @param {Number} distance
 * @param {Number} repelHorizontalStrength
 * @param {Number} repelVerticalStrength
 */
export function repelEntities(
    player,
    distance,
    repelHorizontalStrength = 0.1,
    repelVerticalStrength = 0.1
) {
    const entities = player.dimension.getEntities({
        location: player.location,
        excludeTypes: ["minecraft:item", "minecraft:xp_orb"],
        maxDistance: distance,
    });

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

    for (let entity of entities) {
        if (entity != player) {
            const playerLocation = player.location;
            const entityLocation = entity.location;

            if (!(entity instanceof Player)) {
                if (entity.matches({ excludeFamilies: ["companion"] })) {
                    const isTamed = entity.getComponent(EntityComponentTypes.IsTamed);

                    if (!isTamed && entity != mount?.entityRidingOn) {
                        entity.applyImpulse({
                            x:
                                entityLocation.x > playerLocation.x
                                    ? repelHorizontalStrength
                                    : -repelHorizontalStrength,
                            y: repelVerticalStrength,
                            z:
                                entityLocation.z > playerLocation.z
                                    ? repelHorizontalStrength
                                    : -repelHorizontalStrength,
                        });
                    }
                }
            }
        }
    }
}

/**
 *
 * @param {String} entityId
 * @param {Map<String, Object>} bossList
 * @returns {Boolean}
 */
export function isBoss(entityId, bossList) {
    const isBoss = bossList.has(entityId);

    return isBoss;
}

/**
 *
 * @param {String} entityId
 * @param {Map<String, Object>} bossList
 * @returns {{name:string, prop: string}|undefined}
 */
export function getKilledBoss(entityId, bossList) {
    const boss = bossList.get(entityId);

    return boss;
}

/**
 *
 * @param {Player} player
 * @param {Array<string>} lightSources
 * @returns {Boolean}
 */
export function isHoldingLightSource(player, lightSources) {
    /**
     * @type {EntityEquippableComponent}
     */
    const equipment = player.getComponent(EntityComponentTypes.Equippable);

    if (equipment) {
        const mainhand = equipment.getEquipment(EquipmentSlot.Mainhand);

        if (mainhand) {
            return lightSources.some((source) => source == mainhand.typeId);
        }
    }
}

/**
 *
 * @param {Block} block
 */
export function getAllStates(block) {
    const allStates = block.permutation.getAllStates();

    return JSON.stringify(allStates);
}

export function getHeldItem(player) {
    /**
     * @type {EntityEquippableComponent}
     */
    const equipment = player.getComponent(EntityComponentTypes.Equippable);

    if (equipment) {
        const mainhand = equipment.getEquipment(EquipmentSlot.Mainhand);

        return mainhand;
    }
}

/**
 *
 * @param {Player} player
 * @returns
 */
export function isPlayerLogged(player) {
    const players = world.getAllPlayers();

    return players.includes(player);
}

/**
 *
 * @param {Entity} entity
 * @returns
 */
export function isEntityLoaded(entity) {
    const entities = world.getDimension("overworld").getEntities();

    return entities.includes(entity);
}

export function isDayTime() {
    return world.getTimeOfDay() > 0 && world.getTimeOfDay() < 13000;
}

export function isNightTime() {
    return world.getTimeOfDay() > 13000 && world.getTimeOfDay() < 23000;
}

/**
 *
 * @param {Entity} entity
 * @param {Entity} hitEntity
 * @param {number} horizontalStrength
 * @param {number} verticalStrength
 */
export function applyKnockback(entity, hitEntity, horizontalStrength, verticalStrength) {
    const direction = entity.getViewDirection();

    if (hitEntity instanceof Player) {
        hitEntity.applyKnockback(direction.x, direction.z, horizontalStrength, verticalStrength);
    } else {
        hitEntity.applyImpulse({
            x: direction.x * horizontalStrength,
            y: verticalStrength,
            z: direction.z * horizontalStrength,
        });
    }
}

/**
 *
 * @param {Entity} entity
 * @param {[{time:number,damage:number}]} attacks
 */
export function sequenceAttack(entity, attacks) {
    const viewDirection = entity.getViewDirection();

    const dimension = entity.dimension;

    const attackRange = 4;

    for (let attack = 0; attack < attacks.length; attack++) {
        const { time, damage } = attacks[attack];

        if (entity) {
            system.runTimeout(() => {
                try {
                    const nearbyEntities =
                        dimension.getEntities({
                            location: {
                                x: entity?.location.x,
                                y: entity?.location.y,
                                z: entity?.location.z,
                            },
                            maxDistance: attackRange,
                        }) ?? [];

                    for (let mob of nearbyEntities) {
                        if (mob != entity) {
                            if (mob instanceof Player) {
                                if (entity.matches({ excludeFamilies: ["ressurected"] })) {
                                    mob?.applyDamage(damage, {
                                        cause: EntityDamageCause.entityAttack,
                                        damagingEntity: entity,
                                    });
                                }
                            } else {
                                mob?.applyDamage(damage, {
                                    cause: EntityDamageCause.entityAttack,
                                    damagingEntity: entity,
                                });
                            }
                        }
                    }
                } catch (e) {}
            }, time * 20);
        }
    }
}

/**
 *
 * @param {Entity} entity
 */
export function isUnderground(entity) {
    const hasBlockAbove = entity.dimension.getBlockFromRay(entity.location, {
        x: 0,
        y: 1,
        z: 0,
    })?.block;
    const heigth = entity.location.y;

    return heigth < 60 && hasBlockAbove != undefined;
}
