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

const MAX_INSOMNIO = 120000;

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 HIT_HOSTILE_DELTA = -20;
const HIT_PASSIVE_DELTA = 500;
const KILL_HOSTILE_DELTA = -2000;
const KILL_PASSIVE_DELTA = 5000;

const EXTRA_PROJECTILE = 0.25;
const EXTRA_WEAPON = 0.15;
const EXTRA_BAREHAND = -0.10;

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"
]);

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 }

let OBJ_INSOMNIO = null;

function ensureInsomnioObjective() {
  if (OBJ_INSOMNIO) return OBJ_INSOMNIO;
  try {
    const sb = world.scoreboard;
    let obj = sb.getObjective("insomnio");
    if (!obj) obj = sb.addObjective("insomnio", "insomnio");
    OBJ_INSOMNIO = obj;
    return OBJ_INSOMNIO;
  } catch {
    return null;
  }
}

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) {
  setInsomnio(player, getInsomnio(player) + (amount | 0));
}

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

const typeClassCache = new Map(); // typeId -> bitmask
const TC = {
  HOSTILE: 1 << 0,
  INANIMATE: 1 << 1,
  ANIMAL: 1 << 2,
  VILLAGER: 1 << 3,
};

function classifyType(entity) {
  const t = entity?.typeId;
  if (!t) return 0;

  const cached = typeClassCache.get(t);
  if (cached !== undefined) return cached;

  let mask = 0;
  try {
    const fam = entity.getComponent("minecraft:type_family");
    if (fam) {
      if (fam.hasTypeFamily("monster") || fam.hasTypeFamily("boss")) mask |= TC.HOSTILE;
      if (fam.hasTypeFamily("inanimate")) mask |= TC.INANIMATE;
      if (fam.hasTypeFamily("animal")) mask |= TC.ANIMAL;
      if (fam.hasTypeFamily("villager")) mask |= TC.VILLAGER;
    }
  } catch {}

  typeClassCache.set(t, mask);
  return mask;
}

function isHostileFast(entity)   { return (classifyType(entity) & TC.HOSTILE) !== 0; }
function isInanimateFast(entity) { return (classifyType(entity) & TC.INANIMATE) !== 0; }
function isAnimalFast(entity)    { return (classifyType(entity) & TC.ANIMAL) !== 0; }
function isVillagerFast(entity)  { return (classifyType(entity) & TC.VILLAGER) !== 0; }

function extraSensitivePenaltyFast(victim) {
  const t = victim?.typeId;
  if (!t) return 0;
  if (ANIMALES_SENSIBLES.has(t)) return 2000;
  return isAnimalFast(victim) ? 2000 : 0;
}

system.runInterval(() => {
  TICK++;

  // retry objective
  if (!OBJ_INSOMNIO && (TICK % 20 === 0)) ensureInsomnioObjective();

  // limpieza ligera (cada 10s)
  if (TICK % 200 === 0) {
    const players = world.getPlayers();
    const alive = new Set(players.map(p => p.id));

    for (const k of combo.keys()) if (!alive.has(k)) combo.delete(k);
    for (const k of forgive.keys()) if (!alive.has(k)) forgive.delete(k);
    for (const k of feedCooldown.keys()) if (!alive.has(k)) feedCooldown.delete(k);
    for (const k of pacifism.keys()) if (!alive.has(k)) pacifism.delete(k);
    for (const k of cleanProg.keys()) if (!alive.has(k)) cleanProg.delete(k);
    for (const k of lastViolenceTick.keys()) if (!alive.has(k)) lastViolenceTick.delete(k);
    for (const k of delta.keys()) if (!alive.has(k)) delta.delete(k);
    for (const k of flags.keys()) if (!alive.has(k)) flags.delete(k);
  }
}, 1);

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 markViolence(player) {
  if (!player) return;
  lastViolenceTick.set(player.id, TICK);
}

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;
}

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;
}

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

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;
}

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;
}

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;
  }
}

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);
}

function computeFactor(attacker, victim, damageSource, isKill) {
  let factor = 1;

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

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

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

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

  return factor;
}

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 (victim?.typeId === "minecraft:player") {
    if (PACIFISM_ONLY_IF_HOSTILE) {
      if (!attacker) return;
      if (!isHostileFast(attacker)) return;
    }
    const st = pacifism.get(victim.id);
    if (!st || !st.active) startPacifism(victim.id);
    return;
  }

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

  const hostile = isHostileFast(victim);

  bumpCombo(attacker.id, false);

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

  const base = HIT_PASSIVE_DELTA + extraSensitivePenaltyFast(victim);
  const factorUp = computeFactor(attacker, victim, e.damageSource, false);
  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 (isInanimateFast(victim)) return;

  markViolence(killer);

  const hostile = isHostileFast(victim);

  bumpCombo(killer.id, true);

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

  cancelForgive(killer.id);

  const sensitive = extraSensitivePenaltyFast(victim);
  let penalty = KILL_PASSIVE_DELTA + sensitive;

  if (isVillagerFast(victim)) penalty += 91000;
  if (sensitive) penalty += 50000;

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

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 {}
  });
}

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 {}
  });
}

function applyForgiveAndPacifismTimers(players) {
  for (const p of players) {
    const id = p.id;

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

    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);
  }
}

system.runInterval(() => {
  const players = world.getPlayers();
  flushDeltas(players);
  applyForgiveAndPacifismTimers(players);
}, 40);
