/*
 * Copyright (c) 2026 盐灯水母
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

package com.remains_cave.entity;

import com.remains_cave.attribute.ModAttributes;
import com.remains_cave.config.RemainsCaveConfig;
import com.remains_cave.effect.ModEffects;
import com.remains_cave.entity.ai.StayNearKingGoal;
import com.remains_cave.entity.ai.TeamHurtByTargetGoal;
import com.remains_cave.entity.projectile.RuneEntity;
import com.remains_cave.util.ConfigHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.BlockParticleOption;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.*;
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import org.jetbrains.annotations.NotNull;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.*;
import software.bernie.geckolib.util.GeckoLibUtil;

import java.util.EnumSet;
import java.util.List;
import java.util.Objects;

public class SkeletonWarlockEntity extends Monster implements GeoEntity, AuraBearer {
    // 同步数据
    private static final EntityDataAccessor<Boolean> DATA_HAS_AURA = SynchedEntityData.defineId(SkeletonWarlockEntity.class, EntityDataSerializers.BOOLEAN);
    private static final EntityDataAccessor<Boolean> DATA_IS_SUMMONING = SynchedEntityData.defineId(SkeletonWarlockEntity.class, EntityDataSerializers.BOOLEAN);

    private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this);
    private RuneEntity currentRune = null;
    private static final RawAnimation IDLE = RawAnimation.begin().thenLoop("animation.skeleton_warlock.idle");
    private static final RawAnimation WALK = RawAnimation.begin().thenLoop("animation.skeleton_warlock.walk");
    private static final RawAnimation ATTACK = RawAnimation.begin().thenPlay("animation.skeleton_warlock.attack");
    private static final RawAnimation HURT = RawAnimation.begin().thenPlay("animation.skeleton_warlock.hurt");
    private static final RawAnimation HEAL = RawAnimation.begin().thenPlay("animation.skeleton_warlock.attack");
    private static final RawAnimation SUMMON = RawAnimation.begin().thenPlay("summon");

    private int stunTimer = 0;
    private int spellCooldown = 0;
    private boolean hasJoinedTeam = false;
    private int summonAnimationTimer = 0;          // 服务端计时器
    private boolean hasPlayedSummonAnim = false;   // 持久化标志，防止重进重复播放
    private int particleTick = 0;                   // 粒子计数器

    public boolean isSummoning() {
        if (level().isClientSide) {
            return this.entityData.get(DATA_IS_SUMMONING);
        } else {
            return summonAnimationTimer > 0;
        }
    }
    private float summonYaw = 0;  // 存储召唤时的随机朝向
    /**
     * 开始召唤动画（服务端调用）
     */
    public void startSummonAnimation() {
        if (level().isClientSide) return;
        summonAnimationTimer = 40;
        summonYaw = random.nextFloat() * 360.0f;
        this.setYRot(summonYaw);
        this.setYBodyRot(summonYaw);
        this.setYHeadRot(summonYaw);
        this.entityData.set(DATA_IS_SUMMONING, true);
        // 清除 AI 状态，但不调用 LookControl.setLookAt(null)
        this.setTarget(null);
        this.getNavigation().stop();
        this.getMoveControl().setWantedPosition(this.getX(), this.getY(), this.getZ(), 0.0);
        this.setDeltaMovement(Vec3.ZERO);
    }

    public SkeletonWarlockEntity(EntityType<? extends Monster> type, Level level) {
        super(type, level);
        Objects.requireNonNull(this.getAttribute(ModAttributes.SMITE_RESISTANCE))
                .setBaseValue(RemainsCaveConfig.COMMON.warlockSmiteResistance.get());
    }

    @Override
    public boolean isInvertedHealAndHarm() {
        return true;
    }

    @Override
    public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
        AnimationController<SkeletonWarlockEntity> controller = new AnimationController<>(this, "controller", 2, this::animationPredicate)
                .triggerableAnim("attack", ATTACK)
                .triggerableAnim("hurt", HURT)
                .triggerableAnim("heal", HEAL)
                .triggerableAnim("summon", SUMMON);
        controllers.add(controller);
    }

    public void triggerHealAnim() {
        this.triggerAnim("controller", "heal");
    }

    private PlayState animationPredicate(AnimationState<SkeletonWarlockEntity> state) {
        if (isSummoning()) {
            return PlayState.CONTINUE;
        }
        if (state.isMoving()) {
            return state.setAndContinue(WALK);
        }
        return state.setAndContinue(IDLE);
    }

    public void triggerAttackAnim() {
        this.triggerAnim("controller", "attack");
    }

    @Override
    protected void registerGoals() {
        this.goalSelector.addGoal(2, new HealGoal());
        this.goalSelector.addGoal(3, new AttackGoal());
        this.goalSelector.addGoal(4, new WaterAvoidingRandomStrollGoal(this, 0.8D, 0.0001f));
        this.goalSelector.addGoal(5, new RandomLookAroundGoal(this));

        this.goalSelector.addGoal(1, new StayNearKingGoal(this, 1.0D, 15.0, 9.0) {
            @Override
            public boolean canUse() {
                if (SkeletonWarlockEntity.this.isSummoning()) return false;
                return super.canUse();
            }
            @Override
            public boolean canContinueToUse() {
                if (SkeletonWarlockEntity.this.isSummoning()) return false;
                return super.canContinueToUse();
            }
        });
        this.targetSelector.addGoal(0, new TeamHurtByTargetGoal(this, true));
        this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
    }

    // ---------- 治疗目标 ----------
    private class HealGoal extends Goal {
        private LivingEntity healTarget;
        private int castingTime = 0;
        private boolean hasCast = false;
        private static final double MAX_START_DIST_SQ = 100;
        private static final double MAX_CONTINUE_DIST_SQ = 144;
        private final int windupTicks;
        private final int recoveryTicks;

        public HealGoal() {
            this.setFlags(EnumSet.of(Flag.LOOK, Flag.MOVE));
            this.windupTicks = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockWindupTicks, 6);
            this.recoveryTicks = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockRecoveryTicks, 10);
        }

        @Override
        public boolean canUse() {
            if (spellCooldown > 0 || stunTimer > 0 || SkeletonWarlockEntity.this.isSummoning()) return false;
            List<LivingEntity> skeletons = level().getEntitiesOfClass(LivingEntity.class, getBoundingBox().inflate(10),
                    e -> (e instanceof SkeletonWarriorEntity || e instanceof SkeletonArcherEntity || e instanceof SkeletonWarlockEntity || e instanceof SkeletonKingEntity)
                            && e.getHealth() < e.getMaxHealth() * 0.75
                            && hasLineOfSight(e));
            return !skeletons.isEmpty();
        }

        @Override
        public void start() {
            List<LivingEntity> skeletons = level().getEntitiesOfClass(LivingEntity.class, getBoundingBox().inflate(10),
                    e -> (e instanceof SkeletonWarriorEntity || e instanceof SkeletonArcherEntity || e instanceof SkeletonWarlockEntity || e instanceof SkeletonKingEntity)
                            && e.getHealth() < e.getMaxHealth() * 0.75
                            && hasLineOfSight(e));
            skeletons.sort((a, b) -> {
                if (a instanceof SkeletonKingEntity && !(b instanceof SkeletonKingEntity)) return -1;
                if (!(a instanceof SkeletonKingEntity) && b instanceof SkeletonKingEntity) return 1;
                return Float.compare(a.getHealth(), b.getHealth());
            });
            if (skeletons.isEmpty()) return;
            healTarget = skeletons.getFirst();
            castingTime = windupTicks;
            hasCast = false;
            triggerHealAnim();
        }

        @Override
        public boolean canContinueToUse() {
            if (stunTimer > 0 || SkeletonWarlockEntity.this.isSummoning()) return false;
            return castingTime > 0 && healTarget != null && healTarget.isAlive()
                    && distanceToSqr(healTarget) <= MAX_CONTINUE_DIST_SQ
                    && hasLineOfSight(healTarget);
        }

        @Override
        public void tick() {
            if (castingTime > 0) {
                castingTime--;
                if (!hasCast && castingTime <= 0) {
                    int maxEffects = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockRuneBaseMaxEffects, 3);
                    if (SkeletonWarlockEntity.this.hasEffect(ModEffects.KINGS_AURA)) {
                        maxEffects += ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockRuneAuraExtraEffects, 2);
                    }
                    float damage = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockRuneDamage::get, 4.0f).floatValue();
                    float heal = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockRuneHeal::get, 2.0f).floatValue();
                    int initialDelay = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockRuneInitialDelay, 10);
                    int effectInterval = SkeletonWarlockEntity.this.hasEffect(ModEffects.KINGS_AURA) ? 12 : 20;
                    double radius = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockRuneRadius, 1.5);
                    boolean hasAura = SkeletonWarlockEntity.this.hasEffect(ModEffects.KINGS_AURA); // 新增

                    RuneEntity rune = new RuneEntity(level(), healTarget.getX(), healTarget.getY(), healTarget.getZ(),
                            SkeletonWarlockEntity.this, damage, heal, maxEffects,
                            initialDelay, effectInterval, radius, hasAura);
                    level().addFreshEntity(rune);
                    currentRune = rune;
                    spawnHealParticles(healTarget);
                    spellCooldown = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockSpellCooldown, 75);
                    hasCast = true;
                    castingTime = recoveryTicks;
                    SkeletonWarlockEntity.this.level().playSound(null,
                            SkeletonWarlockEntity.this.getX(),
                            SkeletonWarlockEntity.this.getY(),
                            SkeletonWarlockEntity.this.getZ(),
                            SoundEvents.EVOKER_CAST_SPELL,
                            SkeletonWarlockEntity.this.getSoundSource(),
                            1.0F, 1.0F);
                }
            }
        }

        @Override
        public void stop() {
            castingTime = 0;
            healTarget = null;
            hasCast = false;
        }
    }

    private void spawnHealParticles(LivingEntity target) {
        double x = target.getX();
        double y = target.getY() + target.getBbHeight() / 2;
        double z = target.getZ();
        for (int i = 0; i < 15; i++) {
            double dx = (random.nextDouble() - 0.5) * 2.0;
            double dy = random.nextDouble() * 1.5;
            double dz = (random.nextDouble() - 0.5) * 2.0;
            level().addParticle(ParticleTypes.HEART, x + dx, y + dy, z + dz, dx * 0.1, 0.2, dz * 0.1);
        }
    }

    // ---------- 攻击（伤害法阵）----------
    private class AttackGoal extends Goal {
        private LivingEntity localTarget;
        private int localCastingTime;
        private static final double ATTACK_RANGE_SQ = 225;
        private static final double FOLLOW_RANGE_SQ = 400;
        private final int windupTicks;

        public AttackGoal() {
            this.setFlags(EnumSet.of(Flag.LOOK, Flag.MOVE));
            this.windupTicks = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockWindupTicks, 6);
        }

        @Override
        public boolean canUse() {
            if (spellCooldown > 0 || stunTimer > 0 || SkeletonWarlockEntity.this.isSummoning()) return false;
            LivingEntity target = getTarget();
            return target != null && target.isAlive() && distanceToSqr(target) <= FOLLOW_RANGE_SQ && hasLineOfSight(target);
        }

        @Override
        public void start() {
            localTarget = getTarget();
            localCastingTime = 0;
        }

        @Override
        public boolean canContinueToUse() {
            if (stunTimer > 0 || SkeletonWarlockEntity.this.isSummoning()) return false;
            return localTarget != null && localTarget.isAlive() && distanceToSqr(localTarget) <= FOLLOW_RANGE_SQ && hasLineOfSight(localTarget);
        }

        @Override
        public void tick() {
            if (localTarget == null) return;

            getLookControl().setLookAt(localTarget, 30.0F, 30.0F);

            double distSq = distanceToSqr(localTarget);
            if (distSq <= ATTACK_RANGE_SQ && hasLineOfSight(localTarget)) {
                if (localCastingTime == 0 && spellCooldown <= 0) {
                    localCastingTime = windupTicks;
                    triggerAttackAnim();
                }
                if (localCastingTime > 0) {
                    localCastingTime--;
                    if (localCastingTime == 0) {
                        int maxEffects = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockRuneBaseMaxEffects, 3);
                        if (SkeletonWarlockEntity.this.hasEffect(ModEffects.KINGS_AURA)) {
                            maxEffects += ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockRuneAuraExtraEffects, 1);
                        }
                        float damage = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockRuneDamage::get, 4.0f).floatValue();
                        float heal = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockRuneHeal::get, 2.0f).floatValue();
                        int initialDelay = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockRuneInitialDelay, 10);
                        int effectInterval = SkeletonWarlockEntity.this.hasEffect(ModEffects.KINGS_AURA) ? 12 : 20;
                        double radius = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockRuneRadius, 1.5);
                        boolean hasAura = SkeletonWarlockEntity.this.hasEffect(ModEffects.KINGS_AURA); // 新增

                        RuneEntity rune = new RuneEntity(level(), localTarget.getX(), localTarget.getY(), localTarget.getZ(),
                                SkeletonWarlockEntity.this, damage, heal, maxEffects,
                                initialDelay, effectInterval, radius, hasAura);
                        level().addFreshEntity(rune);
                        currentRune = rune;
                        spellCooldown = ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockSpellCooldown, 75);
                        SkeletonWarlockEntity.this.level().playSound(null,
                                SkeletonWarlockEntity.this.getX(),
                                SkeletonWarlockEntity.this.getY(),
                                SkeletonWarlockEntity.this.getZ(),
                                SoundEvents.EVOKER_CAST_SPELL,
                                SkeletonWarlockEntity.this.getSoundSource(),
                                1.0F, 1.0F);
                    }
                }
            } else {
                getNavigation().moveTo(localTarget, 1.0);
            }
        }

        @Override
        public void stop() {
            localCastingTime = 0;
            localTarget = null;
        }
    }

    @Override
    protected void defineSynchedData(SynchedEntityData.@NotNull Builder builder) {
        super.defineSynchedData(builder);
        builder.define(DATA_HAS_AURA, false);
        builder.define(DATA_IS_SUMMONING, false);
    }

    @Override
    public void addAdditionalSaveData(@NotNull CompoundTag tag) {
        super.addAdditionalSaveData(tag);
        tag.putInt("SummonTimer", summonAnimationTimer);
        tag.putBoolean("HasPlayedSummonAnim", hasPlayedSummonAnim);
    }

    @Override
    public void readAdditionalSaveData(@NotNull CompoundTag tag) {
        super.readAdditionalSaveData(tag);
        summonAnimationTimer = tag.getInt("SummonTimer");
        hasPlayedSummonAnim = tag.getBoolean("HasPlayedSummonAnim");
        if (summonAnimationTimer > 0) {
            this.entityData.set(DATA_IS_SUMMONING, true);
        }
    }

    @Override
    public void performRangedAttack(LivingEntity target, float distanceFactor) { }

    @Override
    public boolean hasAura() {
        return this.entityData.get(DATA_HAS_AURA);
    }

    public void setHasAura(boolean hasAura) {
        this.entityData.set(DATA_HAS_AURA, hasAura);
    }

    @Override
    public void tick() {
        if (!level().isClientSide && this.getTarget() instanceof Player player && player.isCreative()) {
            this.setTarget(null);
            this.setLastHurtByMob(null);
        }
        super.tick();

        // ========== 服务端逻辑 ==========
        if (!level().isClientSide) {
            // 召唤动画计时
            if (summonAnimationTimer > 0) {
                summonAnimationTimer--;
                if (summonAnimationTimer <= 0) {
                    this.entityData.set(DATA_IS_SUMMONING, false);
                }
            }
            // 动画期间强制静止
            if (isSummoning()) {
                this.setYRot(summonYaw);
                this.setYBodyRot(summonYaw);
                this.setYHeadRot(summonYaw);

                this.setTarget(null);
                this.getNavigation().stop();
                this.getMoveControl().setWantedPosition(this.getX(), this.getY(), this.getZ(), 0.0);
                this.setDeltaMovement(Vec3.ZERO);
            }

            if (stunTimer > 0) stunTimer--;
            if (spellCooldown > 0) spellCooldown--;

            boolean aura = this.hasEffect(ModEffects.KINGS_AURA);
            setHasAura(aura);
            if (!hasJoinedTeam) {
                Scoreboard scoreboard = level().getScoreboard();
                PlayerTeam team = scoreboard.getPlayerTeam("skeleton_army");
                if (team == null) {
                    team = scoreboard.addPlayerTeam("skeleton_army");
                    team.setAllowFriendlyFire(false);
                }
                if (this.getTeam() == null) {
                    scoreboard.addPlayerToTeam(this.getScoreboardName(), team);
                }
                hasJoinedTeam = true;
            }
        }

        // ========== 客户端逻辑 ==========
        if (level().isClientSide) {
            boolean summoningFlag = this.entityData.get(DATA_IS_SUMMONING);
            if (!hasPlayedSummonAnim && summoningFlag) {
                this.triggerAnim("controller", "summon");
                hasPlayedSummonAnim = true;
            }
            // 粒子效果
            if (isSummoning()) {
                particleTick++;
                if (particleTick >= 1) {
                    particleTick = 0;
                    BlockPos belowPos = this.blockPosition().below();
                    BlockState belowState = level().getBlockState(belowPos);
                    level().addParticle(new BlockParticleOption(ParticleTypes.BLOCK, belowState),
                            this.getX(), this.getY() + 0.1, this.getZ(),
                            (random.nextDouble() - 0.5) * 0.3,
                            random.nextDouble() * 0.2,
                            (random.nextDouble() - 0.5) * 0.3);
                }
            }
        }
    }

    @Override
    public boolean hurt(@NotNull DamageSource source, float amount) {
        Vec3 originalVelocity = null;
        if (isSummoning()) {
            amount *= 0.2f;
            originalVelocity = this.getDeltaMovement();
        }
        boolean hurt = super.hurt(source, amount);
        if (hurt && !level().isClientSide) {
            if (isSummoning()) {
                if (originalVelocity != null) {
                    this.setDeltaMovement(originalVelocity);
                }
            } else {
                this.triggerAnim("controller", "hurt");
                stunTimer = 10;
            }
        }
        return hurt;
    }

    @Override
    public void knockback(double strength, double x, double z) {
        if (!isSummoning()) {
            super.knockback(strength, x, z);
        }
    }

    @Override
    public AnimatableInstanceCache getAnimatableInstanceCache() {
        return cache;
    }

    public static AttributeSupplier.Builder createAttributes() {
        return Monster.createMonsterAttributes()
                .add(Attributes.MAX_HEALTH, ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockMaxHealth, 30.0))
                .add(Attributes.MOVEMENT_SPEED, ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockMovementSpeed, 0.25))
                .add(Attributes.ARMOR, ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockArmor, 6.0))
                .add(Attributes.ARMOR_TOUGHNESS, ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockArmorToughness, 3.0))
                .add(Attributes.ATTACK_DAMAGE, ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockAttackDamage, 3.0))
                .add(Attributes.FOLLOW_RANGE, ConfigHelper.getOrFallback(RemainsCaveConfig.COMMON.warlockFollowRange, 20.0))
                .add(ModAttributes.SMITE_RESISTANCE);
    }

    @Override
    protected int getBaseExperienceReward() {
        return (int)this.getMaxHealth();
    }

    @Override
    protected @NotNull SoundEvent getHurtSound(@NotNull DamageSource source) {
        return SoundEvents.SKELETON_HURT;
    }

    @Override
    protected @NotNull SoundEvent getDeathSound() {
        return SoundEvents.SKELETON_DEATH;
    }

    @Override
    protected SoundEvent getAmbientSound() {
        return SoundEvents.SKELETON_AMBIENT;
    }
}