import { world, system } from "@minecraft/server";

/** ===================== CONFIG ===================== **/
const MAX_INSOMNIO = 170000;

const FORGIVE_WINDOW_TICKS = 600;
const COMBO_WINDOW_TICKS = 200;

const COMBO_HIT_STEP = 0.12;
const COMBO_KILL_STEP = 0.20;
const COMBO_MAX_MULT = 2.50;

const RANGED_DISTANCE = 8;

const WITNESS_RADIUS = 6;
const ENABLE_WITNESSES = true;
const WITNESS_CACHE_TICKS = 40; // recalcular cada 2s máx por jugador

const HIT_HOSTILE_DELTA = -20;
const HIT_PASSIVE_DELTA = 500;
const KILL_HOSTILE_DELTA = -5000;
const KILL_PASSIVE_DELTA = 5000;

const EXTRA_RANGED = 0.30;
const EXTRA_PROJECTILE = 0.25;
const EXTRA_WEAPON = 0.15;
const EXTRA_BAREHAND = -0.10;
const EXTRA_WITNESSES = 0.35;

const REDEEM_T1 = 10000;
const REDEEM_T2 = 50000;
const REDEEM_T3 = 100000;

const FEED_REDEEM_AMOUNT = 1000;
const FEED_COOLDOWN_TICKS = 40;
const FEED_ITEMS = new Set([
  "minecraft:wheat",
  "minecraft:carrot",
  "minecraft:potato",
  "minecraft:beetroot",
  "minecraft:seeds",
  "minecraft:wheat_seeds",
  "minecraft:beetroot_seeds",
  "minecraft:pumpkin_seeds",
  "minecraft:melon_seeds",
  "minecraft:sweet_berries",
  "minecraft:apple",
  "minecraft:hay_block"
]);

const PACIFISM_WINDOW_TICKS = 200;
const PACIFISM_REDEEM_AMOUNT = 5000;
const PACIFISM_ONLY_IF_HOSTILE = true;

const CLEAN_BLOCKS = new Set(["minecraft:web", "minecraft:deadbush", "minecraft:vine"]);
const CLEAN_REDEEM_AMOUNT = 120;

const CLEAN_REQUIRED_BREAKS = 4;
const CLEAN_GROUP_COOLDOWN_TICKS = 12000;

const CLEAN_REQUIRE_NO_VIOLENCE = true;
const CLEAN_NO_VIOLENCE_TICKS = 200;

const ANIMALES_SENSIBLES = new Set([
  "minecraft:cat",
  "dungeon:butterfly",
  "dungeon:firefly",
  "dungeon:glowzard",
  "dungeon:clamd",
  "dungeon:small_scorpion",
  "dungeon:dragonfly",
  "dungeon:small_cricket",
  "dungeon:small_bedbug",
  "minecraft:panda",
  "minecraft:sniffer",
  "minecraft:fox",
  "minecraft:wolf",
  "minecraft:parrot",
  "minecraft:ocelot",
  "minecraft:horse",
  "minecraft:donkey",
  "minecraft:mule",
  "minecraft:camel",
  "minecraft:axolotl",
  "minecraft:goat",
  "minecraft:frog",
  "minecraft:bee",
  "minecraft:bat",
  "minecraft:allay",
  "minecraft:wandering_trader"
]);

/** ===================== GLOBAL STATE ===================== **/
let TICK = 0;

// por player.id 
const combo = new Map();            // id -> { lastTick, hits, kills }
const forgive = new Map();          // id -> { active, pendingAmount, untilTick }
const feedCooldown = new Map();     // id -> untilTick
const pacifism = new Map();         // id -> { active, untilTick }
const cleanProg = new Map();        // id -> { count, cdUntil }
const lastViolenceTick = new Map(); // id -> tick

const delta = new Map();            // id -> number
const flags = new Map();            // id -> { incCombat, decCombat }
const witnessCache = new Map();     // id -> { lastCheck, has }

/** ===================== TICK COUNTER ===================== **/
system.runInterval(() => {
  TICK++;
}, 1);

/** ===================== SCOREBOARD (LAZY INIT) ===================== **/
let OBJ_INSOMNIO = null;

function ensureInsomnioObjective() {
  if (OBJ_INSOMNIO) return OBJ_INSOMNIO;

  try {
    const sb = world.scoreboard; // falla si es “early execution”
    let obj = sb.getObjective("insomnio");
    if (!obj) obj = sb.addObjective("insomnio", "insomnio");
    OBJ_INSOMNIO = obj;
    return OBJ_INSOMNIO;
  } catch {
    return null;
  }
}

// bootstrap: intenta crear al siguiente tick (y reintenta por si acaso)
system.run(() => {
  ensureInsomnioObjective();
});
system.runInterval(() => {
  if (!OBJ_INSOMNIO) ensureInsomnioObjective();
}, 20);

function getInsomnio(player) {
  const obj = ensureInsomnioObjective();
  if (!obj) return 0;
  try {
    const v = obj.getScore(player);
    return (v === undefined || v === null) ? 0 : v;
  } catch {
    return 0;
  }
}

function setInsomnio(player, value) {
  const obj = ensureInsomnioObjective();
  if (!obj) return;
  try {
    const v = Math.max(0, Math.min(MAX_INSOMNIO, value | 0));
    obj.setScore(player, v);
  } catch {}
}

function addInsomnio(player, amount) {
  const cur = getInsomnio(player);
  setInsomnio(player, cur + (amount | 0));
}

function removeInsomnio(player, amount) {
  const cur = getInsomnio(player);
  setInsomnio(player, cur - (amount | 0));
}

/** ===================== HELPERS ===================== **/
function getTypeFamilyComponent(entity) {
  try { return entity.getComponent("minecraft:type_family"); } catch { return null; }
}
function hasFamily(entity, key) {
  try {
    const c = getTypeFamilyComponent(entity);
    return !!c && c.hasTypeFamily(key);
  } catch {
    return false;
  }
}
function isHostile(entity) { return hasFamily(entity, "monster") || hasFamily(entity, "boss"); }
function isInanimate(entity) { return hasFamily(entity, "inanimate"); }
function isAnimal(entity) { return hasFamily(entity, "animal"); }
function isVillager(entity) { return hasFamily(entity, "villager"); }

function dist(a, b) {
  try {
    const dx = a.x - b.x, dy = a.y - b.y, dz = a.z - b.z;
    return Math.sqrt(dx * dx + dy * dy + dz * dz);
  } catch {
    return 0;
  }
}

function redeemFactorFromScore(score) {
  if (score < REDEEM_T1) return 1.0;
  if (score < REDEEM_T2) return 0.60;
  if (score < REDEEM_T3) return 0.30;
  return 0.15;
}

function isProjectileDamage(damageSource) {
  try {
    const de = damageSource && damageSource.damagingEntity;
    const t = de && de.typeId;
    return (
      t === "minecraft:arrow" ||
      t === "minecraft:trident" ||
      t === "minecraft:snowball" ||
      t === "minecraft:egg"
    );
  } catch {
    return false;
  }
}

function getHeldItem(player) {
  try {
    const inv = player.getComponent("minecraft:inventory");
    return inv?.container?.getItem(player.selectedSlotIndex) ?? null;
  } catch {
    return null;
  }
}

function isWeaponItem(item) {
  if (!item?.typeId) return false;
  const t = item.typeId;
  return (
    t.includes("_sword") ||
    t.includes("_axe") ||
    t === "minecraft:bow" ||
    t === "minecraft:crossbow" ||
    t === "minecraft:trident"
  );
}

function extraSensitivePenalty(victim) {
  try {
    if (isAnimal(victim) || ANIMALES_SENSIBLES.has(victim.typeId)) return 2000;
    return 0;
  } catch {
    return 0;
  }
}

function markViolence(player) {
  if (!player) return;
  lastViolenceTick.set(player.id, TICK);
}

/** ===================== COMBO ===================== **/
function getComboState(playerId) {
  let st = combo.get(playerId);
  if (!st) {
    st = { lastTick: 0, hits: 0, kills: 0 };
    combo.set(playerId, st);
  }
  return st;
}

function bumpCombo(playerId, isKill) {
  const st = getComboState(playerId);
  if (TICK - st.lastTick > COMBO_WINDOW_TICKS) {
    st.hits = 0;
    st.kills = 0;
  }
  st.lastTick = TICK;
  if (isKill) st.kills++;
  else st.hits++;
}

function getComboMult(playerId, isKill) {
  const st = combo.get(playerId);
  if (!st || TICK - st.lastTick > COMBO_WINDOW_TICKS) return 1;

  let m = 1 + (isKill ? st.kills * COMBO_KILL_STEP : st.hits * COMBO_HIT_STEP);
  if (m > COMBO_MAX_MULT) m = COMBO_MAX_MULT;
  return m;
}

/** ===================== WITNESSES (CACHED) ===================== **/
function hasWitnessesCached(attacker, victim) {
  if (!ENABLE_WITNESSES) return false;

  const id = attacker.id;
  const cached = witnessCache.get(id);
  if (cached && (TICK - cached.lastCheck) < WITNESS_CACHE_TICKS) return cached.has;

  let found = false;
  try {
    const dim = victim.dimension;
    const nearby = dim.getEntities({ location: victim.location, maxDistance: WITNESS_RADIUS });

    for (const ent of nearby) {
      if (!ent) continue;
      if (ent.typeId === "minecraft:player") continue;
      if (isInanimate(ent)) continue;
      if (isVillager(ent) || isAnimal(ent)) { found = true; break; }
    }
  } catch {
    found = false;
  }

  witnessCache.set(id, { lastCheck: TICK, has: found });
  return found;
}

/** ===================== FORGIVE ===================== **/
function getForgiveState(playerId) {
  let st = forgive.get(playerId);
  if (!st) {
    st = { active: false, pendingAmount: 0, untilTick: 0 };
    forgive.set(playerId, st);
  }
  return st;
}

function startForgive(playerId, amountAdded) {
  const st = getForgiveState(playerId);
  st.active = true;
  st.pendingAmount = amountAdded | 0;
  st.untilTick = TICK + FORGIVE_WINDOW_TICKS;
}

function cancelForgive(playerId) {
  const st = forgive.get(playerId);
  if (!st) return;
  st.active = false;
  st.pendingAmount = 0;
  st.untilTick = 0;
}

/** ===================== FEED COOLDOWN ===================== **/
function canFeedReward(playerId) {
  const until = feedCooldown.get(playerId) || 0;
  return TICK >= until;
}
function setFeedCooldown(playerId) {
  feedCooldown.set(playerId, TICK + FEED_COOLDOWN_TICKS);
}

/** ===================== PACIFISM ===================== **/
function startPacifism(playerId) {
  pacifism.set(playerId, { active: true, untilTick: TICK + PACIFISM_WINDOW_TICKS });
}
function cancelPacifism(playerId) {
  const st = pacifism.get(playerId);
  if (st) st.active = false;
}

/** ===================== CLEAN PROG ===================== **/
function getCleanProg(playerId) {
  let st = cleanProg.get(playerId);
  if (!st) {
    st = { count: 0, cdUntil: 0 };
    cleanProg.set(playerId, st);
  }
  return st;
}

function canCountClean(playerId) {
  const st = getCleanProg(playerId);
  return TICK >= st.cdUntil;
}

function recordCleanBreak(playerId) {
  const st = getCleanProg(playerId);
  st.count++;

  if (st.count >= CLEAN_REQUIRED_BREAKS) {
    st.count = 0;
    st.cdUntil = TICK + CLEAN_GROUP_COOLDOWN_TICKS;
    return true;
  }
  return false;
}

/** ===================== DELTA BUFFER ===================== **/
function addDelta(player, amount, fromCombat, isIncrease) {
  if (!player) return;
  const id = player.id;

  delta.set(id, (delta.get(id) || 0) + (amount | 0));

  if (fromCombat) {
    let f = flags.get(id);
    if (!f) {
      f = { incCombat: false, decCombat: false };
      flags.set(id, f);
    }
    if (isIncrease) f.incCombat = true;
    else f.decCombat = true;
  }
}

/** ===================== MESSAGES (NO runCommand) ===================== **/
function rand1to5() {
  return 1 + Math.floor(Math.random() * 5);
}

function shouldShowInfo(player) {
  try {
    return !player.hasTag("fear_info");
  } catch {
    return true;
  }
}

function showIncrease(player) {
  if (!shouldShowInfo(player)) return;

  const n = rand1to5();
  try {
    player.onScreenDisplay.setActionBar({
      rawtext: [{ translate: `dungeon.langcode.insomnio${n}` }]
    });
  } catch {}

  try { player.addTag("luck_info"); } catch {}
}

function showDecrease(player) {
  if (!shouldShowInfo(player)) return;

  const n = rand1to5();
  try {
    player.onScreenDisplay.setActionBar({
      rawtext: [{ translate: `dungeon.langcode.fear${n}` }]
    });
  } catch {}

  try { player.addTag("luck_info"); } catch {}
}

function forceDecreaseMessage(player) {
  showDecrease(player);
}

/** ===================== FACTOR ===================== **/
function computeFactor(attacker, victim, damageSource, isKill, baseIsIncrease) {
  let factor = 1;

  const d = dist(attacker.location, victim.location);
  if (d >= RANGED_DISTANCE) factor += EXTRA_RANGED;

  if (isProjectileDamage(damageSource)) factor += EXTRA_PROJECTILE;

  const held = getHeldItem(attacker);
  if (isWeaponItem(held)) factor += EXTRA_WEAPON;
  else factor += EXTRA_BAREHAND;

  if (baseIsIncrease && hasWitnessesCached(attacker, victim)) factor += EXTRA_WITNESSES;

  factor *= getComboMult(attacker.id, isKill);

  if (factor < 0.1) factor = 0.1;
  if (factor > 6) factor = 6;

  return factor;
}

/** ===================== EVENTS ===================== **/
world.afterEvents.entityHurt.subscribe((e) => {
  const attacker = e.damageSource?.damagingEntity;
  const victim = e.hurtEntity;

  if (attacker?.typeId === "minecraft:player") {
    cancelPacifism(attacker.id);
    markViolence(attacker);
  }

  if (!attacker || attacker.typeId !== "minecraft:player") return;
  if (!victim) return;
  if (victim.typeId === "minecraft:player") return;
  if (isInanimate(victim)) return;

  const hostile = isHostile(victim);

  bumpCombo(attacker.id, false);

  if (hostile) {
    const factorDown = computeFactor(attacker, victim, e.damageSource, false, false);
    addDelta(attacker, Math.round(HIT_HOSTILE_DELTA * factorDown), true, false);
    return;
  }

  const base = HIT_PASSIVE_DELTA + extraSensitivePenalty(victim);
  const factorUp = computeFactor(attacker, victim, e.damageSource, false, true);
  const amtUp = Math.round(base * factorUp);

  addDelta(attacker, amtUp, true, true);

  const st = getForgiveState(attacker.id);
  if (!st.active) startForgive(attacker.id, amtUp);
  else cancelForgive(attacker.id);
});

world.afterEvents.entityDie.subscribe((e) => {
  const killer = e.damageSource?.damagingEntity;
  const victim = e.deadEntity;

  if (!killer || killer.typeId !== "minecraft:player") return;
  if (!victim) return;
  if (isInanimate(victim)) return;

  markViolence(killer);

  const hostile = isHostile(victim);

  bumpCombo(killer.id, true);

  if (hostile) {
    const factorDown = computeFactor(killer, victim, e.damageSource, true, false);
    addDelta(killer, Math.round(KILL_HOSTILE_DELTA * factorDown), true, false);
    return;
  }

  cancelForgive(killer.id);

  let penalty = KILL_PASSIVE_DELTA + extraSensitivePenalty(victim);
  if (isVillager(victim)) penalty += 91000;
  if (extraSensitivePenalty(victim)) penalty += 50000;

  const factorUp = computeFactor(killer, victim, e.damageSource, true, true);
  addDelta(killer, Math.round(penalty * factorUp), true, true);
});

world.afterEvents.entityHurt.subscribe((e) => {
  const victim = e.hurtEntity;
  if (!victim || victim.typeId !== "minecraft:player") return;

  const src = e.damageSource?.damagingEntity;

  if (PACIFISM_ONLY_IF_HOSTILE) {
    if (!src) return;
    if (!isHostile(src)) return;
  }

  const st = pacifism.get(victim.id);
  if (!st || !st.active) startPacifism(victim.id);
});

// Interact: dar comida a entidad => reduce
if (world.afterEvents.playerInteractWithEntity) {
  world.afterEvents.playerInteractWithEntity.subscribe((e) => {
    try {
      const player = e.player;
      const target = e.target;
      if (!player || !target) return;

      const held = getHeldItem(player);
      if (!held?.typeId) return;
      if (!FEED_ITEMS.has(held.typeId)) return;

      if (!canFeedReward(player.id)) return;

      addDelta(player, -FEED_REDEEM_AMOUNT, true, false);
      setFeedCooldown(player.id);

      forceDecreaseMessage(player);
    } catch {}
  });
}

// Break: romper 4 bloques basura => reduce, cooldown largo
if (world.afterEvents.playerBreakBlock) {
  world.afterEvents.playerBreakBlock.subscribe((e) => {
    try {
      const p = e.player;
      if (!p) return;

      const id = e.brokenBlockPermutation?.type?.id;
      if (!id || !CLEAN_BLOCKS.has(id)) return;

      if (CLEAN_REQUIRE_NO_VIOLENCE) {
        const lv = lastViolenceTick.get(p.id) || 0;
        if (TICK - lv < CLEAN_NO_VIOLENCE_TICKS) return;
      }

      if (!canCountClean(p.id)) return;
      if (!recordCleanBreak(p.id)) return;

      addDelta(p, -CLEAN_REDEEM_AMOUNT, true, false);
      forceDecreaseMessage(p);
    } catch {}
  });
}

/** ===================== TIMERS + FLUSH (ONE LOOP) ===================== **/
function applyForgiveAndPacifismTimers(players) {
  for (const p of players) {
    const id = p.id;

    // Forgive expire
    const fg = forgive.get(id);
    if (fg?.active && TICK >= fg.untilTick) {
      removeInsomnio(p, fg.pendingAmount);
      cancelForgive(id);
      forceDecreaseMessage(p);
    }

    // Pacifism expire
    const pc = pacifism.get(id);
    if (pc?.active && TICK >= pc.untilTick) {
      addDelta(p, -PACIFISM_REDEEM_AMOUNT, true, false);
      pc.active = false;
      forceDecreaseMessage(p);
    }
  }
}

function flushDeltas(players) {
  if (!ensureInsomnioObjective()) return;

  for (const p of players) {
    const id = p.id;
    let d = delta.get(id) || 0;
    if (!d) continue;

    if (d > 120000) d = 120000;
    if (d < -120000) d = -120000;

    if (d < 0) {
      const current = getInsomnio(p);
      const rf = redeemFactorFromScore(current);
      d = Math.round(d * rf);
      if (d === 0) d = -1;

      removeInsomnio(p, -d);
    } else {
      addInsomnio(p, d);
    }

    const f = flags.get(id);
    if (f) {
      if (d > 0 && f.incCombat) showIncrease(p);
      if (d < 0 && f.decCombat) showDecrease(p);
    }

    delta.set(id, 0);
    flags.delete(id);
  }
}

// todo cada 20 ticks: flush + timers (sin 3 loops separados)
system.runInterval(() => {
  const players = world.getPlayers();
  flushDeltas(players);
  applyForgiveAndPacifismTimers(players);
}, 20);
