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

const BOAT = "silverlabs:tow_boat";
const TOW_PROP = "silverlabs:tow_target";

const SOFT_DIST = 3.0;
const HARD_DIST = 8.0;
const PULL_GAIN = 0.20;
const TICK_RATE = 4;

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

function unitVec(from, to, len) {
  return {
    x: (to.x - from.x) / len,
    y: (to.y - from.y) / len,
    z: (to.z - from.z) / len
  };
}

function getTowTarget(boat) {
  const id = boat.getDynamicProperty(TOW_PROP);
  return typeof id === "string" && id.length > 0 ? id : null;
}

function setTowTarget(boat, targetId) {
  boat.setDynamicProperty(TOW_PROP, targetId);
}

function clearTowTarget(boat) {
  boat.setDynamicProperty(TOW_PROP, "");
}

function findLeashedBoat(player) {
  const overworld = world.getDimension(player.dimension.id);
  const nearby = overworld.getEntities({
    type: BOAT,
    location: player.location,
    maxDistance: 16
  });
  for (const boat of nearby) {
    try {
      const holder = boat.getComponent("leashable")?.leashHolder;
      if (holder && holder.id === player.id) return boat;
    } catch {
      /* ignore — component may be unavailable mid-load */
    }
  }
  return null;
}

world.beforeEvents.playerInteractWithEntity.subscribe((ev) => {
  const { player, target } = ev;
  if (target.typeId !== BOAT) return;
  if (!player.isSneaking) return;

  const inv = player.getComponent("inventory")?.container;
  const held = inv?.getItem(player.selectedSlotIndex);
  if (held) return; // empty hand only

  if (getTowTarget(target)) {
    ev.cancel = true;
    system.run(() => {
      clearTowTarget(target);
      player.sendMessage("§eTow link removed.");
    });
    return;
  }

  const leader = findLeashedBoat(player);
  if (!leader || leader.id === target.id) return;

  ev.cancel = true;
  system.run(() => {
    setTowTarget(target, leader.id);
    try {
      leader.getComponent("leashable")?.unleash();
    } catch {
      /* unleash may not exist on all versions; safe to ignore */
    }
    player.sendMessage("§aTow link created — boat will follow the leader.");
  });
});

function tickFollowers() {
  for (const dim of ["overworld", "nether", "the_end"]) {
    let boats;
    try {
      boats = world.getDimension(dim).getEntities({ type: BOAT });
    } catch {
      continue;
    }
    for (const boat of boats) {
      const targetId = getTowTarget(boat);
      if (!targetId) continue;

      const target = world.getEntity(targetId);
      if (!target || !target.isValid()) {
        clearTowTarget(boat);
        continue;
      }

      const d = dist(boat.location, target.location);
      if (d < SOFT_DIST) continue;

      if (d > HARD_DIST) {
        const u = unitVec(target.location, boat.location, d);
        try {
          boat.teleport({
            x: target.location.x + u.x * (SOFT_DIST - 0.5),
            y: target.location.y,
            z: target.location.z + u.z * (SOFT_DIST - 0.5)
          });
        } catch { /* chunk unloaded */ }
        continue;
      }

      const u = unitVec(boat.location, target.location, d);
      try {
        boat.applyImpulse({
          x: u.x * PULL_GAIN,
          y: 0,
          z: u.z * PULL_GAIN
        });
      } catch { /* boat may not support impulse if unloaded */ }
    }
  }
}

system.runInterval(tickFollowers, TICK_RATE);

world.afterEvents.entityRemove.subscribe((ev) => {
  const removedId = ev.removedEntityId;
  for (const dim of ["overworld", "nether", "the_end"]) {
    let boats;
    try {
      boats = world.getDimension(dim).getEntities({ type: BOAT });
    } catch {
      continue;
    }
    for (const boat of boats) {
      if (getTowTarget(boat) === removedId) clearTowTarget(boat);
    }
  }
});
