// ---- 04_restrictions_runtime.js ----
// guardian_system source chunk
// Original lines: 3430-4169


// ====== Restrição: sem armadura e sem escudo antes da classe ======
function tryReturnOrDrop(player, item) {
  const c = getContainer(player);
  if (c) {
    const leftover = c.addItem(item);
    if (!leftover) return;
  }
  // fallback: dropar perto do player
  try {
    player.dimension.spawnItem(item, player.location);
  } catch (e) {}
}

function dropNearPlayer(player, item) {
  // Usado para itens proibidos (não devolve ao inventário para evitar loop).
  try {
    player.dimension.spawnItem(item, player.location);
  } catch (e) {}
}


function enforceNoArmorAndShieldBeforeClass(player) {
  if (isCreative(player)) return;
  if (player.hasTag(TAG_ESCOLHIDA)) return;

  const eq = _getEquippableSafe(player);
  if (!eq) return;

  const slots = [EquipmentSlot.Head, EquipmentSlot.Chest, EquipmentSlot.Legs, EquipmentSlot.Feet, EquipmentSlot.Offhand];
  let removed = 0;

  for (const s of slots) {
    const it = _getEquipmentSafe(player, s);
    if (!it) continue;

    const isShield = (s === EquipmentSlot.Offhand && it.typeId === "minecraft:shield");
    const isArmor = (s !== EquipmentSlot.Offhand);

    if (isShield || isArmor) {
      _setEquipmentSafe(player, s, undefined);
      tryReturnOrDrop(player, it);
      removed++;
    }
  }

  if (removed > 0) {
    try {
      if (!player.hasTag(TAG_WARN_NO_ARMOR)) {
        player.addTag(TAG_WARN_NO_ARMOR);
        system.run(() => {
          try { __LWJ_MUTE_MESSAGE__(player, _gUi("restrict.warn.need_class_armor")); } catch (e) {}
        });
        system.runTimeout(() => { try { player.removeTag(TAG_WARN_NO_ARMOR); } catch(e) {} }, 60);
      }
    } catch (e) {}
  }
}

function enforceWarriorNoBowArrow(player) {
  if (!classRestrictionsEnabled()) return;
  // (Mudança) Guerreiro(a) pode carregar arco/flechas, mas o USO é bloqueado via eventos.
  return;
}


function enforceArcherNoSwordShield(player) {
  if (!classRestrictionsEnabled()) return;
  // (Mudança) Arqueiro(a) pode carregar espada no inventário, mas NÃO pode manter escudo na segunda mão.
  const classe = getClasseInfo(player);
  if (!classe || classe.id !== "arqueiro") return;

  try {
    const eq = _getEquippableSafe(player);
    const inv = getContainer(player);
    if (!eq) return;

    const off = eq.getEquipment?.(EquipmentSlot.Offhand);
    if (off && off.typeId === "minecraft:shield") {
      try { _setEquipmentSafe(player, EquipmentSlot.Offhand, undefined); } catch (e) {}
      // tenta devolver pro inventário, senão dropa
      if (inv) {
        try {
          const leftover = inv.addItem(off);
          if (leftover) dropNearPlayer(player, off);
        } catch (e) { dropNearPlayer(player, off); }
      } else {
        dropNearPlayer(player, off);
      }

      // aviso com cooldown simples
      try {
        if (!player.hasTag(TAG_WARN_NO_SWORD)) {
          player.addTag(TAG_WARN_NO_SWORD);
          system.run(() => {
            try {
              __LWJ_MUTE_MESSAGE__(player, _gUi("restrict.warn.archer_no_shield"));
            } catch (e) {}
          });
          system.runTimeout(() => { try { player.removeTag(TAG_WARN_NO_SWORD); } catch(e) {} }, 60);
        }
      } catch (e) {}
    }
  } catch (e) {}
}


function enforceArcherOnlyLeatherArmor(player) {
  if (!classRestrictionsEnabled()) return;
  // Arqueiro(a) só pode EQUIPAR armadura de couro (leather_*). Outras armaduras são removidas dos slots.
  const classe = getClasseInfo(player);
  if (!classe || classe.id !== "arqueiro") return;

  const isArmorPiece = (id) => {
    if (typeof id !== "string") return false;
    return id.endsWith("_helmet") || id.endsWith("_chestplate") || id.endsWith("_leggings") || id.endsWith("_boots");
  };
  const isLeather = (id) => typeof id === "string" && id.startsWith("minecraft:leather_");
  let removed = 0;

  try {
    const eq = _getEquippableSafe(player);
    const inv = getContainer(player);
    if (eq) {
      const slots = [EquipmentSlot.Head, EquipmentSlot.Chest, EquipmentSlot.Legs, EquipmentSlot.Feet];
      for (const s of slots) {
        const it = _getEquipmentSafe(player, s);
        if (!it) continue;
        const id = it.typeId;
        if (isArmorPiece(id) && !isLeather(id)) {
          try { _setEquipmentSafe(player, s, undefined); } catch (e) {}
          // tenta devolver pro inventário, senão dropa
          if (inv) {
            try {
              const leftover = inv.addItem(it);
              if (leftover) dropNearPlayer(player, it);
            } catch (e) { dropNearPlayer(player, it); }
          } else {
            dropNearPlayer(player, it);
          }
          removed++;
        }
      }
    }
  } catch (e) {}

  if (removed > 0) {
    try {
      if (!player.hasTag(TAG_WARN_NO_ARMOR)) {
        player.addTag(TAG_WARN_NO_ARMOR);
        system.run(() => {
          try {
            __LWJ_MUTE_MESSAGE__(player, _gUi("restrict.warn.archer_leather_only"));
          } catch (e) {}
        });
        system.runTimeout(() => { try { player.removeTag(TAG_WARN_NO_ARMOR); } catch(e) {} }, 60);
      }
    } catch (e) {}
  }
}


function isArmorTypeId(id) {
  if (typeof id !== "string") return false;
  return id.endsWith("_helmet") || id.endsWith("_chestplate") || id.endsWith("_leggings") || id.endsWith("_boots");
}

function enforceWarriorNoLowArmor(player) {
  if (!classRestrictionsEnabled()) return;
  // Guerreiro(a) só pode EQUIPAR armaduras de ferro ou superiores.
  const classe = getClasseInfo(player);
  if (!classe || classe.id !== "guerreiro") return;

  try {
    const eq = _getEquippableSafe(player);
    const inv = getContainer(player);
    if (!eq) return;

    const slots = [EquipmentSlot.Head, EquipmentSlot.Chest, EquipmentSlot.Legs, EquipmentSlot.Feet];
    let removed = 0;

    for (const s of slots) {
      const it = eq.getEquipment?.(s);
      if (!it) continue;

      const id = it.typeId ?? "";
      if (!isArmorTypeId(id)) continue;

      const low = id.startsWith("minecraft:leather_") || id.startsWith("minecraft:golden_");
      if (!low) continue;

      // Remove do slot e devolve ao inventário (ou dropa se estiver cheio).
      try { _setEquipmentSafe(player, s, undefined); } catch (e) {}
      if (inv) {
        try {
          const leftover = inv.addItem(it);
          if (leftover) dropNearPlayer(player, it);
        } catch (e) { dropNearPlayer(player, it); }
      } else {
        dropNearPlayer(player, it);
      }
      removed++;
    }

    if (removed > 0) {
      if (!player.hasTag(TAG_WARN_WARRIOR_ARMOR)) {
        player.addTag(TAG_WARN_WARRIOR_ARMOR);
        system.run(() => {
          try {
            __LWJ_MUTE_MESSAGE__(player, _gUi("restrict.warn.warrior_iron_plus"));
          } catch (e) {}
        });
        system.runTimeout(() => { try { player.removeTag(TAG_WARN_WARRIOR_ARMOR); } catch(e) {} }, 80);
      }
    }
  } catch (e) {}
}


function enforceMageNoEquippedArmor(player) {
  if (!classRestrictionsEnabled()) return;
  // Mago(a) não pode EQUIPAR armadura (pode carregar no inventário).
  const classe = getClasseInfo(player);
  if (!classe || classe.id !== "mago") return;

  try {
    const eq = _getEquippableSafe(player);
    const inv = getContainer(player);
    if (!eq || !inv) return;

    const slots = [EquipmentSlot.Head, EquipmentSlot.Chest, EquipmentSlot.Legs, EquipmentSlot.Feet];
    let removed = 0;
    for (const s of slots) {
      const it = _getEquipmentSafe(player, s);
      if (!it) continue;
      const id = it.typeId;
      if (!isArmorTypeId(id)) continue;

      // Remove do slot e devolve ao inventário (ou dropa se estiver cheio).
      try { _setEquipmentSafe(player, s, undefined); } catch (e) {}
      try {
        const leftover = inv.addItem(it);
        if (leftover) {
          try { player.dimension.spawnItem(it, player.location); } catch (e) {}
        }
      } catch (e) {}
      removed++;
    }

    if (removed > 0) {
      try {
        if (!player.hasTag(TAG_WARN_NO_ARMOR)) {
          player.addTag(TAG_WARN_NO_ARMOR);
          system.run(() => {
            try { __LWJ_MUTE_MESSAGE__(player, _gUi("restrict.warn.mage_no_armor")); } catch (e) {}
          });
          system.runTimeout(() => { try { player.removeTag(TAG_WARN_NO_ARMOR); } catch(e) {} }, 60);
        }
      } catch (e) {}
    }
  } catch (e) {}
}

// ====== Eventos ======

// Bloqueio opcional de destruição de blocos por criaturas (estilo mobGriefing).
function tentarBloquearExplosao(ev) {
  if (!mobGriefBloqueado()) return;
  const src = ev?.source;
  const tid = src?.typeId ?? "";
  const isMob = tid === "minecraft:creeper" || tid === "minecraft:ghast" || tid === "minecraft:wither" || tid.includes("fireball") || tid.includes("wither_skull");
  if (!isMob) return;
  // Tenta, sem quebrar entre versões, desabilitar dano em blocos.
  try { ev.breaksBlocks = false; } catch (e) {}
  try { ev.impactedBlocks = []; } catch (e) {}
  try { ev.setImpactedBlocks([]); } catch (e) {}
}

try {
  if (world?.beforeEvents?.explosion?.subscribe) {
    world.beforeEvents.explosion.subscribe((ev) => {
      try { tentarBloquearExplosao(ev); } catch (e) {}
    });
  }
} catch (e) {}


// ====== Bloqueio de USO de itens que não pertencem à classe (sem remover do inventário) ======
function classeOwnerDoItem(typeId) {
  if (typeof typeId !== "string") return null;
  const lower = typeId.toLowerCase();
  const isSpear = lower.includes("spear") || lower.includes("lanca");

  // Arqueiro(a)
  if (typeId === "minecraft:bow" || typeId === "minecraft:crossbow" || typeId === "minecraft:arrow" || typeId.startsWith("minecraft:leather_")) {
    return CLASSES.find(c => c.id === "arqueiro") ?? null;
  }

  // Guerreiro(a) (lanças, armas corpo-a-corpo, escudo e armaduras)
  if (
    isSpear ||
    typeId === "minecraft:shield" ||
    typeId.endsWith("_sword") ||
    (typeId.endsWith("_helmet") || typeId.endsWith("_chestplate") || typeId.endsWith("_leggings") || typeId.endsWith("_boots"))
  ) {
    return CLASSES.find(c => c.id === "guerreiro") ?? null;
  }

  // Mago(a) (tridente)
  if (typeId === "minecraft:trident") {
    return CLASSES.find(c => c.id === "mago") ?? null;
  }

  return null;
}


function blockedItemNameTranslateKey(itemId) {
  switch (String(itemId ?? "")) {
    case "minecraft:bow": return "lwj.guardian.restrict.item.bow";
    case "minecraft:crossbow": return "lwj.guardian.restrict.item.crossbow";
    case "minecraft:shield": return "lwj.guardian.restrict.item.shield";
    case "minecraft:trident": return "lwj.guardian.restrict.item.trident";
    default: break;
  }
  const raw = String(itemId ?? "item").split(":").pop() ?? "item";
  if (raw.endsWith("_sword")) return "lwj.guardian.restrict.item.sword";
  if (raw.includes("spear") || raw.includes("lanca")) return "lwj.guardian.restrict.item.spear";
  return null;
}

function blockedItemNameRaw(itemId) {
  const key = blockedItemNameTranslateKey(itemId);
  if (key) return _rmTranslate(key);
  const raw = String(itemId ?? "item").split(":").pop() ?? "item";
  return _rmText(raw.replaceAll("_", " "));
}

function guardianClassTranslateKey(classId) {
  switch (String(classId ?? "")) {
    case "guerreiro": return "lwj.guardian.class.warrior";
    case "arqueiro": return "lwj.guardian.class.archer";
    case "mago": return "lwj.guardian.class.mage";
    default: return null;
  }
}

function guardianClassDisplayRaw(info, fallbackKey) {
  if (info?.id) {
    const key = guardianClassTranslateKey(info.id);
    if (key) return _rmJoin(`${info.emoji ?? ""} `.trimStart(), _rmTranslate(key));
    const fallback = String(info?.fallbackName ?? info?.id ?? "").trim();
    if (fallback) return _rmText(`${info.emoji ?? ""} ${fallback}`.trim());
  }
  return fallbackKey ? _rmTranslate(fallbackKey) : _rmText("?");
}

function avisarUsoBloqueado(player, itemId) {
  try {
    const minhaClasse = getClasseInfo(player);
    const dono = classeOwnerDoItem(itemId);
    const msg = { rawtext: [] };
    _rmPush(
      msg,
      "§c",
      blockedItemNameRaw(itemId),
      " ",
      _rmTranslate("lwj.guardian.restrict.not_for_current_class"),
      " (",
      guardianClassDisplayRaw(minhaClasse, "lwj.guardian.restrict.current_class_generic"),
      "). §7",
      _rmTranslate("lwj.guardian.restrict.cannot_use_now"),
      " §e",
      _rmTranslate("lwj.guardian.restrict.store_or_give"),
      " ",
      guardianClassDisplayRaw(dono, "lwj.guardian.restrict.correct_class_generic"),
      "."
    );
    __LWJ_MUTE_MESSAGE__(player, msg);
  } catch (e) {}
}

// beforeEvents: cancelar ação (usa system.run para mensagens com segurança)

try {
  if (world?.beforeEvents?.itemUse?.subscribe) {
    world.beforeEvents.itemUse.subscribe((ev) => {
      try {
        const player = ev.source;
        const item = ev.itemStack;
        if (!player || !item) return;
        _markGuardianPlayerDirty(player);

        const classe = getClasseInfo(player);
        if (!classe) return;

        if (item.typeId === "minecraft:trident" && classe.id === "mago" && _isSneakingSafe(player)) {
          if (tentarConjurarTempestade(player, item, ev)) return;
        }

        if (!classRestrictionsEnabled()) return;

        // Guerreiro(a): bloqueia arco/crossbow
        if (classe.id === "guerreiro" && (item.typeId === "minecraft:bow" || item.typeId === "minecraft:crossbow")) {
          ev.cancel = true;
          if (!player.hasTag(TAG_WARN_NO_BOW)) {
            system.run(() => {
              try {
                if (player.hasTag(TAG_WARN_NO_BOW)) return;
                player.addTag(TAG_WARN_NO_BOW);
                avisarUsoBloqueado(player, item.typeId);
                system.runTimeout(() => { try { player.removeTag(TAG_WARN_NO_BOW); } catch(e) {} }, 40);
              } catch (e) {}
            });
          }
          return;
        }

        // Arqueiro(a): bloqueia escudo (mesmo se estiver na mão principal)
        if (classe.id !== "guerreiro" && item.typeId === "minecraft:shield") {
          ev.cancel = true;
          if (!player.hasTag(TAG_WARN_NO_SWORD)) {
            system.run(() => {
              try {
                if (player.hasTag(TAG_WARN_NO_SWORD)) return;
                player.addTag(TAG_WARN_NO_SWORD);
                avisarUsoBloqueado(player, item.typeId);
                system.runTimeout(() => { try { player.removeTag(TAG_WARN_NO_SWORD); } catch(e) {} }, 40);
              } catch (e) {}
            });
          }
          return;
        }

        // Tridente/Lança: cancelar clique direito apenas quando o item for proibido para a classe.
        // (Ex.: Guerreiro(a) sem tridente; Mago(a) sem lança; Arqueiro(a) sem tridente/lança)
        const tLower = String(item.typeId).toLowerCase();
        const isTridentUse = item.typeId === "minecraft:trident";
        const isSpearUse = tLower.includes("spear") || tLower.includes("lanca");
        if ((isTridentUse || isSpearUse) && isProibidoParaClasse(classe.id, item.typeId)) {
          ev.cancel = true;
          if (!player.hasTag(TAG_WARN_NO_SWORD)) {
            system.run(() => {
              try {
                if (player.hasTag(TAG_WARN_NO_SWORD)) return;
                player.addTag(TAG_WARN_NO_SWORD);
                avisarUsoBloqueado(player, item.typeId);
                system.runTimeout(() => { try { player.removeTag(TAG_WARN_NO_SWORD); } catch(e) {} }, 40);
              } catch (e) {}
            });
          }
          return;
        }


      } catch (e) {}
    });

function _applyArcherHookPull(player, hit) {
  try {
    if (!(player instanceof Player) || !hit) return;
    const from = player.location;
    const dx = hit.x - from.x;
    const rawDy = (hit.y + HOOK_TARGET_Y_OFFSET) - from.y;
    const dy = Math.max(-HOOK_MAX_FALL_DELTA, Math.min(HOOK_MAX_RISE_DELTA, rawDy));
    const dz = hit.z - from.z;
    const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
    if (!isFinite(dist) || dist < HOOK_STOP_DISTANCE) return;

    const nx = dx / dist;
    const ny = dy / dist;
    const nz = dz / dist;

    let pull = Math.min(HOOK_MAX_FORCE, Math.max(HOOK_MIN_FORCE, dist * HOOK_DISTANCE_SCALE));
    if (dist < HOOK_CLOSE_RANGE) pull *= HOOK_CLOSE_FORCE_MULT;

    const upward = HOOK_UPWARD_BASE + Math.max(0, ny) * HOOK_UPWARD_EXTRA;
    const vertical = Math.min(HOOK_MAX_VERTICAL_IMPULSE, (ny * pull) + upward);

    player.applyImpulse({
      x: nx * pull,
      y: vertical,
      z: nz * pull
    });
  } catch (e) {}
}

function _startArcherHookPull(player, hit) {
  for (let step = 0; step < HOOK_PULL_STEPS; step++) {
    system.runTimeout(() => {
      try {
        _applyArcherHookPull(player, hit);
      } catch (e) {}
    }, step);
  }
}

// ====== Gancho do Arqueiro(a) (arco e flecha) ======
// Ativação: segure agachar (sneak) ao soltar o arco. Quando a flecha acertar um bloco, você é puxado.
try {
  world.afterEvents.itemStopUse.subscribe((ev) => {
    const p = ev.source;
    const item = ev.itemStack;
    if (!p || !item) return;
    if (!(p instanceof Player)) return;
    if (item.typeId !== "minecraft:bow") return;
    const classe = getClasseInfo(p);
    if (!classe || classe.id !== "arqueiro") return;
    try { if (!p.isSneaking) return; } catch (e) { return; }
    if (p.hasTag?.(TAG_HOOK_CD)) return;
    p.addTag?.(TAG_HOOK_READY);
    system.runTimeout(() => { try { p.removeTag?.(TAG_HOOK_READY); } catch (e) {} }, 80);
  });

  if (world?.afterEvents?.itemStartUse?.subscribe) {
    world.afterEvents.itemStartUse.subscribe((ev) => {
      try {
        const p = ev?.source;
        const item = ev?.itemStack;
        if (!(p instanceof Player) || !item) return;
        if (item.typeId !== "minecraft:trident") return;
        if (!_isSneakingSafe(p)) return;
        tentarConjurarTempestade(p, item);
      } catch (e) {}
    });
  }

} catch (e) {}

try {
  world.afterEvents.projectileHitBlock.subscribe((ev) => {
    const proj = ev.projectile;
    const src = ev.source;
    if (!proj || !src) return;
    if (!(src instanceof Player)) return;
    if (proj.typeId !== "minecraft:arrow") return;
    const classe = getClasseInfo(src);
    if (!classe || classe.id !== "arqueiro") return;
    if (!src.hasTag?.(TAG_HOOK_READY)) return;
    if (src.hasTag?.(TAG_HOOK_CD)) return;

    try { src.removeTag?.(TAG_HOOK_READY); } catch (e) {}
    try { src.addTag?.(TAG_HOOK_CD); } catch (e) {}
    system.runTimeout(() => { try { src.removeTag?.(TAG_HOOK_CD); } catch (e) {} }, 15);

    system.run(() => {
      try {
        _startArcherHookPull(src, ev.location);
      } catch (e) {}
    });
  });
} catch (e) {}

  }
} catch (e) {}


// ====== Bloqueio de "uso" (ataque/segurar) de itens proibidos por classe ======
// Observação nerd: espada/tridente não são "itemUse" (não é clique direito).
// Então, sem depender de APIs Beta, a forma mais confiável é impedir que o item fique na mão,
// movendo-o para outro slot (ou dropando se o inventário estiver cheio).

function isProibidoParaClasse(classeId, typeId) {
  if (!classRestrictionsEnabled()) return false;
  if (typeof typeId !== "string") return false;
  const lower = typeId.toLowerCase();
  const isSword = typeId.endsWith("_sword");
  const isBow = typeId === "minecraft:bow" || typeId === "minecraft:crossbow";
  const isShield = typeId === "minecraft:shield";
  const isTrident = typeId === "minecraft:trident";
  const isSpear = lower.includes("spear") || lower.includes("lanca");

  if (classeId === "guerreiro") {
    // Guerreiro(a): sem arco/crossbow e sem tridente (lança é permitida)
    return isBow || isTrident;
  }
  if (classeId === "arqueiro") {
    // Arqueiro(a): sem espada, escudo, tridente e lança
    return isSword || isShield || isTrident || isSpear;
  }
  if (classeId === "mago") {
    // Mago(a): sem espada/arco/escudo e sem lança (tridente é permitido)
    return isSword || isBow || isShield || isSpear;
  }
  return false;
}

function moverItemParaOutroSlotOuDropar(player, fromIndex, item) {
  const inv = getContainer(player);
  if (!inv || !item) return;

  // procura slot vazio (preferir fora da hotbar)
  let target = -1;
  const preferStart = 9;
  try {
    for (let i = preferStart; i < inv.size; i++) {
      if (i === fromIndex) continue;
      const cur = inv.getItem(i);
      if (!cur) { target = i; break; }
    }
    if (target < 0) {
      for (let i = 0; i < inv.size; i++) {
        if (i === fromIndex) continue;
        const cur = inv.getItem(i);
        if (!cur) { target = i; break; }
      }
    }
  } catch (e) {}

  // tira do slot original
  try { inv.setItem(fromIndex, undefined); } catch (e) {}

  if (target >= 0) {
    try { inv.setItem(target, item); return; } catch (e) {}
  }

  // sem espaço: dropa perto do jogador
  dropNearPlayer(player, item);
}

function avisarUsoBloqueadoComCooldown(player, itemId, tag, cooldownTicks) {
  try {
    if (player.hasTag(tag)) return;
    player.addTag(tag);
    system.run(() => avisarUsoBloqueado(player, itemId));
    system.runTimeout(() => { try { player.removeTag(tag); } catch(e) {} }, cooldownTicks);
  } catch (e) {}
}

function enforceItensProibidosNaMao(player) {
  if (!classRestrictionsEnabled()) return;
  const classe = getClasseInfo(player);
  if (!classe) return;

  const inv = getContainer(player);
  if (!inv) return;

  const idx = _getSelectedSlotIndexSafe(player);
  const it = _getContainerItemSafe(inv, idx);
  const id = it?.typeId ?? "";

  if (it && isProibidoParaClasse(classe.id, id)) {
    moverItemParaOutroSlotOuDropar(player, idx, it);
    const tag = (id === "minecraft:bow" || id === "minecraft:crossbow") ? TAG_WARN_NO_BOW : TAG_WARN_NO_SWORD;
    avisarUsoBloqueadoComCooldown(player, id, tag, 40);
  }
}

function enforceOffhandPorClasse(player) {
  if (!classRestrictionsEnabled()) return;
  const classe = getClasseInfo(player);
  if (!classe) return;

  try {
    const eq = _getEquippableSafe(player);
    if (!eq) return;
    const off = eq.getEquipment?.(EquipmentSlot.Offhand);
    if (!off) return;

    const id = off.typeId ?? "";
    // Escudo só pode ficar na segunda mão do Guerreiro(a)
    if (id === "minecraft:shield" && classe.id !== "guerreiro") {
      try { _setEquipmentSafe(player, EquipmentSlot.Offhand, undefined); } catch (e) {}

      const inv = getContainer(player);
      if (inv) {
        try {
          const leftover = inv.addItem(off);
          if (leftover) dropNearPlayer(player, off);
        } catch (e) { dropNearPlayer(player, off); }
      } else {
        dropNearPlayer(player, off);
      }

      avisarUsoBloqueadoComCooldown(player, id, TAG_WARN_NO_SWORD, 60);
    }
  } catch (e) {}
}

// Evento imediato (quando troca o slot da hotbar)
try {
  const ev = world?.afterEvents?.playerHotbarSelectedSlotChange;
  if (ev?.subscribe) {
    ev.subscribe((e) => {
      try { _markGuardianPlayerDirty(e.player); } catch (err) {}
      try { enforceItensProibidosNaMao(e.player); } catch (err) {}
    });
  }
} catch (e) {}


world.afterEvents.playerInteractWithEntity.subscribe((ev) => {
  const player = ev.player;
  const entity = ev.target;
  if (!player || !entity) return;

  // Interação híbrida do Guardião:
  // - clique normal: mantém a UI principal
  // - agachado(a): deixa a camada JSON tratar as respostas simples e o estado básico
  if (entity.typeId === NPC_ID) {
    try { entity.nameTag = npcNameFor(player); } catch (e) {}

    let sneaking = false;
    try { sneaking = player.isSneaking === true; } catch (e) { sneaking = false; }

    if (sneaking) {
      return;
    }

    try {
      system.run(() => {
        try { entity.triggerEvent("lorewarden:basic_reset_idle"); } catch (err) {}
      });
    } catch (e) {}

    _queuePlayerUiTask(player, "guardian_npc", (resolvedPlayer) => {
      try { entity.nameTag = npcNameFor(resolvedPlayer); } catch (err) {}
      return iniciarConversaNpc(resolvedPlayer, entity);
    }, { cooldownTicks: 6, replace: true });
    return;
  }
});

// Reaplica efeitos permanentes do NPC (morte/teleporte pode limpar efeitos)
function abrirConfigPrimeiraEntrada(player, tentativas = 0) {
  return;
}


system.runInterval(() => {
  try {
    for (const player of world.getAllPlayers()) {
      enforceDimensionLockForUnfixedPlayer(player);
    }
  } catch (e) {}
}, 20);

function _scheduleFastVillageNpcSpawnChecks(player) {
  try {
    // Tentativas curtas na entrada: cobre chunks que ainda estão carregando sem esperar o ciclo longo.
    for (const delay of [1, 10, 30, 60, 100]) {
      try {
        system.runTimeout(() => { try { garantirNpcDeVilaPertoDoJogador(player); } catch (e) {} }, delay);
      } catch (e) {}
    }
  } catch (e) {}
}

world.afterEvents.playerSpawn.subscribe((ev) => {
  const p = ev.player;
  if (!p) return;

  // Spawn do NPC apenas pelas vilas carregadas pelo jogador.
  // O antigo spawn inicial perto do jogador foi removido.
  try {
    if (typeof ev.initialSpawn !== "boolean" || ev.initialSpawn) {
      _scheduleFastVillageNpcSpawnChecks(p);
    }
  } catch (e) {}

    system.runTimeout(() => {
    // Configurações disponíveis pelo menu do NPC (Guardião).
    // Defaults: partículas do efeito DESLIGADAS e anti-destruição por explosão LIGADO.
    try {
      if (!p.hasTag(TAG_PARTICLES_PREF_SET)) {
        p.addTag(TAG_EFEITO_NO_PARTICLES);
        p.addTag(TAG_PARTICLES_PREF_SET);
      }
    } catch (e) {}
        sincronizarEfeitosClasse(p);
applySavedEffectsSafely(p);

}, 60);
});

// Scheduler central do runtime do Guardião.
// Mantém apenas dois timers ativos:
// - 1 tick: controle fino do tempo do modo rústico
// - 10 ticks: follow do NPC + fiscalizações + tarefas de manutenção em múltiplos de 10
let _guardianRuntimeHeartbeat10 = 0;
const _guardianDirtyPlayers = new Set();
let _guardianSparseSweepCursor = 0;

function _guardianPlayerKey(player) {
  try { return String(player?.id ?? player?.name ?? ""); } catch (e) { return ""; }
}

function _markGuardianPlayerDirty(player) {
  const key = _guardianPlayerKey(player);
  if (!key) return;
  _guardianDirtyPlayers.add(key);
}

function _collectGuardianTickPlayers(players, forceFull = false, fallbackCount = 2) {
  const list = Array.isArray(players) ? players : [];
  if (!list.length) {
    _guardianDirtyPlayers.clear();
    return [];
  }

  if (forceFull) {
    _guardianDirtyPlayers.clear();
    return list;
  }

  const byKey = new Map();
  for (const player of list) {
    const key = _guardianPlayerKey(player);
    if (key) byKey.set(key, player);
  }

  for (const key of _guardianDirtyPlayers) {
    if (!byKey.has(key)) _guardianDirtyPlayers.delete(key);
  }

  const targets = [];
  const seen = new Set();
  for (const key of _guardianDirtyPlayers) {
    const player = byKey.get(key);
    if (!player || seen.has(key)) continue;
    seen.add(key);
    targets.push(player);
  }
  _guardianDirtyPlayers.clear();

  const extra = Math.max(0, Math.min(list.length, fallbackCount | 0));
  for (let i = 0; i < extra; i++) {
    const player = list[_guardianSparseSweepCursor % list.length];
    _guardianSparseSweepCursor = (_guardianSparseSweepCursor + 1) % Math.max(1, list.length);
    const key = _guardianPlayerKey(player);
    if (!key || seen.has(key)) continue;
    seen.add(key);
    targets.push(player);
  }

  return targets;
}

function _guardianRuntimeTick10() {
  _guardianRuntimeHeartbeat10 += 1;

  let players = [];
  try { players = world.getPlayers(); } catch (e) { players = []; }

  const forceFullSweep = ((_guardianRuntimeHeartbeat10 % 12) === 0);
  const fastTargets = _collectGuardianTickPlayers(players, forceFullSweep, 2);

  // guardianFollowTick roda em intervalo dedicado mais rápido.

  // Fiscal rápido: impedir que itens proibidos fiquem na mão / na segunda mão.
  // Fora dos full sweeps, roda apenas em jogadores afetados recentemente + uma varredura esparsa.
  for (const p of fastTargets) {
    try { enforceItensProibidosNaMao(p); } catch (e) {}
    try { enforceOffhandPorClasse(p); } catch (e) {}
  }

  // Enforcement geral de equipamento e restrições de classe a cada 20 ticks.
  // Também prioriza jogadores afetados recentemente e mantém full sweep periódico como rede de segurança.
  if ((_guardianRuntimeHeartbeat10 % 2) === 0) {
    const restrictionTargets = forceFullSweep ? players : fastTargets;
    for (const p of restrictionTargets) {
      try { enforceNoArmorAndShieldBeforeClass(p); } catch (e) {}
      try { enforceWarriorNoBowArrow(p); } catch (e) {}
      try { enforceArcherNoSwordShield(p); } catch (e) {}
      try { enforceArcherOnlyLeatherArmor(p); } catch (e) {}
      try { enforceMageNoEquippedArmor(p); } catch (e) {}
      try { enforceWarriorNoLowArmor(p); } catch (e) {}
    }
  }

  // Busca de vila/NPC mais rápida: a cada 50 ticks, sem esperar o ciclo de manutenção.
  if ((_guardianRuntimeHeartbeat10 % 5) === 0) {
    try { _tickVillageNpcSpawner(); } catch (e) {}
  }

  // Manutenção leve a cada 200 ticks.
  if ((_guardianRuntimeHeartbeat10 % 20) === 0) {
    for (const p of players) {
      try { applySavedEffectsSafely(p); } catch (e) {}
      try { ensureManualBook(p); } catch (e) {}
    }
  }

  // Limpeza de caches a cada 1200 ticks.
  if ((_guardianRuntimeHeartbeat10 % 120) === 0) {
    try { _cleanupGuardianCaches(); } catch (e) {}
    try { _cleanupVillageAnchorCache(); } catch (e) {}

    // Evita crescimento infinito do contador em mundos de longa duração.
    _guardianRuntimeHeartbeat10 = 0;
  }
}

try {
  if (world?.beforeEvents?.playerLeave?.subscribe) {
    world.beforeEvents.playerLeave.subscribe((ev) => {
      try {
        const pid = _safeStr(ev?.playerId ?? ev?.playerName ?? "");
        if (!pid) return;
        _manualEnsureLastTry.delete(pid);
        rjClearPlayerClassCache(pid);
        _effectApplyBusy.delete(pid);
        _guardianDirtyPlayers.delete(pid);
      } catch (e) {}
    });
  }
} catch (e) { rjDebugWarn("guardian_system:hooks", "Failed to register one or more hook runtime listeners.", e); }

// Garantir que o Manual da Classe não se perca (reentrega se faltar)
try {
  world.afterEvents.playerSpawn.subscribe((ev) => {
    const p = ev.player;
    if (!p) return;
    _markGuardianPlayerDirty(p);
    system.runTimeout(() => { try { ensureManualBook(p); } catch (e) {} }, 1);
  });
} catch (e) { rjDebugWarn("guardian_system:manualBook", "Failed to register manual book restore on player spawn.", e); }


// Controle de tempo do dia/noite:
// - a cada tick, puxa o tempo um pouquinho pra trás para ficar mais lento
system.runInterval(() => {
  try {
    ajustarTempoParaModoTick();
  } catch (e) {
    rjDebugError("guardian_system:timeTick", e, "Failed to process day/night adjustment tick.");
  }
}, 1);

system.runInterval(() => {
  try {
    guardianFollowTick();
  } catch (e) {
    rjDebugError("guardian_system:guardianFollowFast", e, "Failed during fast guardian follow tick.");
  }
}, 10);

system.runInterval(() => {
  try {
    _guardianRuntimeTick10();
  } catch (e) {
    rjDebugError("guardian_system:runtimeTick10", e, "Failed during consolidated guardian runtime tick.");
  }
}, 10);




// ============================================================================
// Reformulação do item "Configurações" -> Painel do Guardião (preferências editáveis)
// - Preferências do jogador: sempre editáveis

