package com.tiedup.remake.network.base;

import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.tasks.PlayerStateTask;
import java.util.function.BiConsumer;
import java.util.function.Function;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;

/**
 *
 * Eliminates code duplication between PacketTying and PacketUntying.
 * Both packets share identical structure for encoding/decoding/handling,
 * differing only in which client task they update.
 *
 * Subclasses only need to implement:
 * - decode() - static method returning the concrete type
 * - getTaskGetter() - returns the function to get the task from PlayerBindState
 * - getTaskSetter() - returns the function to set the task on PlayerBindState
 * - calculateElapsed() - how to calculate elapsed time from stateInfo
 */
public abstract class AbstractProgressPacket extends AbstractClientPacket {

    protected final int stateInfo; // Current time value (-1 = done/cancelled)
    protected final int maxState; // Total duration

    /**
     * Create a new progress packet.
     *
     * @param stateInfo Current time value (-1 for completion/cancel)
     * @param maxState Total duration in seconds
     */
    protected AbstractProgressPacket(int stateInfo, int maxState) {
        this.stateInfo = stateInfo;
        this.maxState = maxState;
    }

    /**
     * Get the current state info value.
     */
    public int getStateInfo() {
        return stateInfo;
    }

    /**
     * Get the max state value.
     */
    public int getMaxState() {
        return maxState;
    }

    /**
     * Encode the packet to the network buffer.
     *
     * @param buf The buffer to write to
     */
    public void encode(FriendlyByteBuf buf) {
        buf.writeInt(stateInfo);
        buf.writeInt(maxState);
    }

    /**
     * Static helper to decode common fields from buffer.
     * Subclasses should call this in their static decode() method.
     *
     * @param buf The buffer to read from
     * @return Array of [stateInfo, maxState]
     */
    protected static int[] decodeFields(FriendlyByteBuf buf) {
        int stateInfo = buf.readInt();
        int maxState = buf.readInt();
        return new int[] { stateInfo, maxState };
    }

    /**
     * Client-side packet handling from AbstractClientPacket.
     * This method is stripped from dedicated server builds.
     * Contains references to client-only classes (Minecraft, LocalPlayer).
     */
    @Override
    @OnlyIn(Dist.CLIENT)
    protected void handleClientImpl() {
        net.minecraft.client.player.LocalPlayer player =
            net.minecraft.client.Minecraft.getInstance().player;
        if (player == null) {
            return;
        }

        PlayerBindState playerState = PlayerBindState.getInstance(player);
        if (playerState == null) {
            return;
        }

        handleProgressUpdate(playerState);
    }

    /**
     * Implementation of progress update logic.
     * Subclasses can override to customize task creation (e.g., with role info).
     *
     * @param playerState The player's bind state
     */
    @OnlyIn(Dist.CLIENT)
    protected void handleProgressUpdate(PlayerBindState playerState) {
        if (stateInfo == -1) {
            // Task completed or cancelled - clear client-side state
            getTaskSetter().accept(playerState, null);
        } else {
            // Update or create client-side task
            PlayerStateTask task = getTaskGetter().apply(playerState);

            if (task == null || task.isOutdated()) {
                // Create new task state
                task = new PlayerStateTask(maxState);
                getTaskSetter().accept(playerState, task);
            }

            // Update progress
            task.update(calculateElapsed());
        }
    }

    /**
     * Get the function to retrieve the task from PlayerBindState.
     * Subclasses must implement this.
     *
     * @return Function that gets the task from PlayerBindState
     */
    protected abstract Function<
        PlayerBindState,
        PlayerStateTask
    > getTaskGetter();

    /**
     * Get the function to set the task on PlayerBindState.
     * Subclasses must implement this.
     *
     * @return BiConsumer that sets the task on PlayerBindState
     */
    protected abstract BiConsumer<
        PlayerBindState,
        PlayerStateTask
    > getTaskSetter();

    /**
     * Calculate the elapsed time value to pass to task.update().
     * Default implementation returns stateInfo directly.
     * Subclasses can override for different calculation (e.g., untying uses maxState - stateInfo).
     *
     * @return The elapsed time value
     */
    protected int calculateElapsed() {
        return stateInfo;
    }
}
