import * as mc from "@minecraft/server";
import { vec_add, vec_normalize, vec_scale, vec_sub, vec3_distance } from "./vec";

/**
 * Calculates a position directly forward from an entity's location.
 * @param {mc.Entity} entity - The entity to calculate from.
 * @param {number} [forward=1] - Distance multiplier for forward position.
 * @returns {object} The forward position as a {x, y, z} object.
 */
export function getPosForward(entity, forward = 1) {
  if (!entity?.location || !entity.getViewDirection) {
    throw new Error("Invalid entity for calculating forward position");
  }
  const location = entity.location;
  const direction = entity.getViewDirection();
  return vec_add(location, vec_scale(direction, forward));
}

/**
 * Retrieves entities within a specified radius of a position, filtered by family and exclusions.
 * @param {mc.Entity} source - The source entity for dimension context.
 * @param {object} position - The center position for the radius check.
 * @param {number} radius - The radius within which to search for entities.
 * @param {string[]} [family=["ravager"]] - Families of entities to include in the search.
 * @returns {mc.Entity[]} Array of entities within the specified radius.
 */
export function getRadiusEntity(source, position, radius, family = ["ravager"]) {
  if (!source?.dimension || !position || radius <= 0) {
    throw new Error("Invalid source, position, or radius for entity search");
  }
  return source.dimension.getEntities({
    location: position,
    families: family,
    maxDistance: radius,
    excludeFamilies: ["inanimate"],
    excludeTypes: [
      "minecraft:item",
      "minecraft:xp_orb",
      "minecraft:npc",
      "minecraft:arrow",
      "minecraft:thrown_trident",
    ],
  });
}

/**
 * Delays the execution of a function by a specified number of ticks.
 * @param {number} tick - The number of ticks to delay.
 * @param {function} func - The function to execute after the delay.
 */
export function delayExecute(tick, func) {
  if (typeof func !== "function" || tick < 0) {
    throw new Error("Invalid tick count or function for delay execution");
  }
  mc.system.runTimeout(() => {
    try {
      func();
    } catch (error) {
      //console.error("Error during delayed execution:", error);
    }
  }, tick);
}

/**
 * Calculates a normalized directional vector from source position to destination position.
 * @param {object} src - The source position {x, y, z}.
 * @param {object} des - The destination position {x, y, z}.
 * @returns {object} The normalized direction vector {x, y, z}.
 */
export function getPosFacingPosVec(src, des) {
  if (!src || !des) {
    throw new Error("Source and destination positions are required");
  }
  return vec_normalize(vec_sub(des, src));
}

/**
 * Makes an entity face a player for a specified duration.
 * @param {mc.Entity} source - The entity to make face the player.
 * @param {number} duration - Duration for which the entity faces the player, in ticks.
 */
export function facePlayer(source, duration) {
  let ticks = 0;
  try {
    let runId = mc.system.runInterval(() => {
      if (ticks >= duration || !source.isValid()) {
        mc.system.clearRun(runId);
        return;
      }
      try {
        source.runCommand(
          "/execute as @s facing entity @p eyes run tp @s ~ ~ ~ ~ ~"
        );
      } catch (error) {
        //console.error("Error executing face command:", error);
      }
      ticks += 5;
    }, 5);
  } catch (error) {
    //console.error("Error in facePlayer function:", error);
  }
}

/**
 * Applies an impulse to the entity in a given direction with optional vertical force.
 * @param {mc.Entity} entity - The entity to apply impulse to.
 * @param {number} power - Power of the impulse.
 * @param {number} [height=0] - Vertical force of the impulse.
 * @param {number} [directionMultiplier=1] - Direction multiplier for forward/backward impulses.
 */
export function applyImpulse(entity, power, height = 0, directionMultiplier = 1) {
  if (!entity?.getViewDirection || typeof power !== "number") {
    throw new Error("Invalid entity or power for applying impulse");
  }
  let direction = entity.getViewDirection();
  entity.applyImpulse({
    x: direction.x * power * directionMultiplier,
    y: height,
    z: direction.z * power * directionMultiplier,
  });
}

/**
 * Applies knockback to an entity in a specified direction.
 * @param {mc.Entity} entity - The entity to apply knockback to.
 * @param {number[]} power - Array containing [horizontalPower, verticalPower].
 * @param {boolean} [isForward=true] - If true, knocks forward; if false, knocks backward.
 */
export function knock(entity, power, isForward = true) {
  let direction = entity.getViewDirection();
  let multiplier = isForward ? 1 : -1;
  entity.applyKnockback(
    direction.x * multiplier,
    direction.z * multiplier,
    power[0],
    power[1]
  );
}

/**
 * Sets knockback from a position.
 * @param {mc.Entity} entity - The entity to apply knockback to.
 * @param {object} position - The position to knock away from.
 * @param {number[]} power - Array containing [horizontalPower, verticalPower].
 */
export function setPosKnockback(entity, position, power) {
  entity.applyKnockback(
    entity.location.x - position.x,
    entity.location.z - position.z,
    power[0],
    power[1]
  );
}

/**
 * Calculates the distance between two positions in 3D space.
 * @param {object} loc1 - The first location {x, y, z}.
 * @param {object} loc2 - The second location {x, y, z}.
 * @returns {number} The distance between loc1 and loc2.
 */
export function calculateDistance(loc1, loc2) {
  return vec3_distance(loc1, loc2);
}

/**
 * Applies a status effect to an entity.
 * @param {mc.Entity} source - The entity to apply the effect to.
 * @param {string} effect - The effect identifier, such as "minecraft:speed" or "minecraft:strength".
 * @param {number} [duration=1] - The effect duration in seconds (converted to ticks internally).
 * @param {number} [amplifier=1] - The effect's amplifier level, which increases its strength.
 * @param {boolean} [particle=false] - Whether to show particles for the effect.
 * @returns {boolean} True if the effect was successfully applied, otherwise false.
 */
export function effect(source, effect, duration = 1, amplifier = 1, particle = false) {
  try {
    return source.addEffect(effect, duration * 20, {
      amplifier: amplifier,
      showParticles: particle,
    });
  } catch (error) {
    //console.error("Failed to apply effect:", error);
    return false;
  }
}
