package com.gmail.nossr50.skills.crossbows;

import static com.gmail.nossr50.util.skills.CombatUtils.delayArrowMetaCleanup;

import com.gmail.nossr50.datatypes.player.McMMOPlayer;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import com.gmail.nossr50.datatypes.skills.SubSkillType;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.skills.SkillManager;
import com.gmail.nossr50.util.MetadataConstants;
import com.gmail.nossr50.util.Permissions;
import com.gmail.nossr50.util.random.ProbabilityUtil;
import com.gmail.nossr50.util.skills.ProjectileUtils;
import com.gmail.nossr50.util.skills.RankUtils;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.AbstractArrow;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.plugin.Plugin;
import org.bukkit.projectiles.ProjectileSource;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;

public class CrossbowsManager extends SkillManager {
    public CrossbowsManager(McMMOPlayer mmoPlayer) {
        super(mmoPlayer, PrimarySkillType.CROSSBOWS);
    }

    public void handleRicochet(@NotNull Plugin pluginRef, @NotNull Arrow arrow,
            @NotNull Vector hitBlockNormal) {
        if (!arrow.isShotFromCrossbow()) {
            return;
        }

        // Check player permission
        if (!Permissions.trickShot(mmoPlayer.getPlayer())) {
            return;
        }

        // TODO: Add an event for this for plugins to hook into
        spawnReflectedArrow(pluginRef, arrow, arrow.getLocation(), hitBlockNormal);
    }

    private void spawnReflectedArrow(@NotNull Plugin pluginRef, @NotNull Arrow originalArrow,
            @NotNull Location origin, @NotNull Vector normal) {
        int bounceCount = 0;

        if (originalArrow.hasMetadata(MetadataConstants.METADATA_KEY_BOUNCE_COUNT)) {
            bounceCount = originalArrow.getMetadata(MetadataConstants.METADATA_KEY_BOUNCE_COUNT)
                    .get(0).asInt();
            if (bounceCount >= getTrickShotMaxBounceCount()) {
                return;
            }
        }

        final ProjectileSource originalArrowShooter = originalArrow.getShooter();
        final Vector arrowInBlockVector = originalArrow.getVelocity();
        final Vector reflectedDirection = arrowInBlockVector.subtract(
                normal.multiply(2 * arrowInBlockVector.dot(normal)));
        final Vector inverseNormal = normal.multiply(-1);

        // Only glancing shots ricochet: a reflection within 45 degrees of the surface normal
        // means the arrow hit too squarely to bounce. Later bounces always ricochet.
        if (bounceCount == 0 && arrowInBlockVector.angle(inverseNormal) < Math.PI / 4) {
            return;
        }

        // Spawn new arrow with the reflected direction
        final Arrow spawnedArrow = originalArrow.getWorld()
                .spawnArrow(origin, reflectedDirection, 1, 1);
        // copy some properties from the old arrow
        spawnedArrow.setShooter(originalArrowShooter);
        spawnedArrow.setCritical(originalArrow.isCritical());
        spawnedArrow.setPierceLevel(originalArrow.getPierceLevel());
        spawnedArrow.setPickupStatus(originalArrow.getPickupStatus());
        spawnedArrow.setKnockbackStrength(originalArrow.getKnockbackStrength());

        // Copy tipped-arrow state: set the item type to TIPPED_ARROW before applying
        // potion data so the pickup item has the correct POTION_DURATION_SCALE (0.125).
        // Without this, spawnArrow() creates an Items.ARROW pickup item, defaulting
        // the scale to 1.0 and making effects last 8× longer than intended.
        if (originalArrow.getBasePotionType() != null) {
            spawnedArrow.setItem(new ItemStack(Material.TIPPED_ARROW));
            spawnedArrow.setBasePotionType(originalArrow.getBasePotionType());
        }

        if (originalArrow.hasCustomEffects()) {
            for (final var effect : originalArrow.getCustomEffects()) {
                spawnedArrow.addCustomEffect(effect, true);
            }
        }

        // copy metadata from old arrow
        ProjectileUtils.copyArrowMetadata(pluginRef, originalArrow, spawnedArrow);
        originalArrow.remove();
        // add important metadata to new arrow
        spawnedArrow.setMetadata(MetadataConstants.METADATA_KEY_BOUNCE_COUNT,
                new FixedMetadataValue(pluginRef, bounceCount + 1));
        spawnedArrow.setMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW,
                new FixedMetadataValue(pluginRef, originalArrowShooter));
        // Persist crossbow identity across bounces.
        spawnedArrow.setShotFromCrossbow(true);

        // Don't allow infinite arrows to be picked up
        if (spawnedArrow.hasMetadata(MetadataConstants.METADATA_KEY_INF_ARROW)) {
            spawnedArrow.setPickupStatus(AbstractArrow.PickupStatus.DISALLOWED);
        }

        // Schedule cleanup of metadata in case metadata cleanup fails
        delayArrowMetaCleanup(spawnedArrow);
    }

    public int getTrickShotMaxBounceCount() {
        return RankUtils.getRank(mmoPlayer, SubSkillType.CROSSBOWS_TRICK_SHOT);
    }

    public double getPoweredShotBonusDamage(Player player, double oldDamage) {
        final double damageBonusPercent = getDamageBonusPercent(player);
        final double newDamage = oldDamage + (oldDamage * damageBonusPercent);
        return Math.min(newDamage,
                oldDamage + mcMMO.p.getAdvancedConfig().getPoweredShotDamageMax());
    }

    public double getDamageBonusPercent(Player player) {
        return ((RankUtils.getRank(player, SubSkillType.CROSSBOWS_POWERED_SHOT))
                * (mcMMO.p.getAdvancedConfig().getPoweredShotRankDamageMultiplier()) / 100.0D);
    }

    public double poweredShot(double oldDamage) {
        if (ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.CROSSBOWS_POWERED_SHOT,
                mmoPlayer)) {
            return getPoweredShotBonusDamage(getPlayer(), oldDamage);
        } else {
            return oldDamage;
        }
    }
}
