import { EquipmentSlot, ItemStack, system, world, } from "@minecraft/server";
import { addItemToInventory, decreaseItemAmountInHand, getItemInSlot, setItemInSlot, } from "../modules/helper/utils";
import { namespace } from "../config/_config";
import ScriptingModule from "./scriptingModule";
import { registerCookingPots } from "../config/cookingPot";
export class CookingPotBehavior {
    /**
     * Registers a cooking pot instance to be managed by the system.
     *
     * @param cookingPot The CookingPot instance to register.
     */
    static register(config) {
        const newPot = new CookingPot(config);
        this.cookingPots.push(newPot);
    }
    /**
     * Initializes the cooking pot system and subscribes to relevant world events.
     */
    static onInitialize() {
        world.beforeEvents.worldInitialize.subscribe((event) => {
            event.blockComponentRegistry.registerCustomComponent(`${namespace}:cooking_pot`, {
                onTick: this.handleTick.bind(this),
                onPlayerInteract: this.handleInteract.bind(this),
                onPlayerDestroy: this.handleBreak.bind(this), // Handle block break
            });
        });
        system.runInterval(() => {
            world.getPlayers().forEach(this.handleHoverParticle.bind(this));
        }, 20);
    }
    /**
     * Handles the block tick event to manage cooking pot state transitions and effects.
     *
     * @param event The block tick event.
     */
    static handleTick(event) {
        const cookingPot = this.findCookingPot(event.block);
        if (!cookingPot)
            return;
        const currentState = this.getCurrentState(event.block);
        if (!currentState)
            return;
        const blockKey = this.getBlockKey(event.block);
        let remainingDuration = this.stateDurations.get(blockKey);
        // Check if onlyOnBlock is defined and matches
        if (cookingPot.onlyOnBlock) {
            const { block: requiredBlock, state: requiredState } = cookingPot.onlyOnBlock;
            // If the block type or state does not match, pause the cooking and clear effects
            if (event.block.below()?.typeId !== requiredBlock ||
                !this.blockMatchesState(event.block.below(), requiredState)) {
                this.clearActiveEffects(blockKey); // Clear active interval effects
                return; // Exit without progressing cooking or triggering effects
            }
        }
        // Initialize duration if undefined
        if (remainingDuration === undefined) {
            this.stateDurations.set(blockKey, currentState.duration * 20);
            remainingDuration = currentState.duration * 20;
        }
        // Process cooking if duration remains
        if (remainingDuration > 0) {
            remainingDuration -= 1; // Decrement by 1 tick
            this.stateDurations.set(blockKey, remainingDuration);
            // Trigger effects while cooking
            this.processEffects(event.block, currentState);
        }
        else if (remainingDuration === 0) {
            // Transition to the next state if cooking is complete
            if (currentState.stateOnComplete) {
                this.initializeState(event.block, currentState.stateOnComplete);
            }
        }
    }
    static blockMatchesState(block, state) {
        const blockState = block.permutation.getAllStates();
        return blockState[state.state] === state.value;
    }
    /**
     * Handles the player interaction event to process state transitions and effects.
     *
     * @param event The player interaction event.
     */
    static handleInteract(event) {
        const cookingPot = this.findCookingPot(event.block);
        if (cookingPot) {
            this.processInteract(event, event.player);
        }
    }
    /**
     * Handles the block destruction event to clear active effects and state durations.
     *
     * @param event The block destruction event.
     */
    static handleBreak(event) {
        const blockKey = this.getBlockKey(event.block);
        this.clearActiveEffects(blockKey);
        this.stateDurations.delete(blockKey);
    }
    /**
     * Finds the cooking pot instance associated with a given block.
     *
     * @param block The block to find the cooking pot for.
     * @returns The CookingPot instance, if found.
     */
    static findCookingPot(block) {
        return this.cookingPots.find((pot) => block.typeId === pot.baseIdentifier || block.typeId.startsWith(`${pot.baseIdentifier}_`));
    }
    /**
     * Gets the block identifier based on the base identifier and state identifier.
     *
     * @param baseIdentifier The base identifier of the cooking pot.
     * @param stateIdentifier The state identifier.
     * @returns The combined block identifier.
     */
    static getBlockId(baseIdentifier, stateIdentifier) {
        const baseParts = baseIdentifier.split(":");
        const stateParts = stateIdentifier.split(":");
        return `${baseParts[0]}:${baseParts[1]}_${stateParts[1]}`;
    }
    /**
     * Gets the current state of a given block.
     *
     * @param block The block to get the current state for.
     * @returns The current CookingPotState, if found.
     */
    static getCurrentState(block) {
        const cookingPot = this.findCookingPot(block);
        if (!cookingPot)
            return undefined;
        const allStates = block.permutation.getAllStates();
        for (const stateKey in allStates) {
            const stateValue = allStates[stateKey];
            const state = cookingPot.states.find((s) => s.state.identifier === stateKey && s.state.value === stateValue);
            if (state)
                return state;
        }
        return undefined;
    }
    /**
     * Processes player interaction with the cooking pot, handling state transitions and effects.
     *
     * @param event The player interaction event.
     */
    static processInteract(event, player) {
        const blockKey = this.getBlockKey(event.block);
        const currentState = this.getCurrentState(event.block);
        if (!currentState)
            return;
        const remainingDuration = this.stateDurations.get(blockKey);
        if (remainingDuration !== undefined && remainingDuration > 0) {
            // Notify player if interaction is attempted during cooldown
            player.sendMessage({
                translate: this.findCookingPot(event.block)?.progressMessage,
            });
            return; // Block interaction until duration expires
        }
        if (currentState.finalState) {
            this.handleFinalState(event, currentState.finalState);
            return;
        }
        const item = getItemInSlot(event.player, EquipmentSlot.Mainhand);
        if (!item)
            return;
        const nextState = currentState.nextStates?.find((ns) => ns.item === item.typeId);
        if (nextState) {
            this.initializeState(event.block, nextState.state);
            if (nextState.removeItem)
                decreaseItemAmountInHand(event.player, 1, true);
            const newItem = nextState.transformItem ? new ItemStack(nextState.transformItem) : undefined;
            if (newItem) {
                const hasMainHandItem = getItemInSlot(event.player, EquipmentSlot.Mainhand);
                if (hasMainHandItem) {
                    addItemToInventory(event.player, newItem);
                }
                else {
                    setItemInSlot(event.player, EquipmentSlot.Mainhand, newItem);
                }
            }
            // Play the interact sound if defined
            if (currentState.interactSound) {
                try {
                    event.block.dimension.playSound(currentState.interactSound, this.getCenterLocation(event.block));
                }
                catch (error) { }
            }
        }
    }
    /**
     * Handles the final state of the cooking pot, processing item transformations and block state changes.
     *
     * @param event The player interaction event.
     * @param finalState The final state to transition to.
     */
    static handleFinalState(event, finalState) {
        const item = getItemInSlot(event.player, EquipmentSlot.Mainhand);
        // Check if finalState.item is "hand" or allow any item in the player's hand
        if (finalState.item === "hand" || // Allow any item if "hand" is specified
            (item && item.typeId === finalState.item) // Handle specific Minecraft item cases
        ) {
            // Process item removal if required
            if (finalState.removeItem && item) {
                decreaseItemAmountInHand(event.player, 1, true);
            }
            // Handle item transformation
            if (finalState.transformItem) {
                const newItem = new ItemStack(finalState.transformItem);
                if (newItem) {
                    const hasMainHandItem = getItemInSlot(event.player, EquipmentSlot.Mainhand);
                    if (hasMainHandItem) {
                        addItemToInventory(event.player, newItem);
                    }
                    else {
                        setItemInSlot(event.player, EquipmentSlot.Mainhand, newItem);
                    }
                }
            }
            // Update the block's fill state
            const fillLevel = event.block.permutation.getState(`${namespace}:fill_state`);
            event.block.setPermutation(event.block.permutation.withState(`${namespace}:fill_state`, fillLevel - 1));
            // Play interaction sound, if defined
            const cookingPot = this.findCookingPot(event.block);
            if (cookingPot && cookingPot.states) {
                const currentState = this.getCurrentState(event.block);
                if (currentState?.interactSound) {
                    try {
                        event.block.dimension.playSound(currentState.interactSound, this.getCenterLocation(event.block));
                    }
                    catch (error) { }
                }
            }
            // Transition the block to the new state if needed
            if (fillLevel - 1 === 0 && finalState.state) {
                const newBlockId = this.getBlockId(this.findCookingPot(event.block).baseIdentifier, finalState.state.identifier);
                // Preserve existing block permutations like direction
                const direction = event.block.permutation.getState("minecraft:cardinal_direction");
                // Set the new block type and apply permutations
                event.block.dimension.setBlockType(event.block.location, newBlockId);
                const newBlock = event.block.dimension.getBlock(event.block.location);
                newBlock.setPermutation(newBlock.permutation
                    .withState(finalState.state.identifier, finalState.state.value)
                    .withState("minecraft:cardinal_direction", direction));
            }
        }
    }
    /**
     * Processes effects for the current state of the cooking pot.
     *
     * @param block The block to process effects for.
     * @param state The current state of the cooking pot.
     */
    static processEffects(block, state) {
        const blockKey = this.getBlockKey(block);
        let remainingDuration = this.stateDurations.get(blockKey);
        if (remainingDuration === undefined || remainingDuration <= 0)
            return;
        // Trigger visual and sound effects at the start
        if (remainingDuration === state.duration * 20) {
            this.triggerEffects(block, state.visualEffects?.onStart);
        }
        remainingDuration = Math.max(0, remainingDuration - 1);
        this.stateDurations.set(blockKey, remainingDuration);
        this.handleIdleEffects(block, state);
        // Transition to stateOnComplete when duration expires
        if (remainingDuration === 0) {
            this.triggerEffects(block, state.visualEffects?.onEnd);
            this.clearActiveEffects(blockKey);
            if (state.hoverParticle) {
                try {
                    block.dimension.spawnParticle(state.hoverParticle, this.getCenterLocation(block));
                }
                catch (error) { }
            }
            // Automatically transition to stateOnComplete if defined
            if (state.stateOnComplete) {
                this.initializeState(block, state.stateOnComplete);
            }
        }
    }
    /**
     * Handles idle effects for the current state of the cooking pot.
     *
     * @param block The block to handle idle effects for.
     * @param state The current state of the cooking pot.
     */
    static handleIdleEffects(block, state) {
        const blockKey = this.getBlockKey(block);
        if (this.activeEffects.has(blockKey))
            return;
        const intervals = [];
        state.visualEffects?.onInterval?.particles?.forEach((effect) => {
            intervals.push(system.runInterval(() => {
                try {
                    block.dimension.spawnParticle(effect.id, this.getCenterLocation(block));
                }
                catch (error) { }
            }, effect.interval));
        });
        state.visualEffects?.onInterval?.sounds?.forEach((effect) => {
            intervals.push(system.runInterval(() => {
                try {
                    block.dimension.playSound(effect.id, this.getCenterLocation(block));
                }
                catch (error) { }
            }, effect.interval));
        });
        if (intervals.length > 0) {
            this.activeEffects.set(blockKey, intervals);
        }
    }
    /**
     * Triggers specified visual and sound effects for the cooking pot.
     *
     * @param block The block to trigger effects for.
     * @param effects The effects to trigger.
     */
    static triggerEffects(block, effects) {
        if (!effects)
            return;
        effects.particles?.forEach((particle) => {
            try {
                block.dimension.spawnParticle(particle, this.getCenterLocation(block));
            }
            catch (error) { }
        });
        effects.sounds?.forEach((sound) => {
            try {
                block.dimension.playSound(sound, this.getCenterLocation(block));
            }
            catch (error) { }
        });
    }
    /**
     * Initializes a new state for the cooking pot.
     *
     * @param block The block to initialize the state for.
     * @param state The state to initialize.
     */
    static initializeState(block, state) {
        const cookingPot = this.findCookingPot(block);
        if (!cookingPot)
            return;
        const newBlockId = this.getBlockId(cookingPot.baseIdentifier, state.identifier);
        const direction = block.permutation.getState("minecraft:cardinal_direction");
        if (block.typeId !== newBlockId) {
            block.dimension.setBlockType(block.location, newBlockId);
            block = block.dimension.getBlock(block.location);
        }
        block.setPermutation(block.permutation
            .withState(state.identifier, state.value)
            .withState("minecraft:cardinal_direction", direction));
        const newState = cookingPot.states.find((s) => s.state.identifier === state.identifier && s.state.value === state.value);
        if (newState) {
            const blockKey = this.getBlockKey(block);
            this.stateDurations.set(blockKey, newState.duration * 20); // Reset duration for new state
            this.clearActiveEffects(blockKey);
        }
    }
    /**
     * Clears active visual and sound effects for a given block key.
     *
     * @param blockKey The key of the block to clear effects for.
     */
    static clearActiveEffects(blockKey) {
        const intervals = this.activeEffects.get(blockKey);
        if (intervals) {
            intervals.forEach((interval) => system.clearRun(interval)); // Stop all active intervals
            this.activeEffects.delete(blockKey); // Remove the key from active effects map
        }
    }
    /**
     * Handles hover particle effects for players looking at the cooking pot.
     *
     * @param player The player to handle hover particle effects for.
     */
    static handleHoverParticle(player) {
        const block = player.getBlockFromViewDirection({ maxDistance: 5 })?.block;
        if (!block)
            return;
        const currentState = this.getCurrentState(block);
        if (currentState?.hoverParticle &&
            (this.stateDurations.get(this.getBlockKey(block)) ?? 0) <= 0) {
            try {
                block.dimension.spawnParticle(currentState.hoverParticle, this.getCenterLocation(block));
            }
            catch (error) { }
        }
    }
    /**
     * Gets the center location of a given block.
     *
     * @param block The block to get the center location for.
     * @returns The center location of the block.
     */
    static getCenterLocation(block) {
        return {
            x: block.location.x + 0.5,
            y: block.location.y,
            z: block.location.z + 0.5,
        };
    }
    /**
     * Gets the key of a given block based on its location.
     *
     * @param block The block to get the key for.
     * @returns The key of the block.
     */
    static getBlockKey(block) {
        const { x, y, z } = block.location;
        return `${x},${y},${z}`;
    }
}
CookingPotBehavior.cookingPots = [];
CookingPotBehavior.activeEffects = new Map();
CookingPotBehavior.stateDurations = new Map();
/**
 * CookingPot Class Definition
 *
 * This is the original CookingPot class being instantiated within the CookingPotBehavior.register method.
 */
export class CookingPot {
    constructor({ baseIdentifier, progressMessage, states, onlyOnBlock, }) {
        this.baseIdentifier = baseIdentifier;
        this.progressMessage = progressMessage;
        this.states = states;
        this.onlyOnBlock = onlyOnBlock;
    }
}
export default class CookingPotModule extends ScriptingModule {
    constructor() {
        super("CookingPotModule");
    }
    onInitialize() {
        registerCookingPots();
    }
}
