import { world, system, Player, EntityDamageCause, Entity, TicksPerSecond, EquipmentSlot } from "@minecraft/server";
import V3 from "../utilities/V3";
import { damageItem } from "../misc/items";

const EFFECT_DURATION = 3;
const PROP_LENGTH = "cyd_dino:length";
const PROP_X_ROTATION = "cyd_dino:x_rotation";
const PROP_Y_ROTATION = "cyd_dino:y_rotation";

// itemStartUse is used here because there was some inconsistency with the itemUse animation playing but the projectile not spawning
world.afterEvents.itemStartUse.subscribe(({ itemStack, source }) => {
  if (!itemStack || itemStack.typeId !== 'cyd_dino:skull_hook') return;

  damageItem(source, EquipmentSlot.Mainhand, 1);
  spawnProjectile(source, 7);

});

world.beforeEvents.worldInitialize.subscribe((eventData) => {
  const { itemComponentRegistry } = eventData;

  itemComponentRegistry.registerCustomComponent("cyd_dino:skull_hook", {
    onHitEntity(e) {
      const victom = e.hitEntity;

      if (victom && victom.isValid()) {
        victom.addEffect("wither", EFFECT_DURATION * TicksPerSecond, { amplifier: 1, showParticles: false });
      }
    }
  });
});

function spawnProjectile(player, length) {
  const hit = getExactLookLocation(player, length);
  if (!hit || !hit.position) return;

  let carriedEntity = null;
  let carriedDest = null;
  if (hit.entity) {
    carriedEntity = hit.entity;
    const view = new V3(player.getViewDirection()).normalize();
    const horiz = new V3(view.x, 0, view.z);
    const dir = (horiz.magnitude() > 0) ? horiz.normalize() : view.normalize();
    const dest = new V3(player.location).add(new V3(dir.x, 0, dir.z).normalize().scale(1));
    dest.y = player.location.y;
    carriedDest = dest;
  }

  smoothTeleport(player, hit.position, 1, carriedEntity, carriedDest);
}

/**
 * @param {Entity} entity
 * @param {number} maxDistance
 */
export function getExactLookLocation(entity, maxDistance, direction = null) {
  const origin = entity.getHeadLocation ? entity.getHeadLocation() : entity.location;
  const start = new V3(origin);
  const dir = direction ? direction : entity.getViewDirection();
  const viewDir = new V3(dir).normalize();

  try {
    const hits = entity.getEntitiesFromViewDirection({ maxDistance: maxDistance });

    if (hits && hits.length) {
      const ent = hits[0].entity || null;
      return { position: start.add(viewDir.scale(hits[0].distance)), entity: ent };
    }
  } catch (e) { }

  try {
    const blockHit = entity.dimension.getBlockFromRay(origin, dir, { maxDistance });

    if (blockHit) {
      const blk = new V3(blockHit.block.location);
      const dist = start.distanceTo(blk);
      return { position: start.add(viewDir.scale(dist)), block: blockHit.block };
    }
  } catch (e) { }

  return { position: start.add(viewDir.scale(maxDistance)) };
}

/**
 * @param {Player} player
 * @param {V3|Vector3} targetLocation
 * @param {number} interval
 * @param {Entity|null} carriedEntity
 * @param {V3|Vector3|null} carriedDest
 */
export async function smoothTeleport(player, targetLocation, interval, carriedEntity = null, carriedDest = null) {
  const savedEffect = player.getEffect('slowness');
  player.addEffect('slowness', interval * TicksPerSecond, { amplifier: 3, showParticles: false });
  const startLocation = new V3(player.location);
  const newTargetLocation = /** @type {V3} */ (new V3(targetLocation));

  const dimension = player.dimension;
  const distanceToPlayer = startLocation.distanceTo(newTargetLocation);
  
  // Initial spawn position
  await system.waitTicks(0.7 * TicksPerSecond);
  const initialChainOffset = getLocalPosition(player.location, player.getRotation(), new V3(0, 1, 0.5));
  const chain = dimension.spawnEntity("cyd_dino:skull_hook_chain", initialChainOffset);
  player.dimension.playSound("cyd_dino.item.skull_hook.extension", player.location, { volume: 1, pitch: 1 });

  // Start with zero length and animate
  let animatedLength = 0;
  chain.setProperty(PROP_LENGTH, animatedLength);

  // Animation parameters
  const extendTicks = Math.min(Math.max(4, Math.floor(distanceToPlayer)), 10); // scale with distance, clamped
  const retractTicks = extendTicks;
  const holdTicks = 0.25 * TicksPerSecond; // 0.5s hold at full length

  // Prepare carried entity movement if present
  let carriedStart = null;
  let carriedDelta = null;
  if (carriedEntity && carriedEntity.isValid() && carriedDest) {
    carriedStart = new V3(carriedEntity.location);
    carriedDelta = new V3(carriedDest).subtract(carriedStart);
  }

  let tick = 0;
  let damageSoundPlayed = false;
  // start the interval one tick later so the chain has a chance to spawn with zero length
  system.runTimeout(() => {
    const intervalId = system.runInterval(() => {
      if (!chain.isValid()) {
        system.clearRun(intervalId);
        return;
      }

      // Update chain position and direction based on current player location
      try {
        if (!player.isValid()) {
          system.clearRun(intervalId);
          chain.remove();
          return;
        }

        // Get current player position and update chain anchor
        const currentChainOffset = getLocalPosition(player.location, player.getRotation(), new V3(0, 1, 0.5));
        chain.teleport(currentChainOffset);

        // Recalculate direction from current position to target
        const dir = newTargetLocation.subtract(currentChainOffset).normalize();
        const xRot = -Math.asin(dir.y) * (180 / Math.PI);
        const yRot = Math.atan2(-dir.x, dir.z) * (180 / Math.PI);
        chain.setProperty(PROP_X_ROTATION, xRot);
        chain.setProperty(PROP_Y_ROTATION, yRot);

        // Update distance based on current player position
        const currentDistance = new V3(player.location).distanceTo(newTargetLocation);
        
        if (tick < extendTicks) {
          // extending
          const frac = (tick + 1) / extendTicks;
          animatedLength = currentDistance * Math.min(1, frac);
          chain.setProperty(PROP_LENGTH, animatedLength);
        } else if (tick < extendTicks + holdTicks) {
          // hold full length
          chain.setProperty(PROP_LENGTH, currentDistance);
        } else {
          // Use original distance for retraction animation
          const rTick = tick - (extendTicks + holdTicks);
          const frac = 1 - (rTick + 1) / retractTicks;
          animatedLength = currentDistance * Math.max(0, frac);
          chain.setProperty(PROP_LENGTH, animatedLength);
        }
      } catch (e) { }

      
      // Handle damage during hold phase
      if (tick >= extendTicks && tick < extendTicks + holdTicks) {
        if (!damageSoundPlayed) {
          if (carriedEntity && carriedEntity.isValid()) {
            player.dimension.playSound("cyd_dino.item.skull_hook.damage", player.location, { volume: 0.5, pitch: 1 });
            carriedEntity.dimension.playSound("cyd_dino.item.skull_hook.damage", carriedEntity.location, { volume: 0.5, pitch: 1 });
            carriedEntity.applyDamage(4, { cause: EntityDamageCause.entityAttack, damagingEntity: player });
            carriedEntity.addEffect("wither", EFFECT_DURATION * TicksPerSecond, { amplifier: 1, showParticles: false });
          }
          damageSoundPlayed = true;
        }
      }
      
      // Handle carried entity movement during retraction
      if (tick >= extendTicks + holdTicks && tick < extendTicks + holdTicks + retractTicks) {
        const rTick = tick - (extendTicks + holdTicks);
        if (carriedStart && carriedDelta && carriedEntity && carriedEntity.isValid() && is_hookable(carriedEntity)) {
          try {
            const progress = (rTick + 1) / retractTicks; // 0..1
            const newPos = carriedStart.add(carriedDelta.scale(progress));
            carriedEntity.teleport(newPos);
          } catch (e) { }
        }
      }
      
      if (tick >= extendTicks + holdTicks + retractTicks) {
        // finished
        system.clearRun(intervalId);
        try {
          chain.remove();
          player.removeEffect('slowness');
          if (savedEffect) player.addEffect('slowness', savedEffect.durationTicks, { amplifier: savedEffect.amplifier, showParticles: savedEffect.showParticles });
          if (player.isValid() && player.typeId === "cyd_dino:dark_draxar_boss") player.triggerEvent("cyd_dino:melee_attack");
          player.dimension.playSound("cyd_dino.item.skull_hook.extension", player.location, { volume: 1, pitch: 1 });
        } catch (e) { }
        return;
      }

      tick++;
    }, interval);
  }, 1);
}

/**
 * @param {Entity} entity 
 */
function is_hookable(entity) {
  try {
    if (!entity.typeId.includes("cyd_dino:")) return true;
    if (entity.typeId == "minecraft:player") return true;

    const hookable = entity.getProperty("cyd_dino:hookable");
    return hookable === true;
  } catch (e) { }
  return false;
}

function toRadians(angle) {
  return angle * (Math.PI / 180);
}

function getLocalPosition(location, rotation, distance) {
  const yaw = rotation.y;
  const pitch = rotation.x;
  const dx = new V3(Math.cos(toRadians(yaw)), 0, Math.sin(toRadians(yaw))).scale(distance.x);
  const dy = new V3(
    Math.sin(toRadians(yaw)) * -Math.sin(toRadians(pitch)),
    Math.cos(pitch * (Math.PI / 180)),
    Math.cos(toRadians(yaw)) * Math.sin(toRadians(pitch))
  ).scale(distance.y);
  const dz = new V3(
    -Math.sin(toRadians(yaw)) * Math.cos(toRadians(pitch)),
    -Math.sin(pitch * (Math.PI / 180)),
    Math.cos(toRadians(yaw)) * Math.cos(toRadians(pitch))
  ).scale(distance.z);
  return new V3(location).add(dx).add(dy).add(dz);
}