import { ItemStartUseAfterEvent, ItemStopUseAfterEvent, world, Player, system, ItemDurabilityComponent, EntityEquippableComponent, EquipmentSlot, ItemCooldownComponent, Block } from "@minecraft/server";
import * as EntityUtils from '../../libs/Core/Server/EntityUtils.js';
import { Vector3 } from "../../libs/Core/Math/VectorUtils.js";
import { createMachine, condition, delayedCondition } from '../../libs/Core/Utils/StateMachine.js';
import { setOnSoulFire } from '../server/setOnSoulFire.js';

class SculkCrusher {
    #players = new Map();

    constructor() {
        this.#initEvents();
    }

    #initEvents() {
        world.afterEvents.itemStartUse.subscribe(this.#itemStartUse.bind(this));
        world.afterEvents.itemStopUse.subscribe(this.#itemEndUse.bind(this));
    }

    /**
     * 
     * @param {ItemStartUseAfterEvent} e 
     */
    #itemStartUse(e) {
        if (e.itemStack.typeId !== 'plank_waao:sculk_crusher') return;
        this.#players.set(e.source, Date.now());
        e.source.runCommandAsync('playsound item.plank_waao.sculk_crusher.charge @a ~~~')
    }

    /**
     * 
     * @param {ItemStopUseAfterEvent} e 
     */
    #itemEndUse(e) {
        if (e.itemStack?.typeId !== 'plank_waao:sculk_crusher') return;
        if (this.#players.has(e.source)) {
            const duration = Date.now() - this.#players.get(e.source);
            const item = EntityUtils.getItemInHand(e.source);
            if (!item || item.typeId !== 'plank_waao:sculk_crusher') return;
            this.#startUseAbility(e.source, duration, e.itemStack);
        }
    }

    async #startUseAbility(player, duration, item) {
        let amplifier = Math.max(Math.min(duration / 1000, 1.4), 0.1);
        amplifier = Math.round((amplifier / 1.4) * 100) / 100;

        this.#startCooldown(player, item);
        this.#decrementDurability(player, item, 1);

        const block = await this.#createStateMachine(player, amplifier);
        if (!block) return;

        let visualLocation = block.center();
        visualLocation = { ...visualLocation, y: visualLocation.y + 0.5 };
        this.#playsounds(player, block.typeId, visualLocation);

        this.#useAbility(player, duration, item, visualLocation, amplifier);
    }

    /**
     * Main method to use ability
     * @param {Player} player 
     * @param {number} duration 
     * @param {ItemStack} item
     */
    #useAbility(player, duration, item, visualLocation, amplifier) {
        const dimension = player.dimension;

        // Spawn visual entity
        dimension.spawnEntity('plank_waao:sculk_crusher_visual', visualLocation);
        dimension.spawnParticle('plank_waao:big_sculk_tentacles', visualLocation);

        // Get hit entities
        const hitEntities = this.#getHitEntities(dimension, visualLocation);

        if (!hitEntities) return;

        // Apply damage and knockback
        this.#applyDamageAndKnockback(hitEntities, player, visualLocation, amplifier);
    }

    /**
     * Applies damage and knockback to hit entities
     * @param {Array} hitEntities 
     * @param {Player} player 
     * @param {Object} visualLocation 
     * @param {number} amplifier 
     */
    #applyDamageAndKnockback(hitEntities, player, visualLocation, amplifier) {
        for (let i = 0; i < hitEntities.length; i++) {
            const entity = hitEntities[i];
            if (entity.typeId === 'plank_waao:sculk_jaw' && entity.hasTag('plank_waao:is_tamed')) return;
            if (entity === player || entity.typeId === 'plank_waao:sculk_crusher_visual') continue;
            if (!entity || !entity.isValid()) continue;

            // Apply damage
            const damage = Math.max(1, Math.round(amplifier * 60));
            entity.applyDamage(damage);

            setOnSoulFire.setOnSoulFire(entity, 60);

            // Calculate knockback
            const entityLocation = entity.location;
            const knockbackDirection = {
                x: entityLocation.x - visualLocation.x,
                z: entityLocation.z - visualLocation.z
            };

            const magnitude = Math.sqrt(knockbackDirection.x ** 2 + knockbackDirection.z ** 2);
            const verticalAmplifier = Math.max(0.45, 1 * amplifier);

            if (magnitude > 0) {
                const normalizedDirection = {
                    x: knockbackDirection.x / magnitude,
                    z: knockbackDirection.z / magnitude
                };

                entity.applyKnockback(normalizedDirection.x, normalizedDirection.z, 1.6 * amplifier, verticalAmplifier);
            } else {
                entity.applyKnockback(0, 0, 0, verticalAmplifier);
            }
        }
    }

    /**
     * Retrieves entities within a certain radius from the location
     * @param {Dimension} dimension 
     * @param {Object} location 
     * @param {number} maxDistance 
     * @returns {Array} hitEntities
     */
    #getHitEntities(dimension, location, maxDistance = 3) {
        return dimension.getEntities({
            location: location,
            maxDistance: maxDistance
        });
    }

    /**
     * 
     * @param {Player} player 
     * @param {string} block 
     * @param {Vector3} location
     */
    #playsounds(player, block, location) {
        const dirt = [
            'minecraft:dirt',
            'minecraft:coarse_dirt',
            'minecraft:dirt_with_roots',
            'minecraft:grass_block',
            'minecraft:grass_path',
            'minecraft:podzol',
            'minecraft:farm_land',
            'minecraft:mycelium',
            'minecraft:acacia_leaves',
            'minecraft:azalea_leaves',
            'minecraft:azalea_leaves_flowered',
            'minecraft:cherry_leaves',
            'minecraft:dark_oak_leaves',
            'minecraft:jungle_leaves',
            'minecraft:mangrove_leaves',
            'minecraft:oak_leaves',
            'minecraft:spruce_leaves'
        ];

        const wood = [
            'minecraft:acacia_log',
            'minecraft:birch_log',
            'minecraft:cherry_log',
            'minecraft:dark_oak_log',
            'minecraft:jungle_log',
            'minecraft:mangrove_log',
            'minecraft:oak_log',
            'minecraft:spruce_log',
            'minecraft:stripped_acacia_log',
            'minecraft:stripped_birch_log',
            'minecraft:stripped_cherry_log',
            'minecraft:stripped_dark_oak_log',
            'minecraft:stripped_jungle_log',
            'minecraft:stripped_mangrove_log',
            'minecraft:stripped_oak_log',
            'minecraft:stripped_spruce_log',
            'minecraft:acacia_wood',
            'minecraft:birch_wood',
            'minecraft:cherry_wood',
            'minecraft:dark_oak_wood',
            'minecraft:jungle_wood',
            'minecraft:mangrove_wood',
            'minecraft:oak_wood',
            'minecraft:spruce_wood',
            'minecraft:stripped_acacia_wood',
            'minecraft:stripped_birch_wood',
            'minecraft:stripped_cherry_wood',
            'minecraft:stripped_dark_oak_wood',
            'minecraft:stripped_jungle_wood',
            'minecraft:stripped_mangrove_wood',
            'minecraft:stripped_oak_wood',
            'minecraft:stripped_spruce_wood',
            'minecraft:acacia_planks',
            'minecraft:birch_planks',
            'minecraft:cherry_planks',
            'minecraft:dark_oak_planks',
            'minecraft:jungle_planks',
            'minecraft:mangrove_planks',
            'minecraft:oak_planks',
            'minecraft:spruce_planks',
            'minecraft:warped_planks',
            'minecraft:warped_stem',
            'minecraft:warped_hyphae',
            'minecraft:stripped_warped_stem',
            'minecraft:stripped_warped_hyphae',
            'minecraft:crimson_planks',
            'minecraft:crimson_stem',
            'minecraft:crimson_hyphae',
            'minecraft:stripped_crimson_stem',
            'minecraft:stripped_crimson_hyphae',
        ]

        const gravel = [
            'minecraft:gravel',
            'minecraft:suspicious_gravel',
            'minecraft:sand',
            'minecraft:suspicious_sand',
            'minecraft:red_sand',
            'minecraft:soul_sand'
        ];

        const sculk = [
            'minecraft:sculk',
            'minecraft:catalyst',
            'minecraft:sculk_sensor',
            'minecraft:sculk_shrieker'
        ];

        let sound = 'stone';

        if (dirt.includes(block)) {
            sound = 'dirt';
        } else if (wood.includes(block)) {
            sound = 'wood';
        } else if (gravel.includes(block)) {
            sound = 'gravel';
        } else if (sculk.includes(block)) {
            sound = 'sculk';
        }

        const soundLoc = `${location.x} ${location.y} ${location.z}`;
        player.runCommandAsync(`playsound item.plank_waao.sculk_crusher.hit_${sound} @a ${soundLoc}`)

    }


    /**
     * Decrements the durability of the item in hand
     * @param {Player} player 
     * @param {ItemStack} item 
     * @param {number} amount
     */
    #decrementDurability(player, item, amount) {
        if (player.getGameMode() == 'creative') return;
        /** @type {ItemDurabilityComponent} */
        let durabilityComponent = item.getComponent('durability');
        /** @type {EntityEquippableComponent} */
        const equippableComponent = player.getComponent("equippable");

        if (durabilityComponent.damage + amount >= durabilityComponent.maxDurability) {
            equippableComponent.setEquipment(EquipmentSlot.Mainhand);
            return;
        }
        durabilityComponent.damage = durabilityComponent.damage + amount;
        equippableComponent.setEquipment(EquipmentSlot.Mainhand, item);
    }

    #startCooldown(player, item) {
        /** @type {ItemCooldownComponent} */
        const cooldownComponent = item.getComponent('minecraft:cooldown');
        cooldownComponent.startCooldown(player);
        player
    }

    /**
     * 
     * @param {Player} player 
     * @returns {Promise<Block|undefined>}
     */
    #createStateMachine(player, amplifier) {
        return new Promise((resolve) => {
            const crusherContext = this;
            createMachine({
                initialState: 'default',
                states: {
                    default: {
                        transitions: [
                            {
                                nextState: 'await_landing',
                                condition: delayedCondition(() => true, 2)
                            }
                        ],
                        onEntry: () => {
                            let knockbackDirection = player.getViewDirection();
                            const knockbackAmplifier = amplifier < 0.8 ? 1 : 1.5;
                            // const shake_time = amplifier < 0.8 ? 0.6 : 1.3;
                            // player.runCommand(`camerashake add @s 0.3 ${shake_time} positional`);
                            player.applyKnockback(knockbackDirection.x, knockbackDirection.z, 2 * knockbackAmplifier, 0.5 * knockbackAmplifier);
                        }
                    },
                    await_landing: {
                        transitions: [
                            {
                                nextState: 'land',
                                condition: condition(() => player.isOnGround)
                            }
                        ]
                    },
                    land: {
                        onEntry: () => {
                            const block = crusherContext.#getTargetBlock(player);
                            resolve(block);
                        }
                    }
                }
            });
        });
    }

    /**
     * 
     * @param {Player} player 
     * @returns {Block|undefined}
     */
    #getTargetBlock(player) {
        const startLocation = EntityUtils.getRelativePosition(player, { x: 2, y: 0, z: 0 });
        for (let i = 0; i < 8; i++) {
            const block = player.dimension.getBlock({ ...startLocation, y: startLocation.y - i })
            if (block.isAir || block.isLiquid) continue;
            return block;
        }

        return undefined;
    }
}

export const sculkCrusher = new SculkCrusher();

// world.afterEvents.itemStartUse.subscribe((e) => {
//     if (e.itemStack.typeId !== 'plank_waao:sculk_crusher') return;
//     console.warn('start use')
// })