package com.tiedup.remake.client.model;

import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.logging.LogUtils;
import com.tiedup.remake.client.animation.StaticPoseApplier;
import com.tiedup.remake.client.animation.util.DogPoseHelper;
import com.tiedup.remake.entities.AbstractTiedUpNpc;
import com.tiedup.remake.entities.EntityKidnapperArcher;
import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.entities.ai.master.MasterState;
import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.items.clothes.GenericClothes;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import dev.kosmx.playerAnim.core.impl.AnimationProcessor;
import dev.kosmx.playerAnim.core.util.SetableSupplier;
import dev.kosmx.playerAnim.impl.Helper;
import dev.kosmx.playerAnim.impl.IMutableModel;
import dev.kosmx.playerAnim.impl.IUpperPartHelper;
import dev.kosmx.playerAnim.impl.animation.AnimationApplier;
import dev.kosmx.playerAnim.impl.animation.IBendHelper;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.PlayerModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.core.Direction;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.slf4j.Logger;

/**
 * Model for AbstractTiedUpNpc - Humanoid female NPC.
 *
 *
 * Features:
 * - Extends PlayerModel for player-like rendering with outer layers
 * - Supports both normal (4px) and slim (3px) arm widths
 * - Modifies animations based on bondage state
 * - Has jacket, sleeves, pants layers like player skins
 *
 * Uses vanilla ModelLayers.PLAYER / PLAYER_SLIM for geometry.
 */
@OnlyIn(Dist.CLIENT)
public class DamselModel
    extends PlayerModel<AbstractTiedUpNpc>
    implements IMutableModel
{

    private static final Logger LOGGER = LogUtils.getLogger();
    private static boolean loggedBendyStatus = false;

    /** Track if bendy-lib has been initialized for this model */
    private boolean bendyLibInitialized = false;

    /** Emote supplier for bending support - required by IMutableModel */
    private final SetableSupplier<AnimationProcessor> emoteSupplier =
        new SetableSupplier<>();

    /**
     * Create model from baked model part.
     *
     * @param root The root model part (baked from vanilla PLAYER layer)
     * @param slim Whether this is a slim (Alex) arms model
     */
    public DamselModel(ModelPart root, boolean slim) {
        super(root, slim);
        initBendyLib(root);
    }

    /**
     * Initialize bendy-lib bend points on model parts.
     *
     * <p>This enables visual bending of knees and elbows when animations
     * specify bend values. Without this initialization, bend values in
     * animation JSON files have no visual effect.
     *
     * <p>Also marks upper parts (head, arms, hat) for proper bend rendering.
     *
     * @param root The root model part
     */
    private void initBendyLib(ModelPart root) {
        if (bendyLibInitialized) {
            return;
        }

        try {
            // Check if bendy-lib is available
            if (IBendHelper.INSTANCE == null) {
                if (!loggedBendyStatus) {
                    LOGGER.warn(
                        "[DamselModel] IBendHelper.INSTANCE is null - bendy-lib not available"
                    );
                    loggedBendyStatus = true;
                }
                return;
            }

            // Log bendy-lib status
            if (!loggedBendyStatus) {
                LOGGER.info(
                    "[DamselModel] IBendHelper.INSTANCE class: {}",
                    IBendHelper.INSTANCE.getClass().getName()
                );
                LOGGER.info(
                    "[DamselModel] Helper.isBendEnabled(): {}",
                    Helper.isBendEnabled()
                );
                loggedBendyStatus = true;
            }

            // Initialize bend points for each body part
            // Direction indicates which end of the limb bends
            IBendHelper.INSTANCE.initBend(
                root.getChild("body"),
                Direction.DOWN
            );
            IBendHelper.INSTANCE.initBend(
                root.getChild("right_arm"),
                Direction.UP
            );
            IBendHelper.INSTANCE.initBend(
                root.getChild("left_arm"),
                Direction.UP
            );
            IBendHelper.INSTANCE.initBend(
                root.getChild("right_leg"),
                Direction.UP
            );
            IBendHelper.INSTANCE.initBend(
                root.getChild("left_leg"),
                Direction.UP
            );

            // Mark upper parts for proper bend rendering
            // These parts will be rendered after applying body bend rotation
            ((IUpperPartHelper) (Object) this.rightArm).setUpperPart(true);
            ((IUpperPartHelper) (Object) this.leftArm).setUpperPart(true);
            ((IUpperPartHelper) (Object) this.head).setUpperPart(true);
            ((IUpperPartHelper) (Object) this.hat).setUpperPart(true);

            LOGGER.info("[DamselModel] bendy-lib initialized successfully");
            bendyLibInitialized = true;
        } catch (Exception e) {
            LOGGER.error("[DamselModel] bendy-lib initialization failed", e);
            // bendy-lib not available or initialization failed
            // Animations will still work, just without visual bending
        }
    }

    // IMutableModel Implementation

    @Override
    public void setEmoteSupplier(SetableSupplier<AnimationProcessor> supplier) {
        if (supplier != null && supplier.get() != null) {
            this.emoteSupplier.set(supplier.get());
        }
    }

    @Override
    public SetableSupplier<AnimationProcessor> getEmoteSupplier() {
        return this.emoteSupplier;
    }

    /**
     * Setup animations for the damsel.
     *
     * Modifies arm and leg positions based on bondage state:
     * - Tied up: Arms behind back, legs frozen (or variant pose based on bind type)
     * - Free: Normal humanoid animations
     *
     *
     * @param entity AbstractTiedUpNpc instance
     * @param limbSwing Limb swing animation value
     * @param limbSwingAmount Limb swing amount
     * @param ageInTicks Age in ticks for idle animations
     * @param netHeadYaw Head yaw rotation
     * @param headPitch Head pitch rotation
     */
    @Override
    public void setupAnim(
        AbstractTiedUpNpc entity,
        float limbSwing,
        float limbSwingAmount,
        float ageInTicks,
        float netHeadYaw,
        float headPitch
    ) {
        // Only show bow animation when in ranged mode (has active shooting target)
        if (entity instanceof EntityKidnapperArcher archer) {
            if (archer.isInRangedMode()) {
                // In ranged mode: show bow animation
                // isAiming() indicates full draw, otherwise ready position
                this.rightArmPose = HumanoidModel.ArmPose.BOW_AND_ARROW;
                this.leftArmPose = HumanoidModel.ArmPose.BOW_AND_ARROW;
            } else {
                // Not in ranged mode: reset to normal poses (no bow animation)
                this.rightArmPose = HumanoidModel.ArmPose.EMPTY;
                this.leftArmPose = HumanoidModel.ArmPose.EMPTY;
            }
        }

        // Call parent to setup base humanoid animations
        super.setupAnim(
            entity,
            limbSwing,
            limbSwingAmount,
            ageInTicks,
            netHeadYaw,
            headPitch
        );

        // Reset all visibility (may have been hidden in previous frame)
        // Arms
        this.leftArm.visible = true;
        this.rightArm.visible = true;
        // Outer layers
        this.hat.visible = true;
        this.jacket.visible = true;
        this.leftSleeve.visible = true;
        this.rightSleeve.visible = true;
        this.leftPants.visible = true;
        this.rightPants.visible = true;

        // Animation triggering is handled by NpcAnimationTickHandler (tick-based).
        // This method only applies transforms, static pose fallback, and layer syncing.
        boolean inPose =
            entity.isTiedUp() || entity.isSitting() || entity.isKneeling();

        if (inPose) {
            ItemStack bind = entity.getEquipment(BodyRegionV2.ARMS);
            PoseType poseType = PoseTypeHelper.getPoseType(bind);

            // Hide arms for wrap/latex_sack poses
            if (poseType == PoseType.WRAP || poseType == PoseType.LATEX_SACK) {
                this.leftArm.visible = false;
                this.rightArm.visible = false;
            }
        }

        // Apply animation transforms via PlayerAnimator's emote.updatePart()
        // AbstractTiedUpNpc implements IAnimatedPlayer, so we can call directly
        AnimationApplier emote = entity.playerAnimator_getAnimation();
        boolean emoteActive = emote != null && emote.isActive();

        // Track current pose type for DOG pose compensation
        PoseType currentPoseType = PoseType.STANDARD;
        if (inPose) {
            ItemStack bindForPoseType = entity.getEquipment(BodyRegionV2.ARMS);
            currentPoseType = PoseTypeHelper.getPoseType(bindForPoseType);
        }

        // Check if this is a Master in human chair mode (head should look around freely)
        boolean isMasterChairAnim =
            entity instanceof EntityMaster masterEnt &&
            masterEnt.getMasterState() == MasterState.HUMAN_CHAIR &&
            masterEnt.isSitting();

        if (emoteActive) {
            // Animation is active - apply transforms via AnimationApplier
            this.emoteSupplier.set(emote);

            // Apply transforms to each body part
            // Skip head for master chair animation — let vanilla look-at control the head
            if (!isMasterChairAnim) {
                emote.updatePart("head", this.head);
            }
            emote.updatePart("torso", this.body);
            emote.updatePart("leftArm", this.leftArm);
            emote.updatePart("rightArm", this.rightArm);
            emote.updatePart("leftLeg", this.leftLeg);
            emote.updatePart("rightLeg", this.rightLeg);

            // DOG pose: PoseStack handles body rotation in setupRotations()
            // Reset body.xRot to prevent double rotation from animation
            // Apply head compensation so head looks forward instead of at ground
            if (currentPoseType == PoseType.DOG) {
                // Reset body rotation (PoseStack already rotates everything)
                this.body.xRot = 0;

                // Head compensation: body is horizontal via PoseStack
                // Head needs to look forward instead of at the ground
                DogPoseHelper.applyHeadCompensation(
                    this.head,
                    null, // hat is synced via copyFrom below
                    headPitch,
                    netHeadYaw
                );
            }

            // Sync outer layers to their parents
            this.hat.copyFrom(this.head);
            this.jacket.copyFrom(this.body);
            this.leftSleeve.copyFrom(this.leftArm);
            this.rightSleeve.copyFrom(this.rightArm);
            this.leftPants.copyFrom(this.leftLeg);
            this.rightPants.copyFrom(this.rightLeg);
        } else if (inPose) {
            // Animation not yet active (1-frame delay) - apply static pose as fallback
            // This ensures immediate visual feedback when bind is applied
            ItemStack bind = entity.getEquipment(BodyRegionV2.ARMS);
            PoseType fallbackPoseType = PoseTypeHelper.getPoseType(bind);

            // Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder)
            boolean armsBound = V2EquipmentHelper.isRegionOccupied(
                entity,
                BodyRegionV2.ARMS
            );
            boolean legsBound = V2EquipmentHelper.isRegionOccupied(
                entity,
                BodyRegionV2.LEGS
            );

            if (
                !armsBound && !legsBound && BindModeHelper.isBindItem(bind)
            ) {
                armsBound = BindModeHelper.hasArmsBound(bind);
                legsBound = BindModeHelper.hasLegsBound(bind);
            }

            // Apply static pose directly to model parts
            StaticPoseApplier.applyStaticPose(
                this,
                fallbackPoseType,
                armsBound,
                legsBound
            );

            // DOG pose: Apply head compensation for horizontal body (same as emote case)
            if (fallbackPoseType == PoseType.DOG) {
                // Reset body rotation (PoseStack handles it)
                this.body.xRot = 0;

                // Head compensation for horizontal body
                DogPoseHelper.applyHeadCompensation(
                    this.head,
                    null, // hat is synced via copyFrom below
                    headPitch,
                    netHeadYaw
                );
            }

            // Sync outer layers after static pose
            this.hat.copyFrom(this.head);
            this.jacket.copyFrom(this.body);
            this.leftSleeve.copyFrom(this.leftArm);
            this.rightSleeve.copyFrom(this.rightArm);
            this.leftPants.copyFrom(this.leftLeg);
            this.rightPants.copyFrom(this.rightLeg);

            // Clear emote supplier since we're using static pose
            this.emoteSupplier.set(null);
        } else {
            // Not in pose and no animation - clear emote supplier and reset bends
            this.emoteSupplier.set(null);
            resetBends();

            // Sync outer layers
            this.hat.copyFrom(this.head);
            this.jacket.copyFrom(this.body);
            this.leftSleeve.copyFrom(this.leftArm);
            this.rightSleeve.copyFrom(this.rightArm);
            this.leftPants.copyFrom(this.leftLeg);
            this.rightPants.copyFrom(this.rightLeg);
        }

        // This MUST happen after super.setupAnim() which can reset visibility
        hideWearerLayersForClothes(entity);
    }

    /**
     * Hide wearer's outer layers when clothes are equipped.
     * Called at the end of setupAnim() to ensure it happens after any visibility resets.
     *
     * <p>Logic: When clothes are equipped, hide ALL wearer's outer layers.
     * The clothes will render their own layers on top.
     * Exception: If keepHead is enabled, the head/hat layers remain visible.
     *
     * @param entity The entity wearing clothes
     */
    private void hideWearerLayersForClothes(AbstractTiedUpNpc entity) {
        ItemStack clothes = entity.getEquipment(BodyRegionV2.TORSO);
        if (
            clothes.isEmpty() ||
            !(clothes.getItem() instanceof GenericClothes gc)
        ) {
            return;
        }

        // Check if keepHead is enabled
        boolean keepHead = gc.isKeepHeadEnabled(clothes);

        // When wearing clothes, hide wearer's outer layers
        // Exception: if keepHead is true, don't hide head/hat
        if (!keepHead) {
            this.hat.visible = false;
        }
        this.jacket.visible = false;
        this.leftSleeve.visible = false;
        this.rightSleeve.visible = false;
        this.leftPants.visible = false;
        this.rightPants.visible = false;
    }

    /**
     * Reset bend values on all body parts.
     * Called when animation stops to prevent lingering bend effects.
     */
    private void resetBends() {
        if (IBendHelper.INSTANCE == null) {
            return;
        }
        try {
            IBendHelper.INSTANCE.bend(this.body, null);
            IBendHelper.INSTANCE.bend(this.leftArm, null);
            IBendHelper.INSTANCE.bend(this.rightArm, null);
            IBendHelper.INSTANCE.bend(this.leftLeg, null);
            IBendHelper.INSTANCE.bend(this.rightLeg, null);
        } catch (Exception e) {
            LOGGER.debug(
                "[DamselModel] bendy-lib not available for bend reset",
                e
            );
        }
    }

    /**
     * Override renderToBuffer to apply body bend rotation.
     *
     * <p>When an animation has a body bend value, we need to:
     * 1. Render non-upper parts (legs) normally
     * 2. Apply the bend rotation to the matrix stack
     * 3. Render upper parts (head, arms, body) with the rotation applied
     *
     * <p>This creates the visual effect of the body bending (like kneeling).
     */
    @Override
    public void renderToBuffer(
        PoseStack matrices,
        VertexConsumer vertices,
        int light,
        int overlay,
        float red,
        float green,
        float blue,
        float alpha
    ) {
        // Check if we should use bend rendering
        if (
            Helper.isBendEnabled() &&
            emoteSupplier.get() != null &&
            emoteSupplier.get().isActive()
        ) {
            // Render with bend support
            renderWithBend(
                matrices,
                vertices,
                light,
                overlay,
                red,
                green,
                blue,
                alpha
            );
        } else {
            // Normal rendering
            super.renderToBuffer(
                matrices,
                vertices,
                light,
                overlay,
                red,
                green,
                blue,
                alpha
            );
        }
    }

    /**
     * Render model parts with body bend applied.
     *
     * <p>Based on PlayerAnimator's bendRenderToBuffer logic:
     * - First render non-upper parts (legs) normally
     * - Then apply body bend rotation
     * - Then render upper parts (head, body, arms) with rotation
     */
    private void renderWithBend(
        PoseStack matrices,
        VertexConsumer vertices,
        int light,
        int overlay,
        float red,
        float green,
        float blue,
        float alpha
    ) {
        // Get all body parts
        Iterable<ModelPart> headParts = headParts();
        Iterable<ModelPart> bodyParts = bodyParts();

        // First pass: render non-upper parts (legs)
        for (ModelPart part : headParts) {
            if (!((IUpperPartHelper) (Object) part).isUpperPart()) {
                part.render(
                    matrices,
                    vertices,
                    light,
                    overlay,
                    red,
                    green,
                    blue,
                    alpha
                );
            }
        }
        for (ModelPart part : bodyParts) {
            if (!((IUpperPartHelper) (Object) part).isUpperPart()) {
                part.render(
                    matrices,
                    vertices,
                    light,
                    overlay,
                    red,
                    green,
                    blue,
                    alpha
                );
            }
        }

        // Apply body bend rotation
        matrices.pushPose();
        IBendHelper.rotateMatrixStack(
            matrices,
            emoteSupplier.get().getBend("body")
        );

        // Second pass: render upper parts (head, body, arms) with bend applied
        for (ModelPart part : headParts) {
            if (((IUpperPartHelper) (Object) part).isUpperPart()) {
                part.render(
                    matrices,
                    vertices,
                    light,
                    overlay,
                    red,
                    green,
                    blue,
                    alpha
                );
            }
        }
        for (ModelPart part : bodyParts) {
            if (((IUpperPartHelper) (Object) part).isUpperPart()) {
                part.render(
                    matrices,
                    vertices,
                    light,
                    overlay,
                    red,
                    green,
                    blue,
                    alpha
                );
            }
        }

        matrices.popPose();
    }
}
