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

const PROP_STRAFE_DIR = "ai_strafe_direction";
const PROP_STRAFE_TIME = "ai_strafe_timer";
const PROP_ATTACK_RADIUS = "ai_attack_radius";
const PROP_ATTACK_INTERVAL_MIN = "ai_attack_interval_min";
const PROP_ATTACK_INTERVAL_MAX = "ai_attack_interval_max";

const DIM_NAMES = ["overworld", "nether", "the_end"];
const ARROW_TYPES = new Set(["minecraft:arrow", "dungeon:arrow", "dungeon:stinger_entity"]);
const SKELETON_FAMILY = "skeleton";
const HORNET_FAMILY = "hornet";

const PUSH_FORCE_CLOSE = 0.02;
const PUSH_FORCE_ALLY = 0.04;
const CHANGE_DIR_CHANCE = 0.4;

/** Rendimiento */
const LOOP_EVERY_TICKS = 1;              // para feeling tipo Java
const REFRESH_LIST_EVERY_TICKS = 20;     // refresca listas cada 1s
const DIFF_REFRESH_EVERY_TICKS = 40;     // dificultad cada 2s
const MAX_ENTITIES_PER_DIM_PER_LOOP = 20; // presupuesto por dimensión por tick
const PUSH_EVERY_TICKS = 4;              // repulsiones no cada tick

/** Movimiento tipo Java (ajustable) */
const ACCEL_PER_TICK = 0.055;             // 0.04–0.08
const MAX_IMPULSE_PER_TICK = 0.12;       // límite de corrección por tick

let TICK = 0;
system.runInterval(() => { TICK++; }, 1);

let DIMS = null;
function initDims() {
  try {
    DIMS = [
      world.getDimension("overworld"),
      world.getDimension("nether"),
      world.getDimension("the_end"),
    ];
  } catch {
    DIMS = null;
  }
}
system.run(initDims);
system.runInterval(() => { if (!DIMS) initDims(); }, 40);

/** Dificultad cacheada */
let currentDifficulty = "normal";
let _obj = null;
let _part = null;

function refreshDifficultyCache() {
  try {
    if (!_obj) _obj = world.scoreboard.getObjective("otherworldly_difficulty");
    if (!_obj) { currentDifficulty = "normal"; return; }

    if (!_part) {
      const parts = world.scoreboard.getParticipants();
      _part = parts.find(p => p.displayName === "difficulty") ?? null;
    }

    const v = _part ? _obj.getScore(_part) : undefined;
    switch (v) {
      case 0: currentDifficulty = "easy"; break;
      case 1: currentDifficulty = "normal"; break;
      case 2: currentDifficulty = "hard"; break;
      case 3: currentDifficulty = "expert"; break;
      default: currentDifficulty = "normal"; break;
    }
  } catch {
    currentDifficulty = "normal";
  }
}

export function getDifficulty() {
  return currentDifficulty;
}

system.run(refreshDifficultyCache);
system.runInterval(refreshDifficultyCache, DIFF_REFRESH_EVERY_TICKS);

const dimCache = new Map(); // dim.id -> { sk, ho, iSk, iHo, lastRefresh }
function getDimLists(dim) {
  let c = dimCache.get(dim.id);
  if (!c) {
    c = { sk: [], ho: [], iSk: 0, iHo: 0, lastRefresh: -999999 };
    dimCache.set(dim.id, c);
  }

  if (TICK - c.lastRefresh >= REFRESH_LIST_EVERY_TICKS) {
    try { c.sk = dim.getEntities({ families: [SKELETON_FAMILY] }); } catch { c.sk = []; }
    try { c.ho = dim.getEntities({ families: [HORNET_FAMILY] }); } catch { c.ho = []; }
    c.iSk = 0;
    c.iHo = 0;
    c.lastRefresh = TICK;
  }

  return c;
}

function safeGetDyn(entity, k) {
  try { return entity.getDynamicProperty(k); } catch { return undefined; }
}
function safeSetDyn(entity, k, v) {
  try { entity.setDynamicProperty(k, v); } catch {}
}

function isInWater(dim, loc) {
  try {
    const bx = Math.floor(loc.x);
    const by = Math.floor(loc.y);
    const bz = Math.floor(loc.z);
    const b = dim.getBlock({ x: bx, y: by, z: bz });
    const id = b?.typeId;
    return !!id && id.includes("water");
  } catch {
    return false;
  }
}

function getController(entity) {
  try {
    const ridingComp = entity.getComponent(EntityComponentTypes.Riding);
    return ridingComp?.entityRidingOn ?? entity;
  } catch {
    return entity;
  }
}

function controllerCanMove(controller) {
  try {
    const fam = controller.getComponent(EntityComponentTypes.TypeFamily);
    if (!controller.isOnGround && !fam?.hasTypeFamily(HORNET_FAMILY) && fam?.hasTypeFamily("inanimate")) return false;
  } catch {}
  return true;
}

function computeAvoidClose(entity, dim, attackRange) {
  const half = attackRange * 0.5;
  const halfSq = half * half;

  let players = [];
  try { players = dim.getPlayers({ location: entity.location, maxDistance: attackRange }); } catch { players = []; }
  if (!players.length) return 1;

  const ex = entity.location.x;
  const ez = entity.location.z;

  for (const p of players) {
    const dx = p.location.x - ex;
    const dz = p.location.z - ez;
    if (dx * dx + dz * dz <= halfSq) return -1;
  }
  return 1;
}

function repelCloseEntitiesExpert(entity, dim) {
  if ((TICK % PUSH_EVERY_TICKS) !== 0) return;

  let closeEntities = [];
  try { closeEntities = dim.getEntities({ location: entity.location, maxDistance: 8 }); } catch { closeEntities = []; }
  if (!closeEntities.length) return;

  const ex = entity.location.x;
  const ez = entity.location.z;

  for (const e of closeEntities) {
    if (e.id === entity.id) continue;
    if (ARROW_TYPES.has(e.typeId)) continue;

    const dx = e.location.x - ex;
    const dz = e.location.z - ez;
    const magSq = dx * dx + dz * dz;
    if (magSq > 0 && magSq < 36) {
      const mag = Math.sqrt(magSq);
      const inv = 1 / mag;
      try {
        entity.applyImpulse({ x: -dx * inv * PUSH_FORCE_CLOSE, y: 0, z: -dz * inv * PUSH_FORCE_CLOSE });
      } catch {}
    }
  }
}

function repelAlliesHard(entity, dim) {
  if ((TICK % PUSH_EVERY_TICKS) !== 0) return;

  let nearby = [];
  try {
    nearby = dim.getEntities({
      families: [SKELETON_FAMILY, HORNET_FAMILY],
      location: entity.location,
      maxDistance: 12,
    });
  } catch { nearby = []; }

  if (!nearby.length) return;

  const ex = entity.location.x;
  const ez = entity.location.z;

  for (const ally of nearby) {
    if (ally.id === entity.id) continue;

    const dx = ally.location.x - ex;
    const dz = ally.location.z - ez;
    const magSq = dx * dx + dz * dz;
    if (magSq < 16 && magSq > 0.0001) {
      const mag = Math.sqrt(magSq);
      const inv = 1 / mag;
      try {
        entity.applyImpulse({ x: -dx * inv * PUSH_FORCE_ALLY, y: 0, z: -dz * inv * PUSH_FORCE_ALLY });
      } catch {}
    }
  }
}

function applyJavaLikeStrafe(controller, angle) {
  const moveComp = controller.getComponent("minecraft:movement");
  const baseSpeed = moveComp?.currentValue ?? 0.15;

  const targetX = Math.cos(angle) * baseSpeed;
  const targetZ = Math.sin(angle) * baseSpeed;

  let v;
  try { v = controller.getVelocity(); } catch { v = { x: 0, y: 0, z: 0 }; }

  let dvx = targetX - v.x;
  let dvz = targetZ - v.z;

  const mag = Math.sqrt(dvx * dvx + dvz * dvz);
  if (mag <= 0.0001) return;

  const accel = ACCEL_PER_TICK;
  const scale = Math.min(accel, mag) / mag;
  dvx *= scale;
  dvz *= scale;

  const outMag = Math.sqrt(dvx * dvx + dvz * dvz);
  if (outMag > MAX_IMPULSE_PER_TICK) {
    const k = MAX_IMPULSE_PER_TICK / outMag;
    dvx *= k;
    dvz *= k;
  }

  try {
    controller.applyImpulse({ x: dvx, y: 0, z: dvz });
  } catch {}
}

function doStrafeTick(dim, entity) {
  const strafeDir = safeGetDyn(entity, PROP_STRAFE_DIR);
  let strafeTime = safeGetDyn(entity, PROP_STRAFE_TIME);

  if (strafeDir === undefined || typeof strafeTime !== "number" || strafeTime < 0) return;
  if (isInWater(dim, entity.location)) return;

  strafeTime -= LOOP_EVERY_TICKS;
  safeSetDyn(entity, PROP_STRAFE_TIME, strafeTime);

  if (strafeTime < 0) {
    try { entity.isSneaking = false; } catch {}
    safeSetDyn(entity, PROP_STRAFE_DIR, undefined);
    return;
  }

  const controller = getController(entity);
  if (!controllerCanMove(controller)) return;

  const attackRange = safeGetDyn(entity, PROP_ATTACK_RADIUS) ?? 20;
  const avoidClose = computeAvoidClose(entity, dim, attackRange);

  if (currentDifficulty === "expert") repelCloseEntitiesExpert(entity, dim);

  try {
    const yaw = controller.getRotation().y;
    const angle = (yaw + strafeDir * (90 - avoidClose * 30) + 145) * Math.PI / 180;

    applyJavaLikeStrafe(controller, angle);

    try {
      const fam = controller.getComponent(EntityComponentTypes.TypeFamily);
      if (fam?.hasTypeFamily(HORNET_FAMILY)) {
        controller.applyImpulse({ x: 0, y: (Math.random() - 0.5) * 0.03, z: 0 });
      }
    } catch {}
  } catch {}

  if (currentDifficulty === "hard" || currentDifficulty === "expert") repelAlliesHard(entity, dim);
}

system.runInterval(() => {
  if (!DIMS) return;

  for (let d = 0; d < DIMS.length; d++) {
    const dim = DIMS[d];
    const cache = getDimLists(dim);

    let processed = 0;

    while (processed < MAX_ENTITIES_PER_DIM_PER_LOOP) {
      let e = null;

      if (cache.iSk < cache.sk.length) {
        e = cache.sk[cache.iSk++];
      } else if (cache.iHo < cache.ho.length) {
        e = cache.ho[cache.iHo++];
      } else {
        break;
      }

      if (!e) continue;
      doStrafeTick(dim, e);
      processed++;
    }

    if (cache.iSk >= cache.sk.length && cache.iHo >= cache.ho.length) {
      cache.iSk = 0;
      cache.iHo = 0;
    }
  }
}, LOOP_EVERY_TICKS);

world.afterEvents.entitySpawn.subscribe(({ entity: arrow }) => {
  if (!arrow || !ARROW_TYPES.has(arrow.typeId)) return;

  const shooter = arrow.getComponent("minecraft:projectile")?.owner;
  if (!shooter) return;

  const shooterFamily = shooter.getComponent("minecraft:type_family");
  if (!shooterFamily?.hasTypeFamily(SKELETON_FAMILY) && !shooterFamily?.hasTypeFamily(HORNET_FAMILY)) return;

  if (shooter.isSneaking) {
    try { arrow.remove(); } catch {}
    try { shooter.isSneaking = false; } catch {}
    return;
  }

  const headLoc = shooter.getHeadLocation();
  let velocity = arrow.getVelocity();
  if (!velocity || !isFinite(velocity.x) || !isFinite(velocity.y) || !isFinite(velocity.z)) {
    velocity = shooter.getViewDirection();
  }

  let targets = [];
  try {
    targets = shooter.getEntitiesFromViewDirection({ maxDistance: 24 })
      .filter(e => e.entity.id !== shooter.id && e.entity.id !== arrow.id);
  } catch {
    targets = [];
  }
  const target = targets.length > 0 ? targets[0].entity : null;

  if (target) {
    const future = { ...target.location };
    const vel = target.getVelocity?.();
    if (vel) {
      future.x += vel.x * 0.6;
      future.z += vel.z * 0.6;
    }
    const dx = future.x - headLoc.x;
    const dz = future.z - headLoc.z;
    const distSq = dx * dx + dz * dz;
    if (distSq > 0.0001) {
      const dist = Math.sqrt(distSq);
      velocity.x += (dx / dist) * 0.2;
      velocity.z += (dz / dist) * 0.2;
    }
  }

  try {
    arrow.tryTeleport({ x: headLoc.x + velocity.x, y: headLoc.y + velocity.y, z: headLoc.z + velocity.z });
  } catch {}
  try { arrow.applyImpulse(velocity); } catch {}

  const difficulty = currentDifficulty;
  const range = shooter.getDynamicProperty(PROP_ATTACK_RADIUS) ?? 20;

  let minDelay = 0;
  let targetsInRange = [];
  try { targetsInRange = shooter.getEntitiesFromViewDirection({ excludeTypes: Array.from(ARROW_TYPES) }); } catch { targetsInRange = []; }

  for (const t of targetsInRange) {
    const rider = shooter.getComponent(EntityComponentTypes.Riding)?.entityRidingOn ?? shooter;
    if (t.entity.id !== arrow.id && t.entity.id !== shooter.id && t.entity.id !== rider.id) {
      minDelay = Math.min(t.distance, range);
      break;
    }
  }

  let scale = 1;
  switch (difficulty) {
    case "easy": scale = 1; break;
    case "normal": scale = 0.75; break;
    case "hard": scale = 0.45; break;
    case "expert": scale = 0.15; break;
  }

  const intervalMin = (shooter.getDynamicProperty(PROP_ATTACK_INTERVAL_MIN) ?? 1.0) * 20 * scale;
  const intervalMax = (shooter.getDynamicProperty(PROP_ATTACK_INTERVAL_MAX) ?? 2.5) * 20 * scale;
  const delay = intervalMin !== intervalMax
    ? Math.floor(intervalMin + (minDelay / range) * (intervalMax - intervalMin))
    : intervalMax;

  system.runTimeout(() => {
    try {
      if (shooter?.getComponent("minecraft:health")?.currentValue > 0) shooter.isSneaking = false;
    } catch {}
  }, Math.max(5, delay - 25));

  shooter.setDynamicProperty(PROP_STRAFE_TIME, delay + 30);

  const dir = shooter.getDynamicProperty(PROP_STRAFE_DIR);
  shooter.setDynamicProperty(
    PROP_STRAFE_DIR,
    dir === undefined
      ? (Math.random() > 0.5 ? 1 : -1)
      : (Math.random() < CHANGE_DIR_CHANCE ? -dir : dir)
  );
});
