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

const LOOKAT_DOT = 0.85;
const SPAWN_MIN_DIST = 20;
const SPAWN_MAX_DIST = 45;
const SPAWN_COOLDOWN_MS = 180000; // ~3 minutes overall cooldown (balanced rarity)
const REEKERANGRY_GRACE_MS = 60000;
const REEKERFLEE_LOST_SIGHT_MS = 500; // shortened to make reekerflee despawn a bit faster
const BUTTERFLY_DESPAWN_MS = 60000;

let lastSpawnTime = 0;
let hasSpawnedTick = false;
let firstPlayerTagged = false;

const playerKillScore = new Map();
const reekerAngryNoTarget = new Map();
const reekerFleeLostSight = new Map();
const infectedOnce = new Set();
const butterflyLastSeen = new Map();

function playerEyePos(p) {
  return { x: p.location.x, y: p.location.y + 1.62, z: p.location.z };
}
function vecNormalize(v) {
  const m = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
  return m === 0 ? { x: 0, y: 0, z: 0 } : { x: v.x / m, y: v.y / m, z: v.z / m };
}
function dot(a, b) {
  return a.x * b.x + a.y * b.y + a.z * b.z;
}
function safeRunEntityFunction(e, f) {
  try {
    e.runCommandAsync(`function ${f}`);
  } catch {}
}
function isTickAlive() {
  try {
    return [...world.getDimension("overworld").getEntities({ type: "rk:tick" })].length > 0;
  } catch {
    return false;
  }
}
function spawnTick() {
  const players = [...world.getPlayers()];
  if (players.length === 0) return;
  const pos = players[0].location;
  world.getDimension("overworld").spawnEntity("rk:tick", pos);
  hasSpawnedTick = true;
}
function addContainerScore(c, s) {
  if (!c) return;
  try {
    for (let i = 0; i < c.size; i++) {
      const item = c.getItem(i);
      if (!item) continue;
      const id = (item.typeId || "").toLowerCase();
      if (id.includes("netherite")) s.score += 5;
      else if (id.includes("diamond")) s.score += 4;
      else if (id.includes("copper")) s.score += 3.5;
      else if (id.includes("iron")) s.score += 3;
      else if (id.includes("chainmail") || id.includes("chain")) s.score += 2.5;
      else if (id.includes("gold")) s.score += 2;
      else if (id.includes("leather") || id.includes("stone")) s.score += 1;
      else if (id.includes("wood")) s.score += 0.5;
      if (id.includes("shield")) s.score += 2;
      if (id.includes("totem")) s.score += 3;
    }
  } catch {}
}
function calcGearScore(p) {
  const s = { score: 0 };
  try {
    const inv = p.getComponent("minecraft:inventory");
    if (inv?.container) addContainerScore(inv.container, s);
  } catch {}
  const comps = ["minecraft:equipment", "equipment", "minecraft:armor_container"];
  for (const n of comps) {
    try {
      const c = p.getComponent(n);
      if (c?.container) addContainerScore(c.container, s);
    } catch {}
  }
  return Math.max(0, s.score);
}

/* detect whether a given log block is part of a real tree (has leaves near/above it)
   This reduces false positives from player-built log structures (houses).
*/
function isTrueTreeLog(x, y, z, overworld) {
  try {
    for (let dy = 0; dy <= 6; dy++) {
      for (let dx = -2; dx <= 2; dx++) {
        for (let dz = -2; dz <= 2; dz++) {
          const b = overworld.getBlock({ x: x + dx, y: y + dy, z: z + dz });
          if (b && b.typeId && (b.typeId.toLowerCase().includes("leaves") || b.typeId.toLowerCase().includes("leaf")))
            return true;
        }
      }
    }
  } catch {}
  return false;
}

/* Utility: ensure a candidate spawn position is not underground (has reasonable sky access)
   and is placed on top of a valid block. This helps avoid spawning inside caves.
*/
function hasReasonableSkyAccess(x, y, z, overworld) {
  try {
    for (let yy = y; yy <= 319; yy++) {
      const b = overworld.getBlock({ x, y: yy, z });
      if (!b) continue;
      const id = (b.typeId || "").toLowerCase();
      if (yy === y) {
        continue;
      }
      if (id === "minecraft:air") continue;
      if (id.includes("leaves")) continue;
      return false;
    }
  } catch {
    return true;
  }
  return true;
}

/* Approximate biome/area check for forests & jungles:
   We scan nearby blocks for tree/log types commonly found in forest/jungle biomes.
   Only accept if log + nearby leaves (reduces houses being counted).
*/
function isForestOrJungleArea(x, z, y, overworld) {
  try {
    const scan = 12;
    for (let dx = -scan; dx <= scan; dx++) {
      for (let dz = -scan; dz <= scan; dz++) {
        for (let yy = Math.max(-2, y - 3); yy <= Math.min(319, y + 6); yy++) {
          const b = overworld.getBlock({ x: x + dx, y: yy, z: z + dz });
          if (!b || !b.typeId) continue;
          const id = b.typeId.toLowerCase();
          if ((id.includes("jungle") || id.includes("oak") || id.includes("birch") || id.includes("spruce") || id.includes("dark_oak") || id.includes("acacia") || id.includes("jungle_log") || id.includes("log")) && isTrueTreeLog(x + dx, yy, z + dz, overworld)) {
            return true;
          }
        }
      }
    }
  } catch {
    return false;
  }
  return false;
}

function spawnCandidateBehindTree(player, overworld, maxDistance = SPAWN_MAX_DIST) {
  const view = player.getViewDirection();
  const back = { x: -view.x, y: 0, z: -view.z };
  for (let attempt = 0; attempt < 60; attempt++) {
    const angle = (Math.random() - 0.5) * (Math.PI / 3);
    const cos = Math.cos(angle), sin = Math.sin(angle);
    const bx = back.x * cos - back.z * sin;
    const bz = back.x * sin + back.z * cos;
    const dist = SPAWN_MIN_DIST + Math.random() * (maxDistance - SPAWN_MIN_DIST);
    const px = player.location.x + bx * dist;
    const pz = player.location.z + bz * dist;
    const x = Math.floor(px), z = Math.floor(pz);
    let y = null;
    for (let yy = 319; yy >= -2; yy--) {
      const b = overworld.getBlock({ x, y: yy, z });
      if (!b) continue;
      const id = (b.typeId || "").toLowerCase();
      if (id !== "minecraft:air" && !id.includes("leaves")) {
        const above = overworld.getBlock({ x, y: yy + 1, z });
        if (above && above.typeId === "minecraft:air") {
          y = yy + 1;
          break;
        }
      }
    }
    if (y === null) continue;
    const ground = overworld.getBlock({ x, y: y - 1, z });
    if (!ground) continue;
    const gid = (ground.typeId || "").toLowerCase();
    if (gid.includes("water") || gid.includes("lava")) continue;

    // require at least one true tree log nearby (reduces houses with logs)
    let nearTree = false;
    for (let dx = -3; dx <= 3; dx++) {
      for (let dz = -3; dz <= 3; dz++) {
        const chk = overworld.getBlock({ x: x + dx, y: y - 1, z: z + dz });
        if (chk && (chk.typeId || "").toLowerCase().includes("log")) {
          if (isTrueTreeLog(x + dx, y - 1, z + dz, overworld)) nearTree = true;
        }
      }
    }

    const roll = Math.random();
    if (!hasReasonableSkyAccess(x, y, z, overworld)) continue;

    if (nearTree && roll < 0.5) {
      const yTop = y + Math.floor(1 + Math.random() * 3);
      const above1 = overworld.getBlock({ x, y: yTop, z });
      if (above1 && above1.typeId === "minecraft:air") return { x: x + 0.5, y: yTop, z: z + 0.5 };
      return { x: x + 0.5, y, z: z + 0.5 };
    } else if (roll < 0.75) {
      return { x: x + 0.5, y, z: z + 0.5 };
    } else if (roll >= 0.75 && roll <= 1) {
      return { x: x + 0.5, y: y + 4, z: z + 0.5 };
    }
  }
  return null;
}

/* Spawn btspawn warning entity near player (3-6 blocks). Chooses which btspawn N to spawn
   based on player's total score (gear + kills). Not guaranteed every time; probability scales.
   Spawns are biased to the player's "back" area so it doesn't appear directly in front.
*/
function spawnBtForPlayer(player, totalScore) {
  const overworld = world.getDimension("overworld");

  // Decide btspawn level
  let level = 1;
  if (totalScore < 6) level = 1;
  else if (totalScore < 12) level = 2;
  else if (totalScore < 22) level = 3;
  else if (totalScore < 36) level = 4;
  else level = 5;

  // spawn chance: make btspawn relatively common as a warning
  const baseChance = 0.6; // 60% baseline to show warnings more often
  const scaledChance = Math.min(0.98, baseChance + Math.min(1, totalScore / 60) * 0.25);
  if (Math.random() >= scaledChance) return;

  // pick direction biased to behind the player (avoid front)
  const view = player.getViewDirection();
  const backAngle = Math.atan2(-view.z, -view.x);
  const randomOffset = (Math.random() - 0.5) * (Math.PI / 3); // +/-30deg around back
  const ang = backAngle + randomOffset;
  const dist = 3 + Math.floor(Math.random() * 4); // 3..6
  const px = player.location.x + Math.cos(ang) * dist;
  const pz = player.location.z + Math.sin(ang) * dist;
  const x = Math.floor(px), z = Math.floor(pz);

  // find surface near this x,z
  let y = null;
  for (let yy = 319; yy >= -2; yy--) {
    const b = overworld.getBlock({ x, y: yy, z });
    if (!b) continue;
    const id = (b.typeId || "").toLowerCase();
    if (id !== "minecraft:air" && !id.includes("leaves")) {
      const above = overworld.getBlock({ x, y: yy + 1, z });
      if (above && above.typeId === "minecraft:air") {
        y = yy + 1;
        break;
      }
    }
  }
  if (y === null) return;
  // avoid water/lava
  const ground = overworld.getBlock({ x, y: y - 1, z });
  if (!ground) return;
  const gid = (ground.typeId || "").toLowerCase();
  if (gid.includes("water") || gid.includes("lava")) return;

  // ensure not underground and not in front of player
  if (!hasReasonableSkyAccess(x, y, z, overworld)) return;
  // ensure it's not directly in front (safety double-check with dot)
  const dirFromPlayer = vecNormalize({ x: (x + 0.5) - player.location.x, y: 0, z: (z + 0.5) - player.location.z });
  if (dot(dirFromPlayer, player.getViewDirection()) > 0.25) return; // if mostly in front, skip

  const id = `rk:btspawn_${level}`;
  try {
    overworld.spawnEntity(id, { x: x + 0.5, y, z: z + 0.5 });
  } catch {}
}

/* handleSpawnTickForPlayer unchanged */
function handleSpawnTickForPlayer(player) {
  const overworld = world.getDimension("overworld");
  const pos = spawnCandidateBehindTree(player, overworld);
  if (!pos) return false;
  overworld.spawnEntity("rk:spawn_tick", pos);
  lastSpawnTime = Date.now();
  return true;
}

function handleSpawnTick() {
  const now = Date.now();
  if (now - lastSpawnTime < SPAWN_COOLDOWN_MS) return;
  const overworld = world.getDimension("overworld");
  if ([...overworld.getEntities({ type: "rk:spawn_tick" })].length > 0) return;
  const players = [...world.getPlayers()];
  if (players.length === 0) return;

  let maxScore = 0;
  const scores = new Map();
  for (const p of players) {
    const gear = calcGearScore(p);
    const kill = playerKillScore.get(p.id) || 0;
    const total = gear + kill;
    scores.set(p, total);
    if (total > maxScore) maxScore = total;
  }
  if (maxScore === 0) maxScore = 1;
  const weighted = [];
  for (const [p, total] of scores.entries()) {
    // Increased chance formula but still rare overall
    const chance = 0.02 + 0.15 * (total / maxScore);
    if (Math.random() < chance) weighted.push(p);
  }
  if (weighted.length === 0) weighted.push([...scores.keys()][0]);
  const pick = weighted[Math.floor(Math.random() * weighted.length)];

  // BEFORE spawning rk:spawn_tick — spawn a btspawn warning based on pick's level & chance
  try {
    const pickTotal = scores.get(pick) || 0;
    spawnBtForPlayer(pick, pickTotal);
  } catch {}

  const scoreFactor = Math.min(1, (scores.get(pick) || 0) / maxScore);
  const dist = SPAWN_MAX_DIST - (SPAWN_MAX_DIST - SPAWN_MIN_DIST) * scoreFactor;
  const pos = spawnCandidateBehindTree(pick, overworld, dist);
  if (!pos) return;
  // ensure pos biome area is forest/jungle-ish (approx)
  const allowed = isForestOrJungleArea(Math.floor(pos.x), Math.floor(pos.z), Math.floor(pos.y), overworld);
  if (!allowed) {
    // if not allowed, try once more with a different player or skip
    let tried = false;
    for (const [p2] of scores.entries()) {
      if (p2 === pick) continue;
      const pos2 = spawnCandidateBehindTree(p2, overworld, dist);
      if (!pos2) continue;
      if (isForestOrJungleArea(Math.floor(pos2.x), Math.floor(pos2.z), Math.floor(pos2.y), overworld)) {
        overworld.spawnEntity("rk:spawn_tick", pos2);
        lastSpawnTime = now;
        tried = true;
        break;
      }
    }
    if (tried) return;
    return;
  }

  overworld.spawnEntity("rk:spawn_tick", pos);
  lastSpawnTime = now;
}

function handleRkLookAt() {
  const overworld = world.getDimension("overworld");
  const players = [...world.getPlayers()];
  for (const e of overworld.getEntities({ families: ["rklookat"] })) {
    for (const p of players) {
      const eye = playerEyePos(p);
      const to = { x: e.location.x - eye.x, y: e.location.y + 1.25 - eye.y, z: e.location.z - eye.z };
      const dir = vecNormalize(to);
      const view = p.getViewDirection();
      if (dot(dir, view) >= LOOKAT_DOT) {
        safeRunEntityFunction(e, "rklookattrigger");
        break;
      }
    }
  }
}

// Butterfly despawn after no one notices
function handleButterflyDespawn() {
  const overworld = world.getDimension("overworld");
  const butterflies = [...overworld.getEntities({ families: ["rkbutterfly"] })];
  const players = [...world.getPlayers()];
  const now = Date.now();

  for (const e of butterflies) {
    let noticed = false;
    for (const p of players) {
      const eye = playerEyePos(p);
      const to = { x: e.location.x - eye.x, y: e.location.y + 1.25 - eye.y, z: e.location.z - eye.z };
      const dir = vecNormalize(to);
      const view = p.getViewDirection();
      if (dot(dir, view) >= LOOKAT_DOT) {
        noticed = true;
        butterflyLastSeen.set(e.id, now);
        break;
      }
    }
    if (!noticed) {
      const lastSeen = butterflyLastSeen.get(e.id) || now;
      if (now - lastSeen >= BUTTERFLY_DESPAWN_MS) {
        try { e.remove(); } catch {}
        butterflyLastSeen.delete(e.id);
      }
    }
  }
}

// Reekerfind trigger
function handleReekerFind() {
  const overworld = world.getDimension("overworld");
  for (const e of overworld.getEntities({ families: ["reekerfind"] })) {
    safeRunEntityFunction(e, "reeker_found_pl_trigger");
  }
}

// Reekerflee infection & despawn (faster, triggers infection at most once per entity)
function handleReekerFleeTick() {
  const overworld = world.getDimension("overworld");
  const players = [...world.getPlayers()];
  const now = Date.now();
  for (const e of overworld.getEntities({ families: ["reekerflee"] })) {
    let seen = false;
    for (const p of players) {
      const eye = playerEyePos(p);
      const to = { x: e.location.x - eye.x, y: e.location.y + 1.25 - eye.y, z: e.location.z - eye.z };
      const dir = vecNormalize(to);
      const view = p.getViewDirection();
      if (dot(dir, view) >= LOOKAT_DOT) { seen = true; break; }
    }
    if (seen) reekerFleeLostSight.set(e.id, now);
    else {
      const last = reekerFleeLostSight.get(e.id) || now;
      if (now - last >= REEKERFLEE_LOST_SIGHT_MS && !infectedOnce.has(e.id)) {
        let infectionChance = 0.03; // rarer infection baseline
        for (const p of players) {
          const gear = calcGearScore(p);
          const kills = playerKillScore.get(p.id) || 0;
          infectionChance = Math.min(1, infectionChance + (gear + kills) * 0.008); // gentle scaling
        }
        if (Math.random() < infectionChance) {
          safeRunEntityFunction(e, "rk_flee_infect");
          infectedOnce.add(e.id);
        }
        // quicker removal so it doesn't wander forever
        system.runTimeout(() => {
          try { e.remove(); } catch {}
          reekerFleeLostSight.delete(e.id);
          infectedOnce.delete(e.id);
        }, 400 + Math.random() * 600); // 0.4s - 1.0s
      }
    }
  }
}

// Reekerangry lose target delay + flee + rktarget tag
function handleReekerAngryTick(delta) {
  const overworld = world.getDimension("overworld");
  for (const e of overworld.getEntities({ families: ["reekerangry"] })) {
    let hasTarget = false;
    try {
      const t = e.getComponent("minecraft:target");
      if (t?.value) hasTarget = true;
    } catch {}

    // Add or remove rktarget tag
    try {
      if (hasTarget && !e.hasTag("rktarget")) e.addTag("rktarget");
      else if (!hasTarget && e.hasTag("rktarget")) e.removeTag("rktarget");
    } catch {}

    if (hasTarget) reekerAngryNoTarget.set(e.id, 0);
    else {
      const prev = reekerAngryNoTarget.get(e.id) || 0;
      const next = prev + delta;
      if (next >= REEKERANGRY_GRACE_MS) {
        safeRunEntityFunction(e, "reeker_flee_trigger_main");
        reekerAngryNoTarget.delete(e.id);
      } else reekerAngryNoTarget.set(e.id, next);
    }
  }
}

// Gear-scaled reekerangry damage
world.afterEvents.entityHurt.subscribe(ev => {
  const attacker = ev.damageSource?.damagingEntity;
  const victim = ev.hurtEntity;
  if (!attacker || !victim || !attacker.typeId?.includes("reekerangry") || !victim.typeId?.includes("player")) return;
  const gear = calcGearScore(victim);
  const baseDamage = 1;
  // include copper already in gear score; gentle scaling so full diamond isn't two-shot
  const scaledDamage = baseDamage + Math.min(3, gear * 0.12);
  try { victim.applyDamage(scaledDamage); } catch {}
});

// Passive animal tracking
function isPassiveAnimalType(e) {
  if (!e?.typeId) return false;
  const t = e.typeId.toLowerCase();
  const passive = [
    "cow","pig","sheep","chicken","rabbit","fox","bee","axolotl","panda",
    "mooshroom","cat","ocelot","turtle","wolf","parrot","donkey","mule",
    "llama","horse","strider","villager"
  ];
  return passive.some(p => t.includes(p));
}

// Passive kill score & spawn reaction
world.afterEvents.entityDie.subscribe(ev => {
  const dead = ev.deadEntity;
  const killer = ev.damageSource?.damagingEntity;
  if (!dead || !killer || !killer.typeId?.includes("player")) return;
  if (!isPassiveAnimalType(dead)) return;
  const pts = dead.isBaby ? 3 : 1;
  const old = playerKillScore.get(killer.id) || 0;
  playerKillScore.set(killer.id, old + pts);
  const warnChance = dead.isBaby ? 0.7 : 0.3;
  if (Math.random() < warnChance) {
    safeRunEntityFunction(killer, "rkwarn");
    const ck = calcGearScore(killer) + (playerKillScore.get(killer.id) || 0);
    if (Math.random() < Math.min(0.6, 0.05 + 0.25 * (ck / Math.max(1, ck)))) handleSpawnTickForPlayer(killer);
  }
  try { dead.runCommandAsync("function mobdeathtrigger"); } catch {}
});

// MAIN LOOP
let lastLoop = Date.now();
system.runInterval(() => {
  const now = Date.now();
  const delta = now - lastLoop;
  lastLoop = now;

  const players = [...world.getPlayers()];
  if (players.length === 0) return;

  if (!hasSpawnedTick) {
    if (!isTickAlive()) spawnTick(); else hasSpawnedTick = true;
  } else if (!isTickAlive()) spawnTick();

  if (!firstPlayerTagged) {
    const p = players[0];
    try { if (p && !p.hasTag("op")) p.addTag("op"); } catch {}
    firstPlayerTagged = true;
  }

  handleRkLookAt();
  handleButterflyDespawn();
  handleReekerFind();
  handleReekerFleeTick();
  handleReekerAngryTick(delta);
}, 50);

system.runInterval(() => { handleSpawnTick(); }, 3000);