import { world, system, EntityDamageCause, BlockVolume, BlockPermutation, MolangVariableMap } from "@minecraft/server";
import * as attackUtils from "../attackUtils.js";
import * as consts from "../constants.js";

const ACTIVE_PROJECTILES = new Set();

export class CustomProjectile {
    constructor(
        owner,
        visualTypeId = "oct_eg:projectile_template",
        direction = { x: 0, y: 0, z: 0 },
        {
            offset = { x: 0, y: 1.5, z: 0 },
            speed = 1.5, // blocks per tick
            airDrag = 0.00,
            gravity = 0.00,
            travelDistance = 32, // in blocks
            travelTime = 5, // in seconds
            damageType = EntityDamageCause.magic,
            damage = 2,
            damageRange = 2, // in blocks
            destroyOnHit = true,
            destroyOnHitBlock = true,
            maxEntitiesHit = 3,
        } = {}) {
        this.owner = owner;
        this.timeCreated = system.currentTick
        this.startingLocation = attackUtils.VOP.add(owner.location, offset);
        this.direction = direction;
        this.location = { ...this.startingLocation };
        this.velocity = attackUtils.VOP.multiply(direction, { x: speed, y: speed, z: speed });
        this.gravity = gravity;
        this.damage = damage;
        this.travelDistance = travelDistance;
        this.travelTime = travelTime;
        this.damageType = damageType;
        this.damageRange = damageRange;
        this.destroyOnHit = destroyOnHit;
        this.destroyOnHitBlock = destroyOnHitBlock;
        this.airDrag = airDrag;
        this.maxEntitiesHit = maxEntitiesHit;
        this.hit_entities = [];
        this.isDestroyed = false;

        try {
            this.linked_entity = owner.dimension.spawnEntity(visualTypeId, this.location);
            this.onCreation();

        } catch (e) {
            world.sendMessage(`§cError spawning projectile at location §6${this.location}§c:\n§8${e}`);
            this.destroy();
        }
    }

    onCreation() {
        this.tameToPlayer();

        const initRotation = this.owner?.getRotation();
        this.linked_entity?.setProperty("oct_eg:rxinit", parseInt(initRotation.x));
        this.linked_entity?.setProperty("oct_eg:ryinit", parseInt(initRotation.y));
    }

    tameToPlayer() {
        if (!this.linked_entity || !this.linked_entity.isValid()) return false;

        const tameableComp = this.linked_entity?.getComponent("minecraft:tameable");
        if (!tameableComp) return false;

        const isTamed = tameableComp?.tame(this.owner);
        return isTamed;
    }

    shootProjectile() {
        this.isShot = true;
        ACTIVE_PROJECTILES.add(this);
    }

    tick() {
        if (this.isDestroyed) return;

        if (!this.linked_entity || !this.linked_entity.isValid()) return this.destroy();
        if (system.currentTick - this.timeCreated >= this.travelTime * 20) return this.destroy();
        if (attackUtils.VOP.distance(this.location, this.startingLocation) > this.travelDistance) return this.destroy();

        this.updatePosition();

        const hitEntities = this.getEntitiesInRange();
        this.hitsItteration(hitEntities);

        try {
            this.linked_entity.teleport(this.location);
            if (this.isInSolidBlock()) return this.onHitBlock();

        } catch (e) {
            //console.log(`§cError moving projectile at location §6${this.location}§c:\n§8${e}`);
            return this.destroy();
        }


    }

    hitsItteration(hitEntities) {
        for (const entity of hitEntities) {
            if (this.hit_entities.includes(entity.id)) continue;

            this.onHitEntity(entity);

            if (this.destroyOnHit && this.hit_entities.length >= this.maxEntitiesHit) return this.destroy();
        }
    }

    updatePosition() {

        this.velocity.x *= 1 - this.airDrag;
        this.velocity.z *= 1 - this.airDrag;
        this.velocity.y *= 1 - this.airDrag;
        this.velocity.y -= this.gravity;

        this.location = attackUtils.VOP.add(this.location, this.velocity);
    }

    getEntitiesInRange() {
        return attackUtils.getValidTargetsInRange(this.linked_entity, {
            startLocation: this.location,
            maxDistance: this.damageRange,
            excludedEntities: [this.owner],
        });
    }

    onHitEntity(entity) {
        if (this.hit_entities.length >= this.maxEntitiesHit) return;

        attackUtils.applyModifiedDamage(
            entity,
            this.damage,
            {
                cause: this.damageType,
                damagingEntity: this.owner
            }
        );
        //world.sendMessage(`§cHIT ENTITY §6${entity.typeId}`);


        this.hit_entities.push(entity.id);
    }

    onHitBlock() {
        // world.sendMessage("§cHIT BLOCK");
        if (this.destroyOnHitBlock || this.destroyOnHit) {
            return this.destroy();
        }
    }

    isInSolidBlock() {
        // const block = this.linked_entity.dimension?.getBlock(this.location);
        const blockHit = this.linked_entity.dimension?.getBlockFromRay(this.location, this.direction, {
            includeLiquidBlocks: false,
            includePassableBlocks: false,
            maxDistance: 1
        });
        const block = blockHit?.block;
        // world.sendMessage(`§cHIT BLOCK §6${block?.typeId}`);
        return (block && !block.isAir && !block.isLiquid) || (
            this.location.x == this.linked_entity.location.x &&
            this.location.y == this.linked_entity.location.y &&
            this.location.z == this.linked_entity.location.z);
    }

    destroy() {
        if (this.isDestroyed) return;

        //world.sendMessage("§cDESTROYED");
        this.isDestroyed = true;

        if (this.linked_entity && this.linked_entity?.isValid()) {
            this.linked_entity.remove();
        }

        ACTIVE_PROJECTILES.delete?.(this);
    }
}

function tickAllProjectiles() {
    for (const projectile of ACTIVE_PROJECTILES) {
        if (projectile.isShot && !projectile.isDestroyed) projectile.tick();
    }
}

function checkLoadedProjectileIsActive(entity) {
    for (const projectile of ACTIVE_PROJECTILES) {
        if (projectile.linked_entity?.id === entity.id) {
            return true;
        }
    }
    return false;
}

world.afterEvents.entityLoad.subscribe((event) => {
    const entity = event.entity;

    if (entity.getComponent('type_family')?.hasTypeFamily('oct_eg.projectile_entity')) {
        if (!checkLoadedProjectileIsActive(entity)) {
            entity.remove(); // remove the stray entity
        }
    }
});

system.runInterval(() => {
    tickAllProjectiles();
}, 1);


export class AirSlashProjectile extends CustomProjectile {

    constructor(owner, direction, gauntlet) {
        const options = {
            offset: { x: 0, y: 1.4, z: 0 },
            speed: 1.2,
            travelTime: 5,
            damageRange: 2,
            destroyOnHit: false,
            destroyOnHitBlock: true,
            maxEntitiesHit: 99
        }
        super(owner, "oct_eg:air_slash", direction, options);
        this.gauntlet = gauntlet;
        this.hRange = this.gauntlet.upgradeStages.skill1 >= 1 ? 2 : 1;

        this.baseTravelDistance = 16;
        this.travelDistance = this.baseTravelDistance
            + (this.gauntlet.upgradeStages.skill1 >= 1 ? 4 : 0)
            + (this.gauntlet.upgradeStages.skill1 >= 3 ? 4 : 0);

        this.baseDamage = 4;
        this.damage = this.baseDamage
            + (this.gauntlet.upgradeStages.passive >= 2 && (this.gauntlet.inAirLogic || this.gauntlet.disengageInAirActive) ? 2 : 0)
            + (this.gauntlet.upgradeStages.skill1 >= 2 ? 1 : 0);
    }

    getEntitiesInRange() {
        const raySpacing = 0.5;
        const raysCount = Math.floor((this.hRange * 2) / raySpacing) + 1;
        const rays = [];
        const perpendicular = {
            x: -this.direction.z,
            y: 0,
            z: this.direction.x,
        };

        for (let i = 0; i < raysCount; i++) {
            const offset = -this.hRange + i * raySpacing;
            rays.push(
                {
                    location: {
                        x: this.location.x + perpendicular.x * offset,
                        y: this.location.y,
                        z: this.location.z + perpendicular.z * offset,
                    },
                    direction: this.direction
                }
            );
        }
        //world.sendMessage(`rays amount: ${rayLocations.length}`);

        const hitEntities = attackUtils.getValidTargetsFromRays(this.linked_entity, rays,
            {
                maxDistance: 1,
                excludedEntities: [this.owner]
            }
        );

        return hitEntities;
    }

    shootProjectile() {
        super.shootProjectile();
        this.linked_entity.setProperty("oct_eg:size", this.hRange);
    }
}
export class EchoPunchProjectile extends CustomProjectile {
    constructor(owner, direction, gauntlet) {
        const visualTypeId = "oct_eg:echo_punch";

        const travelDistance = 4 + (gauntlet.upgradeStages.primary >= 2 ? 2 : 0);
        const options = {
            gravity: 0,
            // offset: { x: 0, y: 1.3, z: 0 },
            speed: 0.5,
            damage: 7,
            damageRange: 1.5,
            destroyOnHitBlock: false,
            destroyOnHit: false,
            maxEntitiesHit: 99,
            travelDistance: travelDistance,
            travelTime: 10
        }
        super(owner, visualTypeId, direction, options);
        this.gauntlet = gauntlet;
    }

    getEntitiesInRange() {
        const proximityTargets = attackUtils.getValidTargetsInRange(this.linked_entity, {
            startLocation: this.location,
            maxDistance: this.damageRange,
            excludedEntities: [this.owner],
        });

        const rayTargets = attackUtils.getValidTargetsFromRays(this.linked_entity,
            [
                { // Ray 1
                    location: this.location,
                    direction: this.direction
                }
            ],
            {
                maxDistance: this.damageRange,
                excludedEntities: [this.owner]
            }
        );

        // Combine both results uniquely
        const combinedSet = new Set([...proximityTargets, ...rayTargets]);

        // Return as array
        return Array.from(combinedSet);
    }

    onHitEntity(entity) {
        super.onHitEntity(entity);

        const shouldApplySlowness = this.gauntlet.upgradeStages.primary >= 1 ? true : false;
        const shouldApplyWeakness = this.gauntlet.upgradeStages.primary >= 3 ? true : false;

        if (shouldApplySlowness) {
            entity.addEffect("minecraft:slowness", 40, { amplifier: 1, showParticles: true });
        }
        if (shouldApplyWeakness) {
            entity.addEffect("minecraft:weakness", 80, { amplifier: 0, showParticles: true });
        }
    }

    destroy() {
        if (this.isDestroyed) return;

        //world.sendMessage("§cDESTROYED");
        this.isDestroyed = true;

        if (this.linked_entity && this.linked_entity?.isValid()) {
            this.linked_entity.triggerEvent("oct_eg:to_despawn");
        }

        ACTIVE_PROJECTILES.delete?.(this);
    }
}
export class SoulSnipeProjectile extends CustomProjectile {

    constructor(owner, direction, gauntlet, chargeStage = 0) {
        const options = {
            offset: { x: 0, y: 1.1, z: 0 },
            speed: 1.1,
            travelTime: 3,
            damageRange: 1.5,
            destroyOnHit: false,
            destroyOnHitBlock: true,
            maxEntitiesHit: 3
        }
        super(owner, "oct_eg:soul_projectile", direction, options);
        this.gauntlet = gauntlet;

        this.baseTravelDistance = 32;
        this.travelDistance = this.baseTravelDistance;

        this.baseDamage = 10;
        this.damage = this.baseDamage +
            (chargeStage > 0 ? (chargeStage - 1) * 5 : 0);

        this.shouldApplyWithering = this.gauntlet.upgradeStages.skill1 >= 3 ? true : false;
        this.includeUndeadMinions = this.gauntlet.upgradeStages.skill1 >= 2 ? true : false;
    }

    getEntitiesInRange() {
        const ray = {
            location: this.location,
            direction: this.direction
        }
        const rayEntities = attackUtils.getValidTargetsFromRays(this.owner, [ray],
            {
                startLocation: this.location,
                maxDistance: 1,
                excludedEntities: [this],
                includeUndeadMinions: this.includeUndeadMinions
            }
        );
        const rangeEntities = attackUtils.getValidTargetsInRange(this.owner, {
            startLocation: this.location,
            maxDistance: this.damageRange,
            excludedEntities: [this, ...rayEntities],
            includeUndeadMinions: this.includeUndeadMinions
        });
        const hitEntities = [...rayEntities, ...rangeEntities];
        return hitEntities;
    }
    onHitEntity(entity) {
        super.onHitEntity(entity);
        if (entity.typeId === "oct_eg:undead_minion") {
            try { undeadMinionExplode(entity, this.owner, this.shouldApplyWithering); } catch (e) { }
        }
        entity.dimension.spawnParticle("oct_eg:soul_snipe.impact", {
            x: entity.location.x,
            y: this.location.y,
            z: entity.location.z
        });
        entity.dimension.playSound("octovon.eg.gauntlet.necro.soul_snipe.impact", entity.location);
    }
}
function undeadMinionExplode(minion, owner, appliesWithering) {
    const nearbyEntities = attackUtils.getValidTargetsInRange(minion, {
        maxDistance: 4,
        excludedEntities: [owner],
    });
    for (const entity of nearbyEntities) {
        attackUtils.applyModifiedDamage(
            entity,
            20,
            {
                cause: EntityDamageCause.entityExplosion,
                damagingEntity: owner
            }
        );
        if (appliesWithering) {
            entity.addEffect("minecraft:wither", 100, { amplifier: 1, showParticles: true });
        }
    }
    minion.dimension.spawnParticle("oct_eg:soul_snipe.impact_big", {
        x: minion.location.x,
        y: minion.location.y + 1,
        z: minion.location.z
    });
    minion.dimension.playSound("octovon.eg.gauntlet.necro.undead_minion.explode", minion.location);
    minion.runCommand('camerashake add @a[r=5] 0.3 2.0 positional');
    minion.runCommand('camerashake add @a[rm=5,r=10] 0.1 2.0 positional');

    if (!minion.isValid()) return;
    try {
        minion.triggerEvent("oct_eg:to_despawn");
    } catch (e) { }
}

export class IceSpikeProjectile extends CustomProjectile {

    constructor(owner, direction, gauntlet) {
        const options = {
            offset: { x: 0, y: 1.1, z: 0 },
            speed: 1.2,
            travelTime: 5,
            damageRange: 1,
            destroyOnHit: true,
            destroyOnHitBlock: true,
            maxEntitiesHit: 1
        }
        super(owner, "oct_eg:ice_spike_projectile", direction, options);
        this.gauntlet = gauntlet;

        this.baseTravelDistance = 32;
        this.travelDistance = this.baseTravelDistance;

        this.baseImpactRange = 1;
        this.baseDamage = 7;
        this.baseSlownessDuration = 40;

        this.shouldFreezeBlocks = this.gauntlet.upgradeStages.skill1 >= 1 ? true : false;
        this.impactRange = this.baseImpactRange + (this.gauntlet.upgradeStages.skill1 >= 2 ? 1 : 0);
        this.damage = this.baseDamage + (this.gauntlet.upgradeStages.skill1 >= 3 ? 3 : 0);
    }

    isInSolidBlock() {
        const playerInWater = this.owner.isInWater;
        if (!playerInWater) {
            const block = this.linked_entity.dimension?.getBlock(this.location);
            if (block && block.isLiquid) return true;
        }

        return super.isInSolidBlock();
    }

    getEntitiesInRange() {
        const rayEntities = attackUtils.getValidTargetsFromRays(this.owner,
            [
                {
                    location: this.location,
                    direction: this.direction
                }
            ],
            {
                startLocation: this.location,
                maxDistance: 1,
                excludedEntities: [this],
                includeTsunami: true
            }
        );
        const rangeEntities = attackUtils.getValidTargetsInRange(this.owner, {
            startLocation: this.location,
            maxDistance: this.damageRange,
            excludedEntities: [this, ...rayEntities],
            includeTsunami: true
        });
        const hitEntities = [...rayEntities, ...rangeEntities];
        return hitEntities;
    }
    destroy() {
        try {
            const slownessModifier = this.gauntlet.fullSetBonus ? 1.5 : 1.0;

            // AOE effect
            for (const entity of attackUtils.getValidTargetsInRange(this.linked_entity, {
                startLocation: this.location,
                maxDistance: this.impactRange,
                excludedEntities: [this.owner, ...this.hit_entities],
            })) {
                attackUtils.applyModifiedDamage(
                    entity,
                    this.damage,
                    {
                        cause: EntityDamageCause.freezing,
                        damagingEntity: this.owner
                    }
                );
                entity.addEffect("minecraft:slowness",
                    Math.round(this.baseSlownessDuration * slownessModifier),
                    { amplifier: 2, showParticles: true }
                );
            }

            if (this.shouldFreezeBlocks) this.setIceBlocks();

            const molang = new MolangVariableMap();
            molang.setFloat("radius", this.impactRange + 0.2);
            this.linked_entity.dimension.spawnParticle(
                "oct_eg:ice_grow",
                this.location,
                molang
            );
            this.linked_entity.dimension.spawnParticle("oct_eg:ice_spike.impact", this.location);
            this.linked_entity.dimension.playSound("octovon.eg.gauntlet.hydro.ice_spike.impact", this.location);


        } catch (e) { }

        super.destroy();
    }
    setIceBlocks() {
        const volume = new BlockVolume(
            { x: this.location.x - this.impactRange, y: this.location.y - this.impactRange, z: this.location.z - this.impactRange },
            { x: this.location.x + this.impactRange, y: this.location.y + this.impactRange, z: this.location.z + this.impactRange }
        );
        const locations = this.linked_entity.dimension.getBlocks(
            volume,
            { includeTypes: ["minecraft:water"] },
            false,
        );

        function* blockPlacingGenerator(dimension) {
            for (const location of locations.getBlockLocationIterator()) {
                const block = dimension.getBlock(location);
                if (block.permutation.getState('liquid_depth') !== 0) continue;
                //world.sendMessage(`Replacing block ${block.typeId} permutation ${JSON.stringify(block.permutation.getAllStates())}`);
                block.setPermutation(BlockPermutation.resolve("minecraft:frosted_ice"));
                yield;
            }
        }

        system.runJob(blockPlacingGenerator(this.linked_entity.dimension));

    }
    onHitEntity(entity) {
        super.onHitEntity(entity);
        //world.sendMessage(`§cHIT ENTITY §6${entity.typeId}`);
        if (entity.typeId === "oct_eg:tsunami") {
            try {
                const projectile = Array.from(ACTIVE_PROJECTILES).find(p => p.linked_entity_id === entity.id);
                if (projectile && projectile instanceof TsunamiProjectile) projectile.setIceBlocks();
            } catch (e) {
                //world.sendMessage(`§cError with tsunami projectile: ${e}`);
            }
        }
    }
}
export class TsunamiProjectile extends CustomProjectile {

    constructor(owner, direction, gauntlet, isSmallWave = false, inheritedRotation = { x: 0, y: 0 }) {
        const options = {
            offset: { x: 0, y: 0.1, z: 0 },
            speed: 0.3,
            travelTime: 10,
            damageRange: 1,
            destroyOnHit: false,
            destroyOnHitBlock: false,
            maxEntitiesHit: 99
        }
        super(owner, "oct_eg:tsunami", direction, options);
        this.gauntlet = gauntlet;
        this.isSmallWave = isSmallWave;

        this.baseSpeed = 0.3;
        this.speed = this.baseSpeed + (this.gauntlet.upgradeStages.skill2 >= 1 ? 0.2 : 0);
        this.velocity = attackUtils.VOP.multiply(direction, { x: this.speed, y: this.speed, z: this.speed });

        this.baseTravelDistance = 10;
        this.travelDistance = this.baseTravelDistance + (this.gauntlet.upgradeStages.skill2 >= 2 ? 4 : 0);

        this.baseDamage = 10;
        this.damage = (this.baseDamage + (this.gauntlet.upgradeStages.skill2 >= 2 ? 5 : 0)) * (this.isSmallWave ? 0.5 : 1);

        this.baseHRange = 1;
        this.hRange = this.baseHRange + (this.gauntlet.upgradeStages.skill2 >= 1 && !this.isSmallWave ? 1 : 0);
        this.vRange = 3;

        this.linked_entity_id = null;
        this.inheritedRotation = inheritedRotation;
        this.initialRotation = owner?.getRotation();

        this.initializePostConstructor = true;
        this.onCreation();

    }

    onCreation() {
        if (!this.initializePostConstructor) return;
        this.tameToPlayer();

        const initRotation = this.isSmallWave
            ? this.inheritedRotation
            : this.initialRotation;

        this.linked_entity?.setProperty("oct_eg:rxinit", parseInt(initRotation.x));
        this.linked_entity?.setProperty("oct_eg:ryinit", parseInt(initRotation.y));

        if (this.isSmallWave) {
            this.linked_entity?.triggerEvent("oct_eg:small_hitbox");
        } else {
            this.linked_entity?.triggerEvent("oct_eg:big_hitbox");
        }

        // Link entity to projectile
        this.linked_entity_id = this.linked_entity.id;
    }

    updatePosition() {
        const step = { ...this.velocity };
        const nextPos = attackUtils.VOP.add(this.location, step);

        const dim = this.linked_entity.dimension;
        if (!dim) return;

        // Get block at the next horizontal position
        const baseBlockPos = {
            x: nextPos.x,
            y: this.location.y,
            z: nextPos.z
        };

        const baseBlock = dim.getBlock(baseBlockPos);
        if (!baseBlock) return;

        // Check up to 1 block above and below for suitable ground
        const checkOffsets = [0, 1, -1]; // Priority: same level, step up, step down
        let adjustedY = null;

        for (const offset of checkOffsets) {
            let testBlock;
            try {
                testBlock = offset > 0 ? baseBlock.above(offset) : baseBlock.below(-offset);
            } catch {
                // Accessing above/below out of bounds, skip this offset
                continue;
            }
            if (!testBlock) continue;

            let ground;
            try {
                ground = testBlock.below();
            } catch {
                continue;
            }
            if (!ground) continue;

            if ((testBlock.isAir || testBlock.isLiquid) && !ground.isAir) {
                adjustedY = testBlock.location.y;
                break;
            }
        }

        // No walkable surface within range
        if (adjustedY === null || Math.abs(adjustedY - this.location.y) > 1) {
            return this.destroy(); // Too steep to continue
        }

        // Update position to follow terrain
        this.location.y = adjustedY;

        // Apply horizontal movement
        this.velocity.x *= 1 - this.airDrag;
        this.velocity.z *= 1 - this.airDrag;
        this.location = attackUtils.VOP.add(this.location, this.velocity);
    }

    getEntitiesInRange() {
        const raySpacing = 0.25;
        const raysCount = Math.floor((this.hRange * 2) / raySpacing) + 1;
        const rays = [];
        const perpendicular = {
            x: -this.direction.z,
            y: 0,
            z: this.direction.x,
        };

        for (let i = 0; i < raysCount; i++) {
            const offset = -this.hRange + i * raySpacing;
            rays.push(
                {
                    location: {
                        x: this.location.x + perpendicular.x * offset,
                        y: this.location.y,
                        z: this.location.z + perpendicular.z * offset,
                    },
                    direction: {
                        x: 0,
                        y: 1,
                        z: 0
                    }
                }
            );
        }
        //world.sendMessage(`rays amount: ${rayLocations.length}`);

        const hitEntities = attackUtils.getValidTargetsFromRays(this.linked_entity, rays,
            {
                maxDistance: this.vRange,
                excludedEntities: [this.owner]
            }
        );

        return hitEntities;
    }

    hitsItteration(hitEntities) {
        for (const entity of hitEntities) {

            entity.applyKnockback(this.direction.x, this.direction.z, 1.5, 0.2);

            if (this.hit_entities.includes(entity.id)) continue;
            this.onHitEntity(entity);
            if (this.destroyOnHit && this.hit_entities.length >= this.maxEntitiesHit) return this.destroy();
        }
    }

    destroy() {
        if (this.isDestroyed) return;

        //world.sendMessage("§cDESTROYED");
        this.isDestroyed = true;

        if (this.linked_entity && this.linked_entity?.isValid()) {
            this.linked_entity.triggerEvent("oct_eg:to_despawn");
        }

        ACTIVE_PROJECTILES.delete?.(this);

        // this.setIceBlocks();

        if (this.isSmallWave || this.gauntlet.upgradeStages.skill2 < 3) return;
        const proj = new TsunamiProjectile(this.owner, this.direction, this.gauntlet, true, this.initialRotation);
        proj.startingLocation = this.startingLocation;
        proj.location = this.startingLocation;
        proj.shootProjectile();
        this.linked_entity.dimension.playSound(
            "octovon.eg.gauntlet.hydro.tsunami.cast",
            this.startingLocation,
            {
                volume: 0.6
            }
        )

    }

    setIceBlocks() {
        const dim = this.linked_entity?.dimension;
        if (!dim) return;

        const spacing = 1;
        const widthSteps = Math.floor((this.hRange * 2) / spacing) + 1;

        const perpendicular = {
            x: -this.direction.z,
            y: 0,
            z: this.direction.x,
        };

        const targets = [];

        // Generate all block positions for width x height grid
        for (let w = 0; w < widthSteps; w++) {
            const offset = -this.hRange + w * spacing;
            const baseX = this.location.x + perpendicular.x * offset;
            const baseZ = this.location.z + perpendicular.z * offset;

            const baseY = this.location.y;
            for (let h = 0; h < this.vRange; h++) {
                targets.push({
                    x: baseX,
                    y: baseY + h,
                    z: baseZ,
                });
            }
        }

        function* blockPlacingGenerator(locations, dimension, onComplete) {
            for (const loc of locations) {
                const block = dimension.getBlock(loc);
                if (block?.isAir) {
                    block.setPermutation(BlockPermutation.resolve("minecraft:ice"));
                }
                yield;
            }
            onComplete?.();
        }

        system.runJob(blockPlacingGenerator(targets, dim, () => this.destroy()));

        const molang = new MolangVariableMap();
        molang.setFloat("radius", 3.2);
        dim.spawnParticle(
            "oct_eg:ice_grow",
            {
                x: this.location.x,
                y: this.location.y + 0.5,
                z: this.location.z
            },
            molang
        )
        dim.spawnParticle("oct_eg:tsunami.freeze", {
            x: this.location.x + perpendicular.x * 1,
            y: this.location.y + 1.5,
            z: this.location.z + perpendicular.z * 1
        });
        dim.spawnParticle("oct_eg:tsunami.freeze", {
            x: this.location.x + perpendicular.x * -1,
            y: this.location.y + 1.5,
            z: this.location.z + perpendicular.z * -1
        });
        dim.playSound("octovon.eg.gauntlet.hydro.tsunami.freeze", this.location);
    }
}