// =====================================================
// SWIFT + DARK RELEASE BOSS AI (CustomNPCs 1.12.2)
// All shinobiaddon moves -> ShinobiNPCScriptAPI
// Futon Vacuum Wave -> direct call (Raiton dropped)
//
// Identity: an untouchable speedster that feeds on your
// chakra. Swift kit controls the tempo; Dark kit builds
// "energy" with Inhaling Maw / taking hits, then unloads
// a Judgment beam whose power scales with stored energy.
//
// 2 phases (above/below 50% HP), weighted picks,
// dodge-teleports, rage on spike damage.
//
// NOTE: requires the JudgmentJutsu NPC fix (power is
// treated as energy for non-player casters: damage =
// power * 100 * DamagePerEnergy config).
// =====================================================

// ================= CONFIG =================
var WINDUP_JUDGE        = 16;   // Judgment telegraph (ticks)
var WINDUP_CHAINS       = 6;    // short telegraph for Abyss Chains
var AGGRESSIVE_HP_RATIO = 0.50;

// Energy: Judgment fires once this many stacks are banked
var ENERGY_THRESH_NORMAL = 3;
var ENERGY_THRESH_AGGRO  = 2;
var ENERGY_MAX           = 6;   // hard cap so hits can't bank forever

// Judgment power = base + stacks * per-stack.
// With the mod fix, beam damage = power * 100 * DamagePerEnergy (default 0.05)
// e.g. 3 stacks -> power 10 -> 50 damage.
var JUDGE_POWER_BASE      = 4.0;
var JUDGE_POWER_PER_STACK = 2.0;

var RAGE_TICKS      = 20 * 8;  // 8 s of Thunderous Cross spam
var RAGE_THUNDER_CD = 3;
var RAGE_FAIL_CD    = 4;

// Dodge config
var DODGE_CHANCE        = 0.30;  // 30% chance to dodge an incoming hit
var DODGE_COOLDOWN_MAX  = 60;    // 3 s between dodges
var DODGE_BEHIND_CHANCE = 0.60;  // 60% teleport behind, rest sidestep

var AIM_EVERY_CAST = true;
var AIM_PITCH      = true;

// ---- Normal phase weights (speed-heavy, banking energy) ----
var WEIGHTS_BASE = {
    MOMENTUM  : 35,
    THUNDEROUS: 30,
    INHALE    : 40,
    CHAINS    : 15,
    VACWAVE   : 25
};

// ---- Aggressive phase (below 50% HP — dark-heavy) ----
var WEIGHTS_AGGRO = {
    MOMENTUM  : 40,
    THUNDEROUS: 25,
    INHALE    : 45,
    CHAINS    : 30,
    VACWAVE   : 15
};

// Cast powers
var SWIFT_POWER  = 10.0;
var INHALE_POWER = 5.0;
var CHAINS_POWER = 5.0;
var VAC_POWER    = 4.0;

// Per-move cooldown after a successful cast (ticks)
var CD_AFTER = {
    MOMENTUM  : 8,
    THUNDEROUS: 8,
    INHALE    : 12,
    CHAINS    : 25,
    VACWAVE   : 10,
    JUDGE     : 45
};
var CD_FAIL = 8;

// ================= STATE =================
var flightActivated = false;
var flightRefreshCd = 0;
var FLIGHT_REFRESH  = 60;
var lastPhase       = -1;
var rageTicks       = 0;
var rageCd          = 0;
var dodgeCd         = 0;

// ================= IMPORTS =================
var Shinobi        = Java.type("com.leolifeless.shinobiaddon.compat.customnpcs.ShinobiNPCScriptAPI");
var ItemFuton      = Java.type("net.narutomod.item.ItemFuton");
var ItemStack      = Java.type("net.minecraft.item.ItemStack");
var NBTTagCompound = Java.type("net.minecraft.nbt.NBTTagCompound");
var Math           = Java.type("java.lang.Math");

// ================= HELPERS =================
function getStored(npc, key, defVal) {
    var d = npc.getStoreddata();
    if (!d.has(key)) {
        if (defVal !== undefined) d.put(key, defVal);
        return defVal;
    }
    return d.get(key);
}
function setStored(npc, key, val) {
    npc.getStoreddata().put(key, val);
}

function clamp(v, a, b) {
    return v < a ? a : (v > b ? b : v);
}

function copyWeights(obj) {
    var out = {};
    for (var k in obj) out[k] = obj[k];
    return out;
}

function weightedPick(weights) {
    var keys = [], sum = 0;
    for (var k in weights) {
        if (weights[k] > 0) { keys.push(k); sum += weights[k]; }
    }
    if (!keys.length || sum <= 0) return null;
    var r = Math.random() * sum, acc = 0;
    for (var i = 0; i < keys.length; i++) {
        acc += weights[keys[i]];
        if (r <= acc) return keys[i];
    }
    return keys[keys.length - 1];
}

function getHp(mc) {
    try { if (mc && mc.getHealth) return mc.getHealth(); } catch(e) {}
    try { if (mc && mc.health != null) return mc.health; } catch(e) {}
    return 1;
}
function getMaxHp(mc) {
    try { if (mc && mc.getMaxHealth) return mc.getMaxHealth(); } catch(e) {}
    return 20;
}

function distToTarget(npc, t) {
    if (!t) return 9999;
    var me = npc.getMCEntity();
    var dx = me.posX - t.posX;
    var dy = me.posY - t.posY;
    var dz = me.posZ - t.posZ;
    return Math.sqrt(dx*dx + dy*dy + dz*dz);
}

function faceTarget(me, t) {
    try {
        var dx = t.posX - me.posX;
        var dz = t.posZ - me.posZ;
        var dy = (t.posY + t.height * 0.6) - (me.posY + me.getEyeHeight());
        me.rotationYaw = Math.atan2(dz, dx) * 180 / Math.PI - 90;
        me.rotationYawHead = me.rotationYaw;
        me.renderYawOffset = me.rotationYaw;
        if (AIM_PITCH) {
            me.rotationPitch = -(Math.atan2(dy, Math.sqrt(dx*dx + dz*dz)) * 180 / Math.PI);
        }
    } catch(e) {}
}

// Teleport behind the target and face them
function teleportBehindTarget(me, t) {
    try {
        var angle = Math.toRadians(t.rotationYaw);
        var offsetX = -Math.sin(angle) * 2.0;
        var offsetZ =  Math.cos(angle) * 2.0;
        me.setPositionAndUpdate(t.posX + offsetX, t.posY, t.posZ + offsetZ);
        faceTarget(me, t);
    } catch(e) {}
}

// Sidestep dodge — teleport to a random side
function teleportSidestep(me, t) {
    try {
        var angle = Math.toRadians(t.rotationYaw) + (Math.PI / 2) * (Math.random() < 0.5 ? 1 : -1);
        var offsetX = -Math.sin(angle) * 3.0;
        var offsetZ =  Math.cos(angle) * 3.0;
        me.setPositionAndUpdate(t.posX + offsetX, t.posY, t.posZ + offsetZ);
        faceTarget(me, t);
    } catch(e) {}
}

function makeFutonStack() {
    var s = new ItemStack(ItemFuton.block);
    try {
        if (!s.hasTagCompound()) s.setTagCompound(new NBTTagCompound());
    } catch(e) {}
    return s;
}

function callJutsuSafe(jutsuEnum, stack, entity, power) {
    try {
        return !!jutsuEnum.jutsu.createJutsu(stack, entity, power);
    } catch(e) { return false; }
}

// ---- Energy helpers (storeddata only — survives script reloads) ----
function getEnergy(npc)      { return getStored(npc, "sd_energy", 0); }
function addEnergy(npc, n)   { setStored(npc, "sd_energy", clamp(getEnergy(npc) + n, 0, ENERGY_MAX)); }
function resetEnergy(npc)    { setStored(npc, "sd_energy", 0); }

function judgePower(npc) {
    return JUDGE_POWER_BASE + getEnergy(npc) * JUDGE_POWER_PER_STACK;
}

function castPick(pick, npc, me) {
    if (pick === "THUNDEROUS") return Shinobi.cast(npc, "swift_thunderous_cross", SWIFT_POWER, true);
    if (pick === "MOMENTUM")   return Shinobi.cast(npc, "swift_momentum_breaker", SWIFT_POWER, true);
    if (pick === "INHALE")     return Shinobi.cast(npc, "dark_inhaling_maw",  INHALE_POWER, true);
    if (pick === "CHAINS")     return Shinobi.cast(npc, "dark_abyss_chains",  CHAINS_POWER, true);
    if (pick === "JUDGE")      return Shinobi.cast(npc, "dark_judgment",      judgePower(npc), true);
    if (pick === "VACWAVE")    return callJutsuSafe(ItemFuton.VACWAVE, makeFutonStack(), me, VAC_POWER);
    return false;
}

// Moves that telegraph before firing
var NEEDS_WINDUP    = { JUDGE: true, CHAINS: true };
var WINDUP_OVERRIDE = { JUDGE: WINDUP_JUDGE, CHAINS: WINDUP_CHAINS };

function applyCastResult(npc, pick, used) {
    if (used && pick === "INHALE") addEnergy(npc, 1);
    if (used && pick === "JUDGE")  resetEnergy(npc);
    var cd = used ? (CD_AFTER[pick] !== undefined ? CD_AFTER[pick] : CD_FAIL) : CD_FAIL;
    setStored(npc, "sd_cd", cd);
}

// ================= MAIN TICK =================
function tick(event) {
    var npc = event.npc;
    if (!npc) return;

    var me;
    try { me = npc.getMCEntity(); } catch(e) { return; }

    var tgt = npc.getAttackTarget();
    if (!tgt) return;
    var t = tgt.getMCEntity ? tgt.getMCEntity() : tgt;
    if (!t) return;

    // Init storeddata keys
    if (!npc.getStoreddata().has("sd_cd"))      setStored(npc, "sd_cd", 0);
    if (!npc.getStoreddata().has("sd_wind"))    setStored(npc, "sd_wind", 0);
    if (!npc.getStoreddata().has("sd_casting")) setStored(npc, "sd_casting", "");
    if (!npc.getStoreddata().has("sd_energy"))  setStored(npc, "sd_energy", 0);

    // Keep Shadowless Flight active
    if (!flightActivated || flightRefreshCd <= 0) {
        Shinobi.cast(npc, "swift_shadowless_flight", SWIFT_POWER, true);
        flightActivated = true;
        flightRefreshCd = FLIGHT_REFRESH;
    } else {
        flightRefreshCd--;
    }

    // Tick dodge cooldown
    if (dodgeCd > 0) dodgeCd--;

    // Phase update
    var phase = 0;
    try {
        var ratio = clamp(getHp(me) / getMaxHp(me), 0.0, 1.0);
        phase = ratio <= AGGRESSIVE_HP_RATIO ? 1 : 0;
    } catch(e) {}

    if (phase !== lastPhase) {
        announcePhase(npc, phase);
        lastPhase = phase;
    }

    // Rage decay
    if (rageTicks > 0) rageTicks--;

    // ---- RAGE MODE: spam Thunderous Cross ----
    if (rageTicks > 0) {
        if (rageCd > 0) { rageCd--; return; }
        if (AIM_EVERY_CAST) faceTarget(me, t);
        var ok = Shinobi.cast(npc, "swift_thunderous_cross", SWIFT_POWER, true);
        rageCd = ok ? RAGE_THUNDER_CD : RAGE_FAIL_CD;
        return;
    }

    // ---- WINDUP ----
    var wind    = getStored(npc, "sd_wind", 0);
    var casting = getStored(npc, "sd_casting", "");

    if (wind > 0) {
        if (wind % 3 === 0) {
            try {
                // Dark moves telegraph with PORTAL, swift with SPELL_WITCH
                var part = (casting === "JUDGE" || casting === "CHAINS") ? "PORTAL" : "SPELL_WITCH";
                npc.spawnParticle(part,
                    npc.getX(), npc.getY() + 1, npc.getZ(),
                    0.4, 0.4, 0.4, 10, 0);
            } catch(e) {}
        }
        wind--;
        setStored(npc, "sd_wind", wind);

        if (wind === 0 && casting) {
            if (AIM_EVERY_CAST) faceTarget(me, t);
            var used = castPick(casting, npc, me);
            setStored(npc, "sd_casting", "");
            applyCastResult(npc, casting, used);
        }
        return;
    }

    // ---- COOLDOWN ----
    var cd = getStored(npc, "sd_cd", 0);
    if (cd > 0) { setStored(npc, "sd_cd", cd - 1); return; }

    // ---- ENERGY THRESHOLD: force Judgment ----
    var energyNow    = getEnergy(npc);
    var energyThresh = phase === 1 ? ENERGY_THRESH_AGGRO : ENERGY_THRESH_NORMAL;
    if (energyNow >= energyThresh) {
        if (AIM_EVERY_CAST) faceTarget(me, t);
        npc.say("§5§lFeel the weight of darkness — JUDGMENT!");
        setStored(npc, "sd_wind",    WINDUP_JUDGE);
        setStored(npc, "sd_casting", "JUDGE");
        return;
    }

    // ---- PICK ATTACK ----
    var weights = copyWeights(phase === 1 ? WEIGHTS_AGGRO : WEIGHTS_BASE);

    var d = distToTarget(npc, t);
    if (d > 14) {
        // Ranged pressure + absorb from range; chains can't reach
        weights.VACWAVE    += 20;
        weights.INHALE     += 15;
        weights.THUNDEROUS += 10;
        weights.CHAINS     -= 15;
    } else if (d < 6) {
        // Trap and burst at close range
        weights.MOMENTUM += 20;
        weights.CHAINS   += 25;
    }

    // Bias toward Inhale while energy is still building
    if (energyNow < energyThresh) weights.INHALE += 15;

    var pick = weightedPick(weights);
    if (!pick) return;

    if (NEEDS_WINDUP[pick]) {
        var windTicks = WINDUP_OVERRIDE[pick] !== undefined ? WINDUP_OVERRIDE[pick] : WINDUP_JUDGE;
        setStored(npc, "sd_wind",    windTicks);
        setStored(npc, "sd_casting", pick);
        return;
    }

    if (AIM_EVERY_CAST) faceTarget(me, t);
    var used2 = castPick(pick, npc, me);
    applyCastResult(npc, pick, used2);
}

// ================= EVENTS =================
function target(event) {
    var npc = event.npc;
    if (!npc) return;
    resetEnergy(npc);
    setStored(npc, "sd_cd",      10);
    setStored(npc, "sd_wind",    0);
    setStored(npc, "sd_casting", "");
    npc.say("§5Your chakra... it will feed my speed.");
    // Opening pressure: vacuum wave on aggro
    try {
        var me = npc.getMCEntity();
        callJutsuSafe(ItemFuton.VACWAVE, makeFutonStack(), me, VAC_POWER);
    } catch(e) {}
}

function damaged(event) {
    var npc = event.npc;
    if (!npc) return;

    // Spike -> rage
    var spiked = false;
    try {
        var src = event.source;
        if (src != null) {
            var imm = src.getImmediateSource ? src.getImmediateSource() : null;
            if (imm != null) {
                var cn = ("" + imm.getClass().getName()).toLowerCase();
                if (cn.indexOf("spike") >= 0) spiked = true;
            }
        }
    } catch(e) {}

    if (spiked) {
        rageTicks = RAGE_TICKS;
        rageCd    = 0;
        npc.say("§e§l...You just made a mistake.");
        return;
    }

    // ---- DODGE on incoming hit ----
    if (dodgeCd <= 0 && Math.random() < DODGE_CHANCE) {
        try {
            var me = npc.getMCEntity();
            var tgt = npc.getAttackTarget();
            if (tgt != null) {
                var t = tgt.getMCEntity ? tgt.getMCEntity() : tgt;
                if (t != null) {
                    // Cancel the damage
                    event.setCanceled(true);

                    if (Math.random() < DODGE_BEHIND_CHANCE) {
                        // Appear behind the player, then punish
                        teleportBehindTarget(me, t);
                        if (Math.random() < 0.50) {
                            if (lastPhase === 1) {
                                // Phase 2: chain them where they stand
                                Shinobi.cast(npc, "dark_abyss_chains", CHAINS_POWER, true);
                            } else {
                                Shinobi.cast(npc, "swift_momentum_breaker", SWIFT_POWER, true);
                            }
                        }
                    } else {
                        // Sidestep
                        teleportSidestep(me, t);
                    }

                    dodgeCd = DODGE_COOLDOWN_MAX;
                    return; // dodged — no damage, no absorption
                }
            }
        } catch(e) {}
    }

    // ---- ABSORB: heavy hits feed the darkness ----
    try {
        if (event.damage > 5 && Math.random() < 0.40) {
            addEnergy(npc, 1);
            var cd = getStored(npc, "sd_cd", 0);
            setStored(npc, "sd_cd", Math.max(0, cd - 8));
            npc.say("§5§oYes... give me more.");
        }
    } catch(e) {}
}

function announcePhase(npc, phase) {
    if (phase === 0) {
        npc.say("§b§oSpeed beyond sight... darkness beyond hope.");
    } else {
        npc.say("§d§lI have drunk enough of your chakra — NOW DARKNESS CONSUMES YOU!");
    }
}
