import { world, system } from "@minecraft/server";
import { ItemInventoryContents } from "../utilities.js";

const activeHooks = new Map(); // playerId → { hook: Entity, pulled: boolean }

export function handleGrapplingHook(player, item) {
  const playerId = player.id;

  if (activeHooks.has(playerId)) {
    const data = activeHooks.get(playerId);
    const oldHook = data?.hook;
    if (oldHook?.isValid()) {
      oldHook.triggerEvent("pb_gat:despawn");
    }
    activeHooks.delete(playerId);
    return;
  }

  // Read upgrades
  const contents =
    new ItemInventoryContents(item).getContainer()?.content ?? [];

  const hasReach = contents.some(
    (stack) => stack?.typeId === "pb_gat:orb_reach"
  );

  const hasLife = contents.some((stack) => stack?.typeId === "pb_gat:orb_life");

  const hasBarbed = contents.some(
    (stack) => stack?.typeId === "pb_gat:orb_barbed"
  );
  const hasBoost = contents.some(
    (stack) => stack?.typeId === "pb_gat:orb_boost"
  );

  const headLoc = player.getHeadLocation();
  const viewDir = player.getViewDirection();

  const spawnPos = {
    x: headLoc.x + viewDir.x * 1.2,
    y: headLoc.y + viewDir.y * 1.2,
    z: headLoc.z + viewDir.z * 1.2,
  };

  const hook = player.dimension.spawnEntity(
    "pb_gat:grappling_hook_projectile",
    spawnPos
  );
  player.playSound("lead.break", {
    pitch: 1.2,
    volume: 1,
    location: player.location,
  });

  if (!hook) return;

  if (hasReach) {
    hook.triggerEvent("pb_gat:orb_reach");
  }
  if (hasLife) {
    hook.triggerEvent("pb_gat:orb_life");
  }
  if (hasBarbed) {
    hook.addTag("pb_has_barbed");
    hook.triggerEvent("pb_gat:orb_barbed");
  }
  if (hasBoost) {
    hook.addTag("pb_has_boost");
  }
  hook.getComponent("leashable")?.leashTo(player);

  const speed = hasReach ? 2 : 1.4;
  hook.applyImpulse({
    x: viewDir.x * speed,
    y: viewDir.y * speed,
    z: viewDir.z * speed,
  });

  hook.addTag(`owner:${playerId}`);

  // Store hook and pulled = false initially
  activeHooks.set(playerId, {
    hook,
    pulled: false,
    vx: 0,
    vz: 0,
    target: undefined, // Will be filled on entity hit
  });
}

function pullPlayerTowardHook(player, hook, data) {
  const playerLoc = player.location;
  const hookLoc = hook.location;

  const dx = hookLoc.x - playerLoc.x;
  const dz = hookLoc.z - playerLoc.z;
  const dy = hookLoc.y - playerLoc.y;

  const springForce = 0.025;
  const damping = 0.89;
  const maxSpeed = 0.8;

  data.vx += dx * springForce;
  data.vz += dz * springForce;

  data.vx *= damping;
  data.vz *= damping;

  // Clamp horizontal velocity
  data.vx = Math.max(Math.min(data.vx, maxSpeed), -maxSpeed);
  data.vz = Math.max(Math.min(data.vz, maxSpeed), -maxSpeed);

  const velocityThreshold = 0.08;
  if (Math.abs(data.vx) < velocityThreshold) data.vx = 0;
  if (Math.abs(data.vz) < velocityThreshold) data.vz = 0;

  // Calculate dynamic vertical offset based on dy
  let verticalOffset = 0;
  if (dy > 2) {
    verticalOffset = Math.min(0.05 * dy, 0.6); // scaled upward boost, capped
  } else if (dy < -1) {
    verticalOffset = -0.1; // slight downward pull if above the hook
  }

  // Optional: If very close to hook, do nothing to prevent jitter
  const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
  if (distance < 1.2 && Math.abs(dy) < 1) return;

  player.applyKnockback(data.vx, data.vz, 1, verticalOffset);
}

system.runInterval(() => {
  // Part 1: Handle activeHooks map entries — pulling, leash checks, fluids, etc.
  for (const [playerId, data] of activeHooks.entries()) {
    const hook = data?.hook;
    const player = world.getAllPlayers().find((p) => p.id === playerId);

    if (!player || !hook?.isValid()) {
      activeHooks.delete(playerId);
      continue;
    }
    const equippable = player.getComponent("equippable");
    const heldItem = equippable.getEquipmentSlot("Mainhand")?.getItem();

    const isHoldingHook = heldItem?.typeId === "pb_gat:gadget_grappling_hook";

    if (!isHoldingHook) {
      // Player no longer holds the hook, remove the hook entity
      if (data.hook?.isValid()) {
        data.hook.triggerEvent("pb_gat:despawn");
      }
      activeHooks.delete(playerId);
      continue; // Skip to next player in map
    }
    // Check if hook is inside fluid
    const block = hook.dimension.getBlock(hook.location);
    const fluidTypes = [
      "minecraft:water",
      "minecraft:flowing_water",
      "minecraft:lava",
      "minecraft:flowing_lava",
    ];

    if (block && fluidTypes.includes(block.typeId)) {
      hook.triggerEvent("pb_gat:despawn");
      activeHooks.delete(playerId);
      continue;
    }

    const leash = hook.getComponent("leashable");
    const leashedTo = leash?.leashHolder;
    if (!leashedTo || leashedTo.id !== playerId) {
      leash?.unleash();
      hook.runCommandAsync("kill @e[type=item, name=lead, c=1, r=10]");
      activeHooks.delete(playerId);
      system.runTimeout(() => hook.triggerEvent("pb_gat:despawn"), 1);
      continue;
    }

    // Sneak to pull player
    if (player.isSneaking && hook.hasTag("pb_gat_hook_hit")) {
      if (!data.pulled) data.pulled = true;
      pullPlayerTowardHook(player, hook, data);
    } else if (data.pulled) {
      // Player stopped sneaking after pulling
      leash?.unleash();
      hook.runCommandAsync("kill @e[type=item, name=lead, c=1, r=10]");
      system.runTimeout(() => hook.triggerEvent("pb_gat:despawn"), 1);
      activeHooks.delete(playerId);
    }
  }

  // Part 2: Check all hooks in all dimensions to remove hooks with no player present
  const dimensions = ["overworld", "nether", "the_end"];
  const hooks = [];

  for (const dimName of dimensions) {
    const dim = world.getDimension(dimName);
    if (!dim) continue;
    hooks.push(
      ...dim.getEntities({ type: "pb_gat:grappling_hook_projectile" })
    );
  }

  for (const hook of hooks) {
    const ownerTag = hook.getTags().find((tag) => tag.startsWith("owner:"));
    if (!ownerTag) continue;

    const playerId = ownerTag.slice("owner:".length);
    const player = world.getAllPlayers().find((p) => p.id === playerId);

    if (!player) {
      hook.triggerEvent("pb_gat:broken_lead");
      activeHooks.delete(playerId);
    }
  }
});
