import {
    ButtonState,
    Entity,
    EntityComponentTypes,
    EntityInventoryComponent,
    EquipmentSlot,
    InputButton,
    Player,
    system,
    world,
} from "@minecraft/server";
import { Vector3 } from "../global_api/Math";

export const ColorPalette = {
    // Red variations
    redLight: { red: 1.0, green: 0.5, blue: 0.5, alpha: 1.0 },
    red: { red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0 },
    redDark: { red: 0.7, green: 0.0, blue: 0.0, alpha: 1.0 },
    // Green variations
    greenLight: { red: 0.5, green: 0.75, blue: 0.5, alpha: 1.0 },
    green: { red: 0.0, green: 0.5, blue: 0.0, alpha: 1.0 },
    greenDark: { red: 0.0, green: 0.35, blue: 0.0, alpha: 1.0 },
    // Blue variations
    blueLight: { red: 0.5, green: 0.5, blue: 1.0, alpha: 1.0 },
    blue: { red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0 },
    blueDark: { red: 0.0, green: 0.0, blue: 0.7, alpha: 1.0 },
    // Yellow variations
    yellowLight: { red: 1.0, green: 1.0, blue: 0.5, alpha: 1.0 },
    yellow: { red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0 },
    yellowDark: { red: 0.7, green: 0.7, blue: 0.0, alpha: 1.0 },
    // Cyan variations
    cyanLight: { red: 0.5, green: 1.0, blue: 1.0, alpha: 1.0 },
    cyan: { red: 0.0, green: 1.0, blue: 1.0, alpha: 1.0 },
    cyanDark: { red: 0.0, green: 0.7, blue: 0.7, alpha: 1.0 },
    // Magenta variations
    magentaLight: { red: 1.0, green: 0.5, blue: 1.0, alpha: 1.0 },
    magenta: { red: 1.0, green: 0.0, blue: 1.0, alpha: 1.0 },
    magentaDark: { red: 0.7, green: 0.0, blue: 0.7, alpha: 1.0 },
    // Orange variations
    orangeLight: { red: 1.0, green: 0.824, blue: 0.5, alpha: 1.0 },
    orange: { red: 1.0, green: 0.647, blue: 0.0, alpha: 1.0 },
    orangeDark: { red: 0.7, green: 0.453, blue: 0.0, alpha: 1.0 },
    // Purple variations
    purpleLight: { red: 0.751, green: 0.25, blue: 0.751, alpha: 1.0 },
    purple: { red: 0.502, green: 0.0, blue: 0.502, alpha: 1.0 },
    purpleDark: { red: 0.351, green: 0.0, blue: 0.351, alpha: 1.0 },
    // Pink variations
    pinkLight: { red: 1.0, green: 0.876, blue: 0.898, alpha: 1.0 },
    pink: { red: 1.0, green: 0.753, blue: 0.796, alpha: 1.0 },
    pinkDark: { red: 0.7, green: 0.527, blue: 0.557, alpha: 1.0 },
    // Brown variations
    brownLight: { red: 0.824, green: 0.333, blue: 0.333, alpha: 1.0 },
    brown: { red: 0.647, green: 0.165, blue: 0.165, alpha: 1.0 },
    brownDark: { red: 0.453, green: 0.116, blue: 0.116, alpha: 1.0 },
    // Gray variations
    grayLight: { red: 0.751, green: 0.751, blue: 0.751, alpha: 1.0 },
    gray: { red: 0.502, green: 0.502, blue: 0.502, alpha: 1.0 },
    grayDark: { red: 0.351, green: 0.351, blue: 0.351, alpha: 1.0 },
    // Teal variations
    tealLight: { red: 0.5, green: 0.8, blue: 0.8, alpha: 1.0 },
    teal: { red: 0.0, green: 0.502, blue: 0.502, alpha: 1.0 },
    tealDark: { red: 0.0, green: 0.351, blue: 0.351, alpha: 1.0 },
    // Lime variations
    limeLight: { red: 0.8, green: 1.0, blue: 0.5, alpha: 1.0 },
    lime: { red: 0.5, green: 1.0, blue: 0.0, alpha: 1.0 },
    limeDark: { red: 0.35, green: 0.7, blue: 0.0, alpha: 1.0 },
    // Indigo variations
    indigoLight: { red: 0.576, green: 0.439, blue: 0.859, alpha: 1.0 },
    indigo: { red: 0.294, green: 0.0, blue: 0.51, alpha: 1.0 },
    indigoDark: { red: 0.2, green: 0.0, blue: 0.35, alpha: 1.0 },
    // Gold variations
    goldLight: { red: 1.0, green: 0.843, blue: 0.0, alpha: 1.0 },
    gold: { red: 0.831, green: 0.686, blue: 0.216, alpha: 1.0 },
    goldDark: { red: 0.58, green: 0.48, blue: 0.15, alpha: 1.0 },
    // Silver variations
    silverLight: { red: 0.9, green: 0.91, blue: 0.98, alpha: 1.0 },
    silver: { red: 0.75, green: 0.752, blue: 0.752, alpha: 1.0 },
    silverDark: { red: 0.55, green: 0.55, blue: 0.55, alpha: 1.0 },
    // Olive variations
    oliveLight: { red: 0.765, green: 0.702, blue: 0.302, alpha: 1.0 },
    olive: { red: 0.5, green: 0.502, blue: 0.0, alpha: 1.0 },
    oliveDark: { red: 0.35, green: 0.35, blue: 0.0, alpha: 1.0 },
    // Coral variations
    coralLight: { red: 1.0, green: 0.6, blue: 0.6, alpha: 1.0 },
    coral: { red: 1.0, green: 0.498, blue: 0.314, alpha: 1.0 },
    coralDark: { red: 0.7, green: 0.35, blue: 0.22, alpha: 1.0 },
    // Slate variations
    slateLight: { red: 0.69, green: 0.768, blue: 0.871, alpha: 1.0 },
    slate: { red: 0.439, green: 0.502, blue: 0.565, alpha: 1.0 },
    slateDark: { red: 0.31, green: 0.35, blue: 0.393, alpha: 1.0 },
    // Turquoise variations
    turquoiseLight: { red: 0.686, green: 0.933, blue: 0.933, alpha: 1.0 },
    turquoise: { red: 0.251, green: 0.878, blue: 0.816, alpha: 1.0 },
    turquoiseDark: { red: 0.18, green: 0.63, blue: 0.576, alpha: 1.0 },
    // Maroon variations
    maroonLight: { red: 0.8, green: 0.267, blue: 0.333, alpha: 1.0 },
    maroon: { red: 0.502, green: 0.0, blue: 0.0, alpha: 1.0 },
    maroonDark: { red: 0.351, green: 0.0, blue: 0.0, alpha: 1.0 },
    // Navy variations
    navyLight: { red: 0.686, green: 0.78, blue: 0.933, alpha: 1.0 },
    navy: { red: 0.0, green: 0.0, blue: 0.502, alpha: 1.0 },
    navyDark: { red: 0.0, green: 0.0, blue: 0.35, alpha: 1.0 },
    // Peach variations
    peachLight: { red: 1.0, green: 0.878, blue: 0.616, alpha: 1.0 },
    peach: { red: 1.0, green: 0.753, blue: 0.529, alpha: 1.0 },
    peachDark: { red: 0.7, green: 0.527, blue: 0.37, alpha: 1.0 },
    // Lavender variations
    lavenderLight: { red: 0.902, green: 0.902, blue: 0.98, alpha: 1.0 },
    lavender: { red: 0.71, green: 0.49, blue: 0.91, alpha: 1.0 },
    lavenderDark: { red: 0.5, green: 0.35, blue: 0.65, alpha: 1.0 },
    // Mint variations
    mintLight: { red: 0.75, green: 1.0, blue: 0.8, alpha: 1.0 },
    mint: { red: 0.498, green: 1.0, blue: 0.831, alpha: 1.0 },
    mintDark: { red: 0.35, green: 0.7, blue: 0.58, alpha: 1.0 },
};

/**
 *
 * @param {Entity} entity
 * @param {Map<string,object>} moves
 */
export function executeRandomizedMove(entity, moves) {
    try {
        const totalWeight = Array.from(moves.values()).reduce((acc, curr) => {
            return acc + curr.weight;
        }, 0);

        let randomNumber = Math.round(Math.random() * totalWeight);

        for (let [name, action] of moves) {
            const { weight, duration, properties, time_down_event, event } = action;

            if (randomNumber <= weight) {
                for (let prop of properties) {
                    entity.setProperty(prop.name, prop.value);
                }

                entity?.triggerEvent(event);

                system.runTimeout(() => {
                    entity?.triggerEvent(time_down_event);
                }, duration * 20);

                return;
            }

            randomNumber -= weight;
        }
    } catch (error) {}
}

/**
 *
 * @param {Entity} entity
 * @param {Entity} target
 */
export function teleportBehindTarget(entity, target) {
    const isTargetLoaded = entity.dimension.getEntities().includes(target);

    if (!isTargetLoaded) return;

    system.runTimeout(() => {
        entity.addEffect("invisibility", 0.45 * 20, {
            amplifier: 1,
            showParticles: false,
        });

        entity.tryTeleport(
            {
                x: target?.location.x - target?.getViewDirection().x * 4,
                y: target?.location.y + 3,
                z: target?.location.z - target?.getViewDirection().z * 4,
            },
            {
                checkForBlocks: true,
            }
        );

        system.runTimeout(() => {
            const location = entity.location;

            const hasFloor = entity.dimension.getBlockFromRay(
                location,
                { x: 0, y: -1, z: 0 },
                {
                    includePassableBlocks: false,
                    includeLiquidBlocks: false,
                }
            );

            if (!hasFloor) {
                entity.tryTeleport(
                    {
                        x: target?.location.x,
                        y: target?.location.y + 3,
                        z: target?.location.z,
                    },
                    {
                        checkForBlocks: true,
                    }
                );

                return;
            }
        }, 1 * 20);
    }, 0.1 * 20);
}

export class NVector3Utils {
    constructor() {}
    static add(a, b) {
        return {
            x: a.x + b.x,
            y: a.y + b.y,
            z: a.z + b.z,
        };
    }
    static subtract(a, b) {
        return {
            x: a.x - b.x,
            y: a.y - b.y,
            z: a.z - b.z,
        };
    }
    static distance(a, b) {
        const dx = a.x - b.x;

        const dy = a.y - b.y;

        const dz = a.z - b.z;

        return Math.hypot(dx, dy, dz);
    }
    static toInteger(vector) {
        return {
            x: Math.round(vector.x),
            y: Math.round(vector.y),
            z: Math.round(vector.z),
        };
    }
}

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);
}

export function getAllStates(block) {
    const allStates = block.permutation.getAllStates();

    return JSON.stringify(allStates);
}

export function getHeldItem(player) {
    const equipment = player.getComponent(EntityComponentTypes.Equippable);

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

        return mainhand;
    }
}

export function consumeItem(player, itemStack) {
    const item = getHeldItem(player);

    if (!item) return;

    const itemCount = item.amount;

    if (itemCount > 1) {
        player.runCommand(
            `replaceitem entity @s slot.weapon.mainhand 0 ${itemStack.typeId} ${Math.max(
                1,
                itemCount - 1
            )}`
        );
    } else {
        player.runCommand(`clear @s ${itemStack.typeId}`);
    }
}

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");

        if (!shootable) return;

        const velocity = {
            x: direction.x * power,
            y: direction.y * power,
            z: direction.z * power,
        };

        if (shootable) {
            shootable.owner = player;

            shootable.gravity = gravity ?? 0.0;

            const soundOptions = {
                location: player.location,
                volume: 1,
                pitch: 1,
            };

            player.playSound(sound ?? "", soundOptions);

            shootable.shoot({
                x: direction.x * power,
                y: direction.y * power,
                z: direction.z * power,
            });
        }
    });

    return projectile;
}

export function isTamed(entity) {
    if (!entity) return false;

    const tameable = entity.getComponent(EntityComponentTypes.Tameable);

    if (!tameable) return false;

    return tameable.isTamed;
}

export function isOwner(entity, player) {
    if (!entity) return false;

    const tameable = entity.getComponent(EntityComponentTypes.Tameable);

    if (!tameable) return false;

    return tameable?.tamedToPlayer?.id === player.id;
}

export function getOwner(entity) {
    if (!entity) return;

    const tameable = entity.getComponent(EntityComponentTypes.Tameable);

    if (!tameable) return;

    const playerId = tameable.tamedToPlayer?.id;

    if (!playerId) return;

    return world.getAllPlayers().find((p) => p.id === playerId);
}

export function entityFlyHandler(entity, options) {
    if (!entity || !options) return;

    const { horizontal_speed, vertical_speed } = options;

    const rideableComponent = entity.getComponent(EntityComponentTypes.Rideable);

    if (!rideableComponent) return;

    const riders = rideableComponent?.getRiders();

    if (riders.length == 0) return;

    const controllingRider = riders[0];

    if (!controllingRider) return;

    const movementVector = controllingRider.inputInfo.getMovementVector();

    const controllingRiderViewDirection = controllingRider.getViewDirection();

    const controllingRiderJumpButtonState = controllingRider.inputInfo.getButtonState(
        InputButton.Jump
    );

    if (controllingRiderJumpButtonState == ButtonState.Pressed) {
        entity.applyImpulse({
            x: movementVector.y * controllingRiderViewDirection.x * horizontal_speed,
            y: Math.max(0.5, controllingRiderViewDirection.y) * vertical_speed,
            z: movementVector.y * controllingRiderViewDirection.z * horizontal_speed,
        });
    }
}

export function knockupEntities(entity, radius, horizontalStrength, verticalStrength) {
    const location = entity.location;

    const tameableComponent = entity.getComponent(EntityComponentTypes.Tameable);

    const owner = tameableComponent.tamedToPlayer;

    const nearbyEntities = entity.dimension.getEntities({
        maxDistance: radius,
        location: location,
        excludeTypes: [],
    });

    for (let nearbyEntity of nearbyEntities) {
        if (nearbyEntity != entity) {
            if (nearbyEntity instanceof Player) {
                if (nearbyEntity != owner)
                    nearbyEntity.applyKnockback(0, 0, horizontalStrength, verticalStrength);
            } else {
                const tameableComponent = nearbyEntity.getComponent(EntityComponentTypes.Tameable);

                const isTamed = tameableComponent?.isTamed || false;

                if (!isTamed) {
                    nearbyEntity.applyImpulse({ x: 0, y: verticalStrength, z: 0 });
                }
            }
        }
    }
}

export function knockbackEntity(source, target, horizontalStrength, verticalStrength) {
    const sourceLocation = source.location;

    const direction = source.getViewDirection();

    if (source && source instanceof Player) {
        if (target instanceof Player) {
            target.applyKnockback(
                sourceLocation.x > target.location.x ? -1 : 1,
                sourceLocation.z > target.location.z ? -1 : 1,
                horizontalStrength,
                verticalStrength
            );
        } else {
            target.applyImpulse({
                x: direction.x * horizontalStrength,
                y: verticalStrength,
                z: direction.z * horizontalStrength,
            });
        }
    } else if (source) {
        if (target instanceof Player) {
            target.applyKnockback(
                sourceLocation.x > target.location.x ? -1 : 1,
                sourceLocation.z > target.location.z ? -1 : 1,
                horizontalStrength,
                verticalStrength
            );
        } else {
            target.applyImpulse({
                x: direction.x * horizontalStrength,
                y: verticalStrength,
                z: direction.z * horizontalStrength,
            });
        }
    }
}

/**
 *
 * @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 {string} armor
 * @param {{single_part:boolean}} options
 */
export function handlePlayerFly(player, armor, options) {
    const { single_part } = options;

    const equippedArmorParts = getArmor(player);

    let equippedArmor = undefined;

    if (single_part) {
        equippedArmor = equippedArmorParts.find((part) => part.includes(armor))?.split(":")[1];
    } else {
        equippedArmor = equippedArmorParts.every((part) => part.includes(armor))
            ? equippedArmorParts[0]?.split(":")[1]
            : undefined;
    }

    if (!equippedArmor) return;

    const equippedArmorName = equippedArmor;

    if (equippedArmorName == armor) {
        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;

        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,
            });
        }
    }
}

/**
 * Spawns multiple entities in a cone in front of the player.
 * @param {Player} player - The player who defines the origin and direction of the cone.
 * @param {string} entityType - The identifier of the entity to spawn (e.g., "minecraft:zombie").
 * @param {number} count - The number of entities to spawn.
 * @param {number} spreadDegrees - The total cone aperture in degrees (e.g., 60).
 * @param {number} distance - The distance from the player at which each entity will appear.
 */
export function spawnConeEntities(player, entityType, count, spreadDegrees, distance) {
    const origin = player.location;
    const view = player.getViewDirection();
    const baseYaw = Math.atan2(view.z, view.x);
    const totalSpreadRad = (spreadDegrees * Math.PI) / 180;
    const step = count > 1 ? totalSpreadRad / (count - 1) : 0;
    const startYaw = baseYaw - totalSpreadRad / 2;

    const entities = [];

    for (let i = 0; i < count; i++) {
        const yaw = startYaw + step * i;
        const dx = Math.cos(yaw);
        const dz = Math.sin(yaw);
        const spawnPos = {
            x: origin.x + dx * distance,
            y: origin.y + 1,
            z: origin.z + dz * distance,
        };
        const entity = player.dimension.spawnEntity(entityType, spawnPos);

        entity.setRotation({ x: player.getRotation().x, y: player.getRotation().y });

        entities.push(entity);
    }

    return entities;
}

/**
 * Spawns projectiles in a cone in front of the source and fires them.
 *
 * @param {Entity} source - The entity that spawns and fires the projectiles.
 * @param {string} projectileType - Identifier of the projectile entity to spawn.
 * @param {number} count - Number of projectiles to spawn.
 * @param {number} spreadAngle - Total spread angle of the cone in degrees.
 * @param {number} power - Velocity multiplier for each projectile.
 * @param {number} gravity - Gravity factor applied to each projectile.
 */
export function shootCone(
    source,
    projectileType,
    count,
    spreadAngle,
    power = 1.5,
    gravity = 0.001
) {
    const projectiles = spawnConeEntities(source, projectileType, count, spreadAngle, 3);

    for (const projectile of projectiles) {
        const projComp = projectile.getComponent("minecraft:projectile");
        if (!projComp) continue;

        projComp.owner = source;
        projComp.gravity = gravity;

        const dir = projectile.getViewDirection();
        projComp.shoot({
            x: dir.x * power,
            y: dir.y * power,
            z: dir.z * power,
        });
    }
}

/**
 *
 * @param {Player} player
 * @param {Entity} portal
 * @returns {Vector3}
 */
export function setPlayerLastEnteredPortal(player, portal) {
    const propertyName = "pixell_bbo:last_entered_portal";

    player.setDynamicProperty(propertyName, portal.location);

    return portal.location;
}

/**
 *
 * @param {Player} player
 * @returns
 */
export function getPlayerLastEnteredPortal(player) {
    const propertyName = "pixell_bbo:last_entered_portal";

    /**
     * @type {Vector3}
     */
    const lastPortalPosition = player.getDynamicProperty(propertyName);

    /**
     * @type {Vector3}
     */
    const spawnPoint = {
        x: world.getDefaultSpawnLocation().x,
        y: world.getDefaultSpawnLocation().y,
        z: world.getDefaultSpawnLocation().z,
    } ?? {
        x: player.getSpawnPoint().x,
        y: player.getSpawnPoint().y,
        z: player.getSpawnPoint().z,
    };

    if (lastPortalPosition) {
        return lastPortalPosition;
    } else {
        return spawnPoint;
    }
}

/**
 *
 * @param {Player} player
 *
 */
export function resetPlayerLastEnteredPortal(player) {
    const propertyName = "pixell_bbo:last_entered_portal";
    player.removeTag("pixell_bbo:teleporting_to_arena");

    player.setDynamicProperty(propertyName);
}

/**
 * @param {Player} player
 * @param {string} itemID
 * @param {number} amount
 */
export function hasItems(player, itemID, amount = 1) {
    const container = player.getComponent(EntityInventoryComponent.componentId).container;

    for (let i = 0; i < container.size; i++) {
        const slot = container.getSlot(i);
        if (slot.hasItem() && slot.typeId == itemID) {
            amount -= slot.amount;
        }
        if (amount <= 0) return true;
    }
    return false;
}
