import {
    EasingType,
    Entity,
    EntityComponentTypes,
    EntityInventoryComponent,
    EquipmentSlot,
    ItemComponentTypes,
    ItemStack,
    Player,
    StructureAnimationMode,
    StructureRotation,
    system,
    world,
} from "@minecraft/server";

/**
 *
 * @param {Entity} entity
 */
export function movePlayerAway(entity, distance = 1.6) {
    try {
        const { dimension, location } = entity;
        if (dimension == undefined || location == undefined) return;
        if (dimension && location) {
            const nearbyPlayers = dimension.getPlayers({
                location: location,
                maxDistance: distance,
            });

            if (nearbyPlayers.length > 0) {
                for (let player of nearbyPlayers) {
                    const velocity = player.getVelocity();

                    const xDirection = player.location.x > location.x ? 1 : -1;

                    const zDirection = player.location.z > location.z ? 1 : -1;

                    player.applyKnockback(xDirection, zDirection, 0.5, velocity.y);
                }
            }
        }
    } catch (e) {}
}

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

const clownTents = new Map([
    [
        "px_hc:clown",
        {
            structureId: "px_hc:tent_1",
            mob: "px_hc:clown",
            mobSpawnOffset: {
                x: 0,
                y: 1,
                z: 0,
            },
            size: {
                x: 35,
                y: 35,
                z: 35,
            },
            offset: {
                x: 0,
                y: -1,
                z: 0,
            },
        },
    ],
    [
        "px_hc:ringmaster",
        {
            structureId: "px_hc:tent_2",
            mob: "px_hc:ringmaster",
            mobSpawnOffset: {
                x: 0,
                y: 1,
                z: 0,
            },
            size: {
                x: 35,
                y: 35,
                z: 35,
            },
            offset: {
                x: 0,
                y: -1,
                z: 0,
            },
        },
    ],
    [
        "px_hc:acrobat_juggler",
        {
            structureId: "px_hc:tent_3",
            mob: "px_hc:acrobat_juggler",
            mobSpawnOffset: {
                x: 0,
                y: 0,
                z: 0,
            },
            size: {
                x: 35,
                y: 35,
                z: 35,
            },
            offset: {
                x: 0,
                y: -1,
                z: 0,
            },
        },
    ],
    [
        "px_hc:illusionist",
        {
            structureId: "px_hc:tent_4",
            mob: "px_hc:illusionist",
            mobSpawnOffset: {
                x: 0,
                y: 0,
                z: 0,
            },
            size: {
                x: 35,
                y: 35,
                z: 35,
            },
            offset: {
                x: 0,
                y: -1,
                z: 0,
            },
        },
    ],
    [
        "px_hc:strongman",
        {
            structureId: "px_hc:tent_5",
            mob: "px_hc:strongman",
            mobSpawnOffset: {
                x: 0,
                y: 1,
                z: 0,
            },
            size: {
                x: 35,
                y: 35,
                z: 35,
            },
            offset: {
                x: 0,
                y: -1,
                z: 0,
            },
        },
    ],
    [
        "px_hc:harley_quinn",
        {
            structureId: "px_hc:tent_6",
            mob: "px_hc:harley_quinn",
            mobSpawnOffset: {
                x: 0,
                y: 0,
                z: 0,
            },
            size: {
                x: 35,
                y: 35,
                z: 35,
            },
            offset: {
                x: 0,
                y: -1,
                z: 0,
            },
        },
    ],
    [
        "px_hc:butcher_clown",
        {
            structureId: "px_hc:tent_7",
            mob: "px_hc:butcher_clown",
            mobSpawnOffset: {
                x: 0,
                y: 0,
                z: 0,
            },
            size: {
                x: 35,
                y: 35,
                z: 35,
            },
            offset: {
                x: 0,
                y: -2,
                z: 0,
            },
        },
    ],
    [
        "px_hc:pyro",
        {
            structureId: "px_hc:tent_8",
            mob: "px_hc:pyro",
            mobSpawnOffset: {
                x: 0,
                y: 1,
                z: 0,
            },
            size: {
                x: 35,
                y: 35,
                z: 35,
            },
            offset: {
                x: 0,
                y: -1,
                z: 0,
            },
        },
    ],
    [
        "px_hc:stilt_walker",
        {
            structureId: "px_hc:tent_9",
            mob: "px_hc:stilt_walker",
            mobSpawnOffset: {
                x: 0,
                y: 0,
                z: 0,
            },
            size: {
                x: 35,
                y: 35,
                z: 35,
            },
            offset: {
                x: 0,
                y: -1,
                z: 0,
            },
        },
    ],
    [
        "px_hc:jester",
        {
            structureId: "px_hc:tent_10",
            mob: "px_hc:jester",
            mobSpawnOffset: {
                x: 0,
                y: 0,
                z: 0,
            },
            size: {
                x: 35,
                y: 35,
                z: 35,
            },
            offset: {
                x: 0,
                y: -1,
                z: 0,
            },
        },
    ],
]);

const CameraPreset = {
    Free: "minecraft:free",
    FirstPerson: "minecraft:first_person",
    ThirdPerson: "minecraft:third_person",
};

/**
 *
 * @param {Player} player
 * @param {String} clown
 * @param {Number} delay
 */
export function placeCircusTent(player, clown, delay) {
    const { structureId, size, offset, mob, mobSpawnOffset } = clownTents.get(clown);
    const { dimension, location } = player;
    const viewDirection = player.getViewDirection();
    const playerRotation = player.getRotation();

    if (!structureId) return;

    let structureRotation;
    let structureLocation;

    if (playerRotation.y >= 45 && playerRotation.y <= 135) {
        structureRotation = StructureRotation.None;
        offset.x = -size.x;
        offset.z = -size.z / 2;

        structureLocation = {
            x: location.x - size.x / 2 + mobSpawnOffset.x,
            y: location.y + mobSpawnOffset.y,
            z: location.z + mobSpawnOffset.z,
        };
    }
    if (playerRotation.y <= -45 && playerRotation.y >= -135) {
        structureRotation = StructureRotation.Rotate180;
        offset.x = 0;
        offset.z = -size.z / 2;

        structureLocation = {
            x: location.x + size.x / 2 + mobSpawnOffset.x,
            y: location.y + mobSpawnOffset.y,
            z: location.z + mobSpawnOffset.z,
        };
    }
    if (playerRotation.y >= -45 && playerRotation.y <= 45) {
        structureRotation = StructureRotation.Rotate270;
        offset.x = -size.x / 2;
        offset.z = 0;

        structureLocation = {
            x: location.x + mobSpawnOffset.x,
            y: location.y + mobSpawnOffset.y,
            z: location.z + size.z / 2 + mobSpawnOffset.z,
        };
    }
    if (
        (playerRotation.y >= 135 && playerRotation.y < 180) ||
        (playerRotation.y <= -135 && playerRotation.y > -180)
    ) {
        structureRotation = StructureRotation.Rotate90;
        offset.x = -size.x / 2;
        offset.z = -size.z;

        structureLocation = {
            x: location.x + mobSpawnOffset.x,
            y: location.y + mobSpawnOffset.y,
            z: location.z - size.z / 2 + mobSpawnOffset.z,
        };
    }

    if (isPlayerLogged(player)) {
        const CAMERA_ANIM = system.runTimeout(() => {
            player.camera.setCamera(CameraPreset.Free, {
                easeOptions: {
                    easeType: EasingType.Linear,
                    easeTime: 0,
                },
                location: {
                    x: location.x - viewDirection.x,
                    y: location.y,
                    z: location.z - viewDirection.z,
                },
                facingLocation: structureLocation,
            });

            player.camera.setCamera(CameraPreset.Free, {
                easeOptions: {
                    easeType: EasingType.Linear,
                    easeTime: 6,
                },
                location: {
                    x: location.x - viewDirection.x * 24,
                    y: location.y + 24,
                    z: location.z - viewDirection.z * 24,
                },
                facingLocation: structureLocation,
            });
        }, delay + 6 * 20);
    }

    const CLEAR_CAMERA = system.runTimeout(() => {
        if (isPlayerLogged(player)) {
            player.camera.clear();
        }
    }, delay + 12 * 20);

    const SPAWN_STRUCTURE = system.runTimeout(() => {
        world.structureManager.place(
            structureId,
            dimension,
            {
                x: location.x + offset.x,
                y: location.y + offset.y,
                z: location.z + offset.z,
            },
            {
                rotation: structureRotation,
                includeEntities: false,
                animationMode: StructureAnimationMode.Layers,
                animationSeconds: 4,
            }
        );

        const placeholder = dimension.spawnEntity("px_hc:placeholder", structureLocation);

        const SPAWN_MOB = system.runTimeout(() => {
            const clownSpawned = dimension.spawnEntity(mob, structureLocation);
        }, 7 * 20);
    }, delay * 20);
}

/**
 *
 * @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);
}

/**
 *
 * @param {Number} min
 * @param {Number} max
 * @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 {Player} player
 * @param {Map<string, {item:string, slot:EquipmentSlot}>} replaceableItems
 */
export function replaceHotbarItems(player, replaceableItems) {
    const hotbarItems = getHotbarItems(player);

    if (hotbarItems) {
        /**
         * @type {EntityInventoryComponent}
         */
        const inventory = player.getComponent(EntityComponentTypes.Inventory);

        hotbarItems.forEach((item) => {
            const isReplaceable = replaceableItems.has(item.itemId);

            const itemStack = item.item;

            if (itemStack instanceof ItemStack) {
                /**
                 * @type {ItemDurabilityComponent}
                 */
                const durability = itemStack.getComponent(ItemComponentTypes.Durability);
                /**
                 * @type {ItemEnchantableComponent}
                 */
                const enchantable = itemStack.getComponent(ItemComponentTypes.Enchantable);

                if (durability && enchantable) {
                    if (isReplaceable) {
                        const replacedItem = new ItemStack(replaceableItems.get(item.itemId).item);

                        /**
                         * @type {ItemDurabilityComponent}
                         */
                        const replacedItemDurability = replacedItem.getComponent(
                            ItemComponentTypes.Durability
                        );
                        /**
                         * @type {ItemEnchantableComponent}
                         */
                        const replacedItemEnchantable = replacedItem.getComponent(
                            ItemComponentTypes.Enchantable
                        );

                        if (replacedItemDurability && replacedItemEnchantable) {
                            replacedItemDurability.damage = durability.damage;

                            replacedItemEnchantable.addEnchantments(enchantable.getEnchantments());

                            inventory.container.setItem(item.slot, replacedItem);
                        }
                    }
                }
            }
        });
    }
}

/**
 *
 * @param {Player} player
 * @param {Map<string, {item:string, slot:EquipmentSlot}>} replaceableItems
 */
export function replaceInventoryItems(player, replaceableItems) {
    const inventoryItems = getInventoryItems(player);

    if (inventoryItems) {
        /**
         * @type {EntityInventoryComponent}
         */
        const inventory = player.getComponent(EntityComponentTypes.Inventory);

        inventoryItems.forEach((item) => {
            if (item.slot > 8) {
                const isReplaceable = replaceableItems.has(item.itemId);

                const itemStack = item.item;

                if (itemStack instanceof ItemStack) {
                    /**
                     * @type {ItemDurabilityComponent}
                     */
                    const durability = itemStack.getComponent(ItemComponentTypes.Durability);
                    /**
                     * @type {ItemEnchantableComponent}
                     */
                    const enchantable = itemStack.getComponent(ItemComponentTypes.Enchantable);

                    if (durability && enchantable) {
                        if (isReplaceable) {
                            const replacedItem = new ItemStack(
                                replaceableItems.get(item.itemId).item
                            );

                            /**
                             * @type {ItemDurabilityComponent}
                             */
                            const replacedItemDurability = replacedItem.getComponent(
                                ItemComponentTypes.Durability
                            );
                            /**
                             * @type {ItemEnchantableComponent}
                             */
                            const replacedItemEnchantable = replacedItem.getComponent(
                                ItemComponentTypes.Enchantable
                            );

                            if (replacedItemDurability && replacedItemEnchantable) {
                                replacedItemDurability.damage = durability.damage;

                                replacedItemEnchantable.addEnchantments(
                                    enchantable.getEnchantments()
                                );

                                inventory.container.setItem(item.slot, replacedItem);
                            }
                        }
                    }
                }
            }
        });
    }
}

/**
 *
 * @param {Player} player
 */
export function getHotbarItems(player) {
    const items = [];

    /**
     * @type {EntityInventoryComponent}
     */
    const inventory = player.getComponent(EntityComponentTypes.Inventory);

    if (inventory) {
        for (let i = 0; i < 9; i++) {
            const item = inventory.container.getItem(i);

            items.push({
                item: item ?? {},
                itemId: item?.typeId ?? "Empty",
                amount: item?.amount ?? 0,
                slot: i,
            });
        }

        return items;
    }
}

/**
 *
 * @param {Player} player
 */
export function getInventoryItems(player) {
    const items = [];

    /**
     * @type {EntityInventoryComponent}
     */
    const inventory = player.getComponent(EntityComponentTypes.Inventory);

    if (inventory) {
        const inventorySize = inventory.container.size;

        for (let i = 0; i < inventorySize; i++) {
            const item = inventory.container.getItem(i);

            items.push({
                item: item ?? {},
                itemId: item?.typeId ?? "Empty",
                amount: item?.amount ?? 0,
                slot: i,
            });
        }

        return items;
    }
}
