// =============================================================================
// LWI balance: WEIGHTED + LUCK-AWARE outcomes for the Fuze Lucky Blockling.
//
// Stock Fuze rolls all 22 outcomes uniformly (1/22). This rewrites the
// pre-rolled outcome (playerLuck, read by the procedure in playerDestroy) with
// a tier-weighted roll, BIASED by the full luck pipeline:
//   - the block's stored Luck (written by the Lucky Wand), captured by Lucky
//     Tweaks at BreakEvent (LuckState.CAPTURED, same server thread),
//   - gear bonuses (Lucky Ring) contributed via LuckyTweaksApi (LuckState.BONUS),
//   - Lucky Totem boost/curse flags from luckytools (DropTrackerState) -> force
//     a top/bottom-tier outcome, with the curse credit confirmed back.
// Weight shifting uses the Lucky Block mod's own formula:
//   f = 1/(1 - |luck|*0.0077); weight' = weight * f^(tier*sign(luck)).
//
// The blockling is opted into Wand/Totem usage through the luckytools
// `luckytools:luck_aware` block tag (added below) -- without this script that
// tag must NOT be set, the tools would scam the player.
//
// NOTE: outcome 13 suppression lives HERE (weight 0); fuze_outcome13.js is kept only as a safety
// net against external forcers -- do not raise weight 13 without removing that script's redirect.
// Tiers = the pack author's table (mirrored in luckytools FuzeBlocklingMapper;
// keep both in sync). Outcome 13 (pulsion chestplate) weight 0 = pack nerf.
// First-break hazing kept: a player's first blockling is ALWAYS outcome 14.
//
// Script name starts with fuze_b... so it loads BEFORE fuze_force8_TESTING
// (alphabetical): the test forcer still wins while it exists.
// Rhino note: ALL let/const at callback top level; try blocks only assign.
// =============================================================================

const BLOCKLING_W = "fuze_relics:lucky_blockling";

// Opt the blockling into Wand/Totem usage (luckytools refuses untagged lookalikes).
ServerEvents.tags("block", (event) => {
  event.add("luckytools:luck_aware", BLOCKLING_W);
});

// outcome -> tier (the designer table) and tier -> base weight totals:
// +3=6, +2=12(+13 at 0), +1=20, 0=34, -1=15, -2=6, -3=7.
const OUTCOME_TIERS = {
  2: 3, 7: 3, 17: 3, 21: 3,
  13: 2, 15: 2, 19: 2, 20: 2,
  4: 1, 8: 1, 9: 1, 16: 1,
  1: 0, 6: 0, 10: 0, 11: 0,
  5: -1, 18: -1, 22: -1,
  3: -2,
  12: -3, 14: -3,
};
const OUTCOME_WEIGHTS = {
  2: 1.5, 7: 1.5, 17: 1.5, 21: 1.5,
  13: 0, 15: 4, 19: 4, 20: 4,
  4: 5, 8: 5, 9: 5, 16: 5,
  1: 8.5, 6: 8.5, 10: 8.5, 11: 8.5,
  5: 5, 18: 5, 22: 5,
  3: 6,
  12: 3.5, 14: 3.5,
};
const TOP_OUTCOMES = [2, 7, 17, 21];     // tier +3 (Totem boost)
const BOTTOM_OUTCOMES = [12, 14];        // tier -3 (Totem curse)

let FuzeVarsW = null;
try {
  FuzeVarsW = Java.loadClass("net.mcreator.fuzerelics.network.FuzeRelicsModVariables");
} catch (e) {
  console.info("[fuze-weights] Fuze Relics absent -> weighted outcomes disabled");
}
// luckytools / luckytweaks PUBLIC APIs (optional: plain weighted roll without them).
let ToolsApi = null;
let TweaksApi = null;
try {
  ToolsApi = Java.loadClass("com.lwi.luckytools.api.LuckyToolsApi");
} catch (e) {
  console.info("[fuze-weights] luckytools absent -> no Totem support");
}
try {
  TweaksApi = Java.loadClass("com.lwi.luckytweaks.api.LuckyTweaksApi");
} catch (e) {
  console.info("[fuze-weights] luckytweaks absent -> no luck biasing");
}

function rollWeightedOutcome(luck) {
  // Same shape as the Lucky Block mod: f blows up near |luck|~130, clamp like Lucky Tweaks does.
  let l = Math.max(-100, Math.min(120, luck));
  let f = 1 / (1 - Math.abs(l) * 0.0077);
  let sign = l > 0 ? 1 : l < 0 ? -1 : 0;
  let keys = Object.keys(OUTCOME_TIERS);
  let total = 0;
  let weights = {};
  for (let k of keys) {
    let w = OUTCOME_WEIGHTS[k] * (sign === 0 ? 1 : Math.pow(f, OUTCOME_TIERS[k] * sign));
    weights[k] = w;
    total += w;
  }
  let r = Math.random() * total;
  for (let k of keys) {
    r -= weights[k];
    if (r < 0) return Number(k);
  }
  return 10; // unreachable fallback: a neutral outcome
}

function pickFrom(list) {
  return list[Math.floor(Math.random() * list.length)];
}

BlockEvents.broken((event) => {
  if (!FuzeVarsW) return;
  const block = event.block;
  if (!block || String(block.id) !== BLOCKLING_W) return;
  const player = event.player;
  if (!player) return;

  let vars = null;
  let outcome = -1;
  let luck = 0;
  let boost = false;
  let curse = false;
  let captured = null;
  let bonus = 0;
  let leftover = -1;
  try {
    vars = player.getCapability(FuzeVarsW.PLAYER_VARIABLES_CAPABILITY).resolve().orElse(null);
    if (!vars) return;
    // Debug: the value fuze pre-rolled at the END of the previous break. If a break ever runs
    // WITHOUT this handler (ghost path), THIS is the outcome it would execute.
    leftover = Math.round(Number(vars.playerLuck));

    // Totem flags (set by luckytools at HIGH priority, same thread, before us). Read BEFORE the
    // first-break hazing so a simultaneous curse is still accounted for (audit 2026-06-12).
    if (ToolsApi) {
      boost = ToolsApi.isBoostedBreak();
      curse = ToolsApi.isCursedBreak();
    }

    // The KNOWN first-break hazing (community lore): always outcome 14, tracked by a persistent
    // stage. Deliberately NO curse confirmation here: outcome 14 is bad, but it was PREDETERMINED
    // -- the totem didn't force anything the player wouldn't have suffered anyway, so the curse
    // debt persists and re-forces a later, genuine break (same semantics as a failed delivery).
    // A boost is likewise overridden by the hazing -- accepted lore cost.
    if (!player.stages.has("lwi_fuze_first_done")) {
      player.stages.add("lwi_fuze_first_done");
      vars.playerLuck = 14;
      console.info("[fuze-weights] first break for " + player.username + " -> scripted outcome 14"
          + (curse ? " (curse NOT discharged: hazing was predetermined)" : ""));
      return;
    }
    // Full luck: block's stored Luck (Wand) captured by Lucky Tweaks + gear bonus (Ring).
    if (TweaksApi) {
      captured = TweaksApi.getCapturedLuck();
      bonus = Number(TweaksApi.getContributedLuck());
      luck = (captured === null ? 0 : Number(captured)) + bonus;
    }

    if (boost) {
      outcome = pickFrom(TOP_OUTCOMES);
    } else if (curse) {
      outcome = pickFrom(BOTTOM_OUTCOMES);
      // The forced bad outcome IS delivered (the procedure always runs), so confirm the curse
      // credit -- decrements the debt and shatters the totem on the last one (same contract as
      // the drop-pipeline mixin).
      if (ToolsApi) ToolsApi.confirmCurseBadDrop(player);
    } else {
      outcome = rollWeightedOutcome(luck);
    }
    vars.playerLuck = outcome;
    // Outcome 12's knockback is a raw server-side setDeltaMovement with NO hurtMarked: player
    // motion is client-authoritative, so stock Fuze only ever delivered the push through the
    // client-side ghost run we just sanitized away. Raise the sync flag ourselves: the procedure
    // (running right after this event) adds its -2.5x look motion, and the flag ships it to the
    // client at end of tick. The push is back -- through the front door this time.
    if (outcome === 12) {
      player.hurtMarked = true;
    }
  } catch (e) {
    console.warn("[fuze-weights] failed: " + e);
    return;
  }
  console.info("[fuze-weights] outcome " + outcome
      + (boost ? " (TOTEM BOOST)" : curse ? " (TOTEM CURSE)" : " (luck=" + luck + ", captured=" + captured + ", bonus=" + bonus + ")")
      + " [leftover was " + leftover + "]");
});

console.info("[fuze-weights] active (tier-weighted, luck-aware blockling outcomes)");
