//VECTORS
/**
 * Returns a zero vector {x: 0, y: 0, z: 0}.
 */
export function zero() {
  return { x: 0, y: 0, z: 0 };
}
/**
 * Returns a unit vector pointing south (positive Z axis).
 */
export function south() {
  return { x: 0, y: 0, z: 1 };
}
/**
 * Returns a unit vector pointing north (negative Z axis).
 */
export function north() {
  return { x: 0, y: 0, z: -1 };
}
/**
 * Returns a unit vector pointing east (positive X axis).
 */
export function east() {
  return { x: 1, y: 0, z: 0 };
}
/**
 * Returns a unit vector pointing west (negative X axis).
 */
export function west() {
  return { x: -1, y: 0, z: 0 };
}
/**
 * Returns a unit vector pointing up (positive Y axis).
 */
export function up() {
  return { x: 0, y: 1, z: 0 };
}
/**
 * Returns a unit vector pointing down (negative Y axis).
 */
export function down() {
  return { x: 0, y: -1, z: 0 };
}
/**
 * Calculates the Euclidean distance between two 3D vectors.
 * @param {Object} vector1
 * @param {Object} vector2
 * @returns {number}
 */
export function distance(vector1, vector2) {
  const dx = vector1.x - vector2.x;
  const dy = vector1.y - vector2.y;
  const dz = vector1.z - vector2.z;
  return Math.hypot(dx, dy, dz);
}
/**
 * Calculates the Manhattan distance between two 3D vectors.
 * @param {Object} vector1
 * @param {Object} vector2
 * @returns {number}
 */
export function manhattanDistance(vector1, vector2) {
  const dx = Math.abs(vector1.x - vector2.x);
  const dy = Math.abs(vector1.y - vector2.y);
  const dz = Math.abs(vector1.z - vector2.z);
  return dx + dy + dz;
}
/**
 * Scales a vector by a scalar value.
 * @param {Object} vector
 * @param {number} scalar
 * @returns {Object}
 */
export function scale(vector, scalar) {
  return {
    x: vector.x * scalar,
    y: vector.y * scalar,
    z: vector.z * scalar,
  };
}
/**
 * Calculates the dot product of two vectors.
 * @param {Object} vector1
 * @param {Object} vector2
 * @returns {number}
 */
export function dot(vector1, vector2) {
  return vector1.x * vector2.x + vector1.y * vector2.y + vector1.z * vector2.z;
}
/**
 * Calculates the cosine similarity between two vectors.
 * Returns a value between -1 and 1, where 1 means vectors point in the same direction,
 * -1 means they point in opposite directions, and 0 means they are perpendicular.
 * @param {Object} v1 - First vector with x, y, z components
 * @param {Object} v2 - Second vector with x, y, z components
 * @returns {number|undefined} Cosine similarity value, or undefined if either vector has zero magnitude
 */
export function cosineSimilarity(v1, v2) {
  const dot = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
  const magV1 = Math.sqrt(v1.x ** 2 + v1.y ** 2 + v1.z ** 2);
  const magV2 = Math.sqrt(v2.x ** 2 + v2.y ** 2 + v2.z ** 2);

  if (magV1 === 0 || magV2 === 0) {
    return undefined;
  }

  return dot / (magV1 * magV2);
}
/**
 * Calculates the distance between two vectors in the XZ plane (ignoring Y component).
 * Useful for horizontal distance calculations in 3D space.
 * @param {Object} v1 - First vector with x, y, z components
 * @param {Object} v2 - Second vector with x, y, z components
 * @returns {number} Horizontal distance between the two vectors
 */
export function horizontalDistance(v1, v2) {
  const dx = v1.x - v2.x;
  const dz = v1.z - v2.z;
  return Math.hypot(dx, dz);
}
/**
 * Generates a random unit vector in 3D space with uniform distribution on the unit sphere.
 * @returns {Object} A normalized random vector with x, y, z components
 */
export function random() {
  const vector = {
    x: Math.random() * 2 - 1,
    y: Math.random() * 2 - 1,
    z: Math.random() * 2 - 1,
  };
  return normalize(vector);
}
/**
 * Calculates the cross product of two vectors.
 * @param {Object} vector1
 * @param {Object} vector2
 * @returns {Object}
 */
export function cross(vector1, vector2) {
  return {
    x: vector1.y * vector2.z - vector1.z * vector2.y,
    y: vector1.z * vector2.x - vector1.x * vector2.z,
    z: vector1.x * vector2.y - vector1.y * vector2.x,
  };
}
/**
 * Adds two vectors together.
 * @param {Object} vector1
 * @param {Object} vector2
 * @returns {Object}
 */
export function add(vector1, vector2) {
  return {
    x: vector1.x + vector2.x,
    y: vector1.y + vector2.y,
    z: vector1.z + vector2.z,
  };
}
/**
 * Subtracts vector2 from vector1.
 * @param {Object} vector1
 * @param {Object} vector2
 * @returns {Object}
 */
export function subtract(vector1, vector2) {
  return {
    x: vector1.x - vector2.x,
    y: vector1.y - vector2.y,
    z: vector1.z - vector2.z,
  };
}
/**
 * Returns the magnitude (length) of a vector.
 * @param {Object} v
 * @returns {number}
 */
export function magnitude(v) {
  return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
}
/**
 * Normalizes a vector to unit length.
 * @param {Object} vector
 * @returns {Object}
 */
export function normalize(vector) {
  const mag = magnitude(vector);
  if (mag === 0) return { x: 0, y: 0, z: 0 };
  const invMag = 1 / mag;
  return {
    x: vector.x * invMag,
    y: vector.y * invMag,
    z: vector.z * invMag,
  };
}
/**
 * Reflects a vector (negates all components).
 * @param {Object} vector
 * @returns {Object}
 */
export function reflect(vector) {
  return {
    x: -vector.x,
    y: -vector.y,
    z: -vector.z,
  };
}
/**
 * Projects a vector onto the XZ plane (sets y to 0).
 * @param {Object} vector
 * @returns {Object}
 */
export function projectToXZ(vector) {
  return {
    x: vector.x,
    y: 0,
    z: vector.z,
  };
}
/**
 * Offsets a location by a normalized vector scaled by an amount.
 * @param {Object} location
 * @param {Object} offsetVector
 * @param {number} amount
 * @returns {Object}
 */
export function offset(location, offsetVector, amount) {
  const normalizedVector = normalize(offsetVector);
  const newLocation = {
    x: location.x + normalizedVector.x * amount,
    y: location.y + normalizedVector.y * amount,
    z: location.z + normalizedVector.z * amount,
  };

  return newLocation;
}
/**
 * Offsets a location relative to a view direction using a local coordinate system.
 * Creates a right-handed coordinate system where X is right, Y is up, and Z is forward.
 * @param {Object} location - The base location to offset from
 * @param {Object} viewDirection - The forward direction vector
 * @param {Object} offsetVector - Local offset (x=right, y=up, z=forward)
 * @returns {Object} The offset location in world coordinates
 */
export function offsetRelative(location, viewDirection, offsetVector) {
  const globalUp = { x: 0, y: 1, z: 0 };
  const right = normalize(cross(globalUp, viewDirection)); // Get the right vector from the view direction and global up vector
  const up = normalize(cross(viewDirection, right)); // Get the local up vector from the view direction and right vector

  return {
    x:
      location.x +
      right.x * offsetVector.x +
      up.x * offsetVector.y +
      viewDirection.x * offsetVector.z,
    y:
      location.y +
      right.y * offsetVector.x +
      up.y * offsetVector.y +
      viewDirection.y * offsetVector.z,
    z:
      location.z +
      right.z * offsetVector.x +
      up.z * offsetVector.y +
      viewDirection.z * offsetVector.z,
  };
}
/**
 * Flips a vector (scales by -1).
 * @param {Object} vector
 * @returns {Object}
 */
export function flip(vector) {
  return scale(vector, -1);
}
/**
 * Calculates the normal vector of a plane defined by three points.
 * @param {Object} pointA
 * @param {Object} pointB
 * @param {Object} pointC
 * @returns {Object}
 */
export function planeNormal(pointA, pointB, pointC) {
  // Compute vectors AB and AC
  const vectorAB = subtract(pointB, pointA);
  const vectorAC = subtract(pointC, pointA);

  // Compute the cross product of AB and AC to get the normal
  const normal = cross(vectorAB, vectorAC);

  // Normalize the normal vector
  return normalize(normal);
}
/**
 * Finds the intersection of a line and a plane in 3D space.
 * Returns both the 3D intersection point and its 2D relative position on the plane.
 * @param {Object} lineStart - Starting point of the line
 * @param {Object} lineDirection - Direction vector of the line
 * @param {Object} planeCenter - A point on the plane
 * @param {Object} planeNormal - Normal vector of the plane
 * @returns {Object|null} Object with intersection3D and relative2D properties, or null if no intersection
 */
export function intersectPlane(lineStart, lineDirection, planeCenter, planeNormal) {
  const dot = dot(lineDirection, planeNormal);
  if (Math.abs(dot) < 1e-6) return null; // No intersection (the vector is parallel to the plane)

  const t = dot(subtract(lineStart, planeCenter), planeNormal) / dot;
  if (t < 0)
    return {
      intersection3D: null,
      relative2D: null,
    }; // No valid intersection

  const intersection3D = {
    x: lineStart.x + t * lineDirection.x,
    y: lineStart.y + t * lineDirection.y,
    z: lineStart.z + t * lineDirection.z,
  };

  const intersectionVector = subtract(intersection3D, planeCenter);
  const relative2D = {
    x: dot(intersectionVector, cross(planeNormal, lineDirection)),
    y: dot(intersectionVector, cross(cross(planeNormal, lineDirection), planeNormal)),
  };

  return {
    intersection3D: intersection3D,
    relative2D: relative2D,
  };
}
/**
 * Calculates the angle (in radians) between two vectors.
 * @param {Object} vector1
 * @param {Object} vector2
 * @returns {number}
 */
export function angleBetween(vector1, vector2) {
  return Math.acos(dot(vector1, vector2) / (magnitude(vector1) * magnitude(vector2)));
}
/**
 * Calculates the yaw (horizontal rotation) in degrees from a direction vector.
 * Returns the angle around the Y-axis, with 0° pointing north (+Z) and 90° pointing east (+X).
 * @param {Object} vector - Direction vector
 * @returns {number} Yaw angle in degrees (-180 to 180)
 */
export function yaw(vector) {
  const dx = vector.x;
  const dz = vector.z;

  const yaw = Math.atan2(dx, dz);

  return yaw * (180 / Math.PI);
}
/**
 * Calculates the pitch (vertical rotation) in degrees from a direction vector.
 * Returns the angle of elevation/depression, with 0° being horizontal, +90° pointing up, -90° pointing down.
 * @param {Object} vector - Direction vector
 * @returns {number} Pitch angle in degrees (-90 to 90)
 */
export function pitch(vector) {
  const dx = vector.x;
  const dy = vector.y;
  const dz = vector.z;

  const pitch = Math.asin(dy / Math.hypot(dx, dz, dy));

  return pitch * (180 / Math.PI);
}
/**
 * Sets the magnitude (length) of a vector to a given value.
 * @param {Object} vector
 * @param {number} magnitude
 * @returns {Object}
 */
export function setMagnitude(vector, magnitude) {
  return scale(normalize(vector), magnitude);
}
/**
 * Checks if two vectors are equal (all components match).
 * @param {Object} vec1
 * @param {Object} vec2
 * @returns {boolean}
 */
export function areEqual(vec1, vec2) {
  return vec1.x === vec2.x && vec1.y === vec2.y && vec1.z === vec2.z;
}
/**
 * Projects a vector onto the surface of a cone defined by axis and angle.
 * @param {Object} vector
 * @param {Object} coneAxis
 * @param {number} coneAngle (in radians)
 * @returns {Object}
 */
export function projectToCone(vector, coneAxis, coneAngle) {
  const axisNormalized = normalize(coneAxis);
  const dotProduct = dot(vector, axisNormalized);
  const projectionLength = dotProduct;
  const perpendicularLength = projectionLength * Math.tan(coneAngle);

  const projectionOntoAxis = scale(axisNormalized, dotProduct);
  const perpendicularComponent = subtract(vector, projectionOntoAxis);
  const perpendicularAdjusted = scale(
    perpendicularComponent,
    perpendicularLength / magnitude(perpendicularComponent)
  );

  return add(projectionOntoAxis, perpendicularAdjusted);
}
/**
 * Constrains a vector to be within a cone defined by axis and angle.
 * @param {Object} vector
 * @param {Object} coneAxis
 * @param {number} coneAngle (in radians)
 * @returns {Object}
 */
export function constrainToCone(vector, coneAxis, coneAngle) {
  if (angleBetween(vector, coneAxis) > coneAngle) {
    return projectToCone(vector, coneAxis, coneAngle);
  } else {
    return vector;
  }
}
/**
 * Returns the axis ('x', 'y', or 'z') with the largest absolute value in the vector.
 * @param {Object} _vector
 * @returns {string}
 */
export function biggestAxis(_vector) {
  const vector = { x: Math.abs(_vector.x), y: Math.abs(_vector.y), z: Math.abs(_vector.z) };
  if (vector.x > vector.y && vector.x > vector.z) {
    return "x";
  } else if (vector.y > vector.x && vector.y > vector.z) {
    return "y";
  } else if (vector.z > vector.x && vector.z > vector.y) {
    return "z";
  } else {
    return "y";
  }
}
/**
 * Computes an escape vector based on which sides are blocked (solid).
 * Creates a movement direction that avoids obstacles by moving away from blocked sides.
 * @param {boolean} top - Whether the top side is blocked
 * @param {boolean} bottom - Whether the bottom side is blocked
 * @param {boolean} left - Whether the left side is blocked
 * @param {boolean} right - Whether the right side is blocked
 * @param {boolean} front - Whether the front side is blocked
 * @param {boolean} back - Whether the back side is blocked
 * @returns {Object} Normalized escape direction vector
 */
export function escapeVector(top, bottom, left, right, front, back) {
  let escapeVector = { x: 0, y: 0, z: 0 };

  // Adjust for Y-axis (up/down)
  if (top) escapeVector.y -= 1; // If top is solid, move down
  if (bottom) escapeVector.y += 1; // If bottom is solid, move up

  // Adjust for X-axis (left/right)
  if (left) escapeVector.x += 1; // If left is solid, move right
  if (right) escapeVector.x -= 1; // If right is solid, move left

  // Adjust for Z-axis (front/back)
  if (front) escapeVector.z += 1; // If front is solid, move back
  if (back) escapeVector.z -= 1; // If back is solid, move forward

  // Normalize the escape vector (to keep movement consistent in all directions)
  return normalize(escapeVector);
}
/**
 * Checks if a vector is parallel to a major axis within a tolerance.
 * @param {Object} vector
 * @param {number} [tolerance=1e-6]
 * @returns {string|boolean} Axis name or false
 */
export function isParallelToAxis(vector, tolerance = 1e-6) {
  // Check if vector is parallel to the X-axis
  if (Math.abs(vector.y) < tolerance && Math.abs(vector.z) < tolerance) {
    return "x";
  }
  // Check if vector is parallel to the Y-axis
  else if (Math.abs(vector.x) < tolerance && Math.abs(vector.z) < tolerance) {
    return "y";
  }
  // Check if vector is parallel to the Z-axis
  else if (Math.abs(vector.x) < tolerance && Math.abs(vector.y) < tolerance) {
    return "z";
  }
  // If not parallel to any axis
  else {
    return false;
  }
}
/**
 * Applies Math.floor to all components of a vector.
 * @param {Object} vector
 * @returns {Object}
 */
export function floor(vector) {
  return { x: Math.floor(vector.x), y: Math.floor(vector.y), z: Math.floor(vector.z) };
}
/**
 * Applies Math.ceil to all components of a vector.
 * @param {Object} vector
 * @returns {Object}
 */
export function ceil(vector) {
  return { x: Math.ceil(vector.x), y: Math.ceil(vector.y), z: Math.ceil(vector.z) };
}
/**
 * Checks if a vector lies on a major axis plane within a tolerance.
 * Useful for determining if a vector is primarily 2D in nature.
 * @param {Object} vector - Vector to check
 * @param {number} [tolerance=0.05] - Maximum deviation from plane
 * @returns {string|boolean} Plane name ('xz', 'xy', 'yz') or false if not on any plane
 */
export function isOnAxisPlane(vector, tolerance = 0.05) {
  const isXZ = Math.abs(vector.y) < tolerance;
  const isXY = Math.abs(vector.z) < tolerance;
  const isYZ = Math.abs(vector.x) < tolerance;
  if (isXZ) return "xz";
  if (isXY) return "xy";
  if (isYZ) return "yz";
  return false;
}
/**
 * Snaps a vector to the nearest major axis (returns a unit vector along the largest component).
 * Useful for converting arbitrary directions to cardinal directions.
 * @param {Object} param0 - Vector to snap with x, y, z components
 * @returns {Object} Unit vector along the dominant axis (+-1, 0, 0), (0, +-1, 0), or (0, 0, +-1)
 */
export function snapToAxis({ x, y, z }) {
  const absX = Math.abs(x);
  const absY = Math.abs(y);
  const absZ = Math.abs(z);

  if (absX >= absY && absX >= absZ) {
    return { x: Math.sign(x), y: 0, z: 0 };
  } else if (absY >= absX && absY >= absZ) {
    return { x: 0, y: Math.sign(y), z: 0 };
  } else {
    return { x: 0, y: 0, z: Math.sign(z) };
  }
}
/**
 * Rotates a vector around a given axis by a specified angle using Rodrigues' rotation formula.
 * @param {Object} vector - Vector to rotate
 * @param {Object} axis - Rotation axis (will be normalized)
 * @param {number} angle - Rotation angle in degrees
 * @returns {Object} Rotated vector
 */
export function rotateAroundAxis(vector, axis, angle) {
  // Convert angle to radians
  const rad = (angle * Math.PI) / 180;

  // Normalize the axis
  const k = normalize(axis);

  // Compute the components of Rodrigues' formula
  const cosTheta = Math.cos(rad);
  const sinTheta = Math.sin(rad);

  const dotProduct = dot(k, vector);
  const crossProduct = cross(k, vector);

  return {
    x: vector.x * cosTheta + crossProduct.x * sinTheta + k.x * dotProduct * (1 - cosTheta),
    y: vector.y * cosTheta + crossProduct.y * sinTheta + k.y * dotProduct * (1 - cosTheta),
    z: vector.z * cosTheta + crossProduct.z * sinTheta + k.z * dotProduct * (1 - cosTheta),
  };
}
/**
 * Generates a random unit vector perpendicular to the given vector.
 * @param {Object} vector
 * @returns {Object}
 */
export function randomPerpendicular(vector) {
  let randomVector;
  do {
    randomVector = random();
  } while (areParallel(vector, randomVector));

  // Compute the perpendicular vector using cross product
  const perpendicularVector = cross(vector, randomVector);

  // Normalize the result to make it a unit vector
  return normalize(perpendicularVector);
}
/**
 * Checks if two vectors are parallel (cross product is zero vector).
 * @param {Object} v1
 * @param {Object} v2
 * @returns {boolean}
 */
export function areParallel(v1, v2) {
  const crossProduct = cross(v1, v2);
  return crossProduct.x === 0 && crossProduct.y === 0 && crossProduct.z === 0;
}
/**
 * Generates an array of vectors within a cone around a given vector.
 * Each vector is randomly rotated within the cone angle from the base vector.
 * @param {Object} vector - Base direction vector (cone axis)
 * @param {number} coneAngle - Maximum deviation angle in degrees
 * @param {number} amount - Number of vectors to generate
 * @returns {Object[]} Array of vectors within the cone
 */
export function coneVectors(vector, coneAngle, amount) {
  let vectors = [];
  for (let i = 0; i < amount; i++) {
    const randomPerpendicular = randomPerpendicular(vector);
    const randomAngle = (Math.random() * coneAngle) / 2;
    vectors.push(rotateAroundAxis(vector, randomPerpendicular, randomAngle));
  }
  return vectors;
}
/**
 * Generates all integer coordinates within a sphere of given center and radius.
 * Useful for iterating over all block positions within a spherical region.
 * @param {Object} center - Center point of the sphere
 * @param {number} radius - Radius of the sphere
 * @returns {Object[]} Array of all integer coordinate vectors inside the sphere
 */
export function generateSphereCoordinates(center, radius) {
  const { x: cx, y: cy, z: cz } = center;
  const radiusSquared = radius * radius;
  const coordinates = [];

  // Loop through all integer coordinates within a cube enclosing the sphere
  for (let x = cx - radius; x <= cx + radius; x++) {
    for (let y = cy - radius; y <= cy + radius; y++) {
      for (let z = cz - radius; z <= cz + radius; z++) {
        // Check if the point is inside the sphere
        const distanceSquared = (x - cx) ** 2 + (y - cy) ** 2 + (z - cz) ** 2;
        if (distanceSquared <= radiusSquared) {
          coordinates.push({ x, y, z });
        }
      }
    }
  }
  return coordinates;
}
