/**
 * Interface imports for JSDoc.
 * @typedef {import("@minecraft/server").Vector3} Vector3
 */

import { Entity, world } from "@minecraft/server";
export function getStringFromArray(arry, string) {
  return arry.find((item) => item.startsWith(string));
}

export function checkStringInArray(arr, string) {
  return arr.some((item) => item.startsWith(string));
}

/**
 * Gets the Gamemode of a player
 * @param {Player} player player to get
 * @param {GameMode} gm The gamemode.
 * @returns {GameMode}
 * @example if (getGamemode(player, 'creative')) return;
 */
export function getGamemode(player, gm) {
  return world.getPlayers({ name: player.name, gameMode: gm }).length;
}

export function randomTrueFalse() {
  return Math.random() < 0.5;
}

export function getLocationbyFacingBlock(face, location) {
  let placeLoc;
  switch (face) {
    case "Up":
      placeLoc = {
        x: location.x + 0.5,
        y: location.y + 1,
        z: location.z + 0.5,
      };
      break;
    case "Down":
      placeLoc = {
        x: location.x + 0.5,
        y: location.y - 1,
        z: location.z + 0.5,
      };
      break;
    case "North":
      placeLoc = { x: location.x + 0.5, y: location.y, z: location.z - 0.5 };
      break;
    case "South":
      placeLoc = { x: location.x + 0.5, y: location.y, z: location.z + 1.5 };
      break;
    case "West":
      placeLoc = { x: location.x - 0.5, y: location.y, z: location.z + 0.5 };
      break;
    case "East":
      placeLoc = { x: location.x + 1.5, y: location.y, z: location.z + 0.5 };
      break;
    default:
      break;
  }
  return placeLoc;
}

export function getEntityRotation(entity) {
  let entityRotation = entity.getRotation().y;
  if (entityRotation >= -22.5 && entityRotation <= 22.5) {
    // //////////world.sendMessage("0");
    //////////world.sendMessage("180");
    return 180;
  }
  if (entityRotation >= -67.5 && entityRotation <= -22.5) {
    // //////////world.sendMessage("0");
    //////////world.sendMessage("180");
    return 180;
  }
  if (entityRotation >= -112.5 && entityRotation <= -67.5) {
    // //////////world.sendMessage("90");
    //////////world.sendMessage("90");
    return 90;
  }
  if (entityRotation >= -157.5 && entityRotation <= -112.5) {
    // //////////world.sendMessage("35");
    //////////world.sendMessage("90");
    return 90;
  }
  if ((entityRotation >= 157.5 && entityRotation <= 180) || (entityRotation >= -180 && entityRotation <= -157.5)) {
    // //////////world.sendMessage("180");
    //////////world.sendMessage("0");
    return 0;
  }
  if (entityRotation >= 112.5 && entityRotation <= 157.5) {
    // //////////world.sendMessage("225");
    //////////world.sendMessage("270");
    return 270;
  }
  if (entityRotation >= 67.5 && entityRotation <= 112.5) {
    // //////////world.sendMessage("270");
    //////////world.sendMessage("270");
    return 270;
  }
  if (entityRotation >= 22.5 && entityRotation <= 67.5) {
    // //////////world.sendMessage("315");
    //////////world.sendMessage("180");
    return 180;
  }
}

export function getElementPosition(array, element) {
  const position = array.indexOf(element);
  if (position === -1) {
    return `${element} is not in the list`;
  }
  return position;
}

export function getElementPositionArrayWithObjects(array, patternValue) {
  const position = array.findIndex((element) => element.pattern === patternValue);
  if (position === -1) {
    return `${patternValue} is not in the list`;
  }
  return position;
}

export function stringBetween(str, start, end) {
  const startIndex = str.indexOf(start);
  const endIndex = str.indexOf(end, startIndex + start.length);

  if (startIndex === -1 || endIndex === -1) {
    return null; // If start or end strings are not found
  }

  return str.substring(startIndex + start.length, endIndex);
}

export function getStringBeforeasdsd(substring, fullString) {
  const index = fullString.indexOf(substring);
  if (index !== -1) {
    return fullString.substring(0, index);
  } else {
    return fullString; // Or return an empty string, depending on your needs
  }
}

export function getStringAfter(substring, fullString) {
  const index = fullString.indexOf(substring);
  if (index !== -1) {
    return fullString.substring(index + substring.length);
  } else {
    return ""; // Or return the fullString, depending on your needs
  }
}

export function getStringBefore(str, searchString) {
  const index = str.indexOf(searchString);
  return index === -1 ? str : str.substring(0, index);
}

export function checkBelowEntityDespawn(player, entityTypeId) {
  const nearbyEntities = world.getDimension(player.dimension.id).getEntities({
    type: "hb_hr:" + entityTypeId + "",
    location: player.location,
    maxDistance: 200,
  });
  nearbyEntities.forEach((entity) => {
    if (!entity.isValid()) return;
    else {
      let entityBlockBelow = {
        x: entity.location.x,
        y: entity.location.y - 0.5,
        z: entity.location.z,
      };
      let blockBelow = world.getDimension(entity.dimension.id).getBlock(entityBlockBelow);
      if (!blockBelow) return;
      if (blockBelow.isAir || blockBelow.isLiquid) {
        if (!entity.hasTag("hb_hr:start_block_despawn")) {
          // entity.teleport({ x: entity.location.x, y: entity.location.y + 0.5, z: entity.location.z });
          entity.addTag("hb_hr:start_block_despawn");
        }
        entity.runCommand("/scriptevent hb_hr:" + entityTypeId + "_despawn");
        return;
      }
    }
  });
}

export function checkBelowEntityDespawnRoof(player, familyType) {
  const nearbyEntities = world.getDimension(player.dimension.id).getEntities({
    families: [familyType],
    location: player.location,
    maxDistance: 200,
  });
  nearbyEntities.forEach((entity) => {
    if (!entity.isValid()) return;
    else {
      let entityBlockBelow = {
        x: entity.location.x,
        y: entity.location.y - 0.5,
        z: entity.location.z,
      };
      let blockBelow = world.getDimension(entity.dimension.id).getBlock(entityBlockBelow);
      if (!blockBelow) return;
      if (blockBelow.isAir || blockBelow.isLiquid) {
        if (!entity.hasTag("hb_hr:start_block_despawn")) {
          // entity.teleport({ x: entity.location.x, y: entity.location.y + 0.5, z: entity.location.z });
          entity.addTag("hb_hr:start_block_despawn");
        }
        entity.runCommand("/scriptevent hb_hr:roof_despawn");
        return;
      }
    }
  });
}

export function getSideBlockLocations(face, middleLocation) {
  let leftBlock, rightBlock;
  switch (face) {
    case "north":
      leftBlock = {
        x: middleLocation.x - 1,
        y: middleLocation.y,
        z: middleLocation.z,
      };
      rightBlock = {
        x: middleLocation.x + 1,
        y: middleLocation.y,
        z: middleLocation.z,
      };
      break;
    case "south":
      leftBlock = {
        x: middleLocation.x + 1,
        y: middleLocation.y,
        z: middleLocation.z,
      };
      rightBlock = {
        x: middleLocation.x - 1,
        y: middleLocation.y,
        z: middleLocation.z,
      };
      break;
    case "west":
      leftBlock = {
        x: middleLocation.x,
        y: middleLocation.y,
        z: middleLocation.z + 1,
      };
      rightBlock = {
        x: middleLocation.x,
        y: middleLocation.y,
        z: middleLocation.z - 1,
      };
      break;
    case "east":
      leftBlock = {
        x: middleLocation.x,
        y: middleLocation.y,
        z: middleLocation.z - 1,
      };
      rightBlock = {
        x: middleLocation.x,
        y: middleLocation.y,
        z: middleLocation.z + 1,
      };
      break;
    default:
      console.error("Unexpected face direction: " + face);
      return null;
  }

  return { leftBlock, rightBlock };
}

export function getSideBlockLocations2Blocks(face, middleLocation) {
  let leftBlock, rightBlock, nextLeftBlock, nextRightBlock;

  switch (face) {
    case "north":
      leftBlock = {
        x: middleLocation.x - 1,
        y: middleLocation.y,
        z: middleLocation.z,
      };
      rightBlock = {
        x: middleLocation.x + 1,
        y: middleLocation.y,
        z: middleLocation.z,
      };
      nextLeftBlock = {
        x: middleLocation.x - 2,
        y: middleLocation.y,
        z: middleLocation.z,
      };
      nextRightBlock = {
        x: middleLocation.x + 2,
        y: middleLocation.y,
        z: middleLocation.z,
      };
      break;
    case "south":
      leftBlock = {
        x: middleLocation.x + 1,
        y: middleLocation.y,
        z: middleLocation.z,
      };
      rightBlock = {
        x: middleLocation.x - 1,
        y: middleLocation.y,
        z: middleLocation.z,
      };
      nextLeftBlock = {
        x: middleLocation.x + 2,
        y: middleLocation.y,
        z: middleLocation.z,
      };
      nextRightBlock = {
        x: middleLocation.x - 2,
        y: middleLocation.y,
        z: middleLocation.z,
      };
      break;
    case "west":
      leftBlock = {
        x: middleLocation.x,
        y: middleLocation.y,
        z: middleLocation.z + 1,
      };
      rightBlock = {
        x: middleLocation.x,
        y: middleLocation.y,
        z: middleLocation.z - 1,
      };
      nextLeftBlock = {
        x: middleLocation.x,
        y: middleLocation.y,
        z: middleLocation.z + 2,
      };
      nextRightBlock = {
        x: middleLocation.x,
        y: middleLocation.y,
        z: middleLocation.z - 2,
      };
      break;
    case "east":
      leftBlock = {
        x: middleLocation.x,
        y: middleLocation.y,
        z: middleLocation.z - 1,
      };
      rightBlock = {
        x: middleLocation.x,
        y: middleLocation.y,
        z: middleLocation.z + 1,
      };
      nextLeftBlock = {
        x: middleLocation.x,
        y: middleLocation.y,
        z: middleLocation.z - 2,
      };
      nextRightBlock = {
        x: middleLocation.x,
        y: middleLocation.y,
        z: middleLocation.z + 2,
      };
      break;
    default:
      console.error("Unexpected face direction: " + face);
      return null;
  }

  return { leftBlock, rightBlock, nextLeftBlock, nextRightBlock };
}

export function unloadInventoryandSpawnItems(entity) {
  // world.sendMessage("spawn items");
  const inventoryComponent = entity.getComponent("inventory");
  if (!inventoryComponent) return;
  let { container, inventorySize } = inventoryComponent;
  for (let i = 0; i < inventorySize; i++) {
    let foundItem = container.getItem(i);
    if (foundItem) world.getDimension(entity.dimension.id).spawnItem(foundItem, entity.location);
  }
}

/**
 * Returns the Y rotation needed to point at the given location.
 * The result is in degrees in range (-180, 180).
 * @param {Vector3} from
 * @param {Vector3} to
 * @returns number
 */
export function getRotationPointingAt(from, to) {
  const { x: fromX, z: fromZ } = from;
  const { x: toX, z: toZ } = to;
  let rotation = Math.atan2(toZ - fromZ, toX - fromX);
  // Must be degrees in range (-180, 180)
  rotation = (rotation * (180 / Math.PI) + 720 - 90) % 360;
  rotation = rotation > 180 ? rotation - 360 : rotation;
  return rotation;
}

/**
 * Shuffles an array in place using the Fisher-Yates algorithm.
 * @param {Array} array
 */
export function shuffle(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
}

/**
 * Creates a shuffled copy of an array using Fisher-Yates algorithm.
 * @param {Array} array
 * @returns {Array}
 */
export function shuffled(array) {
  const shuffled = [...array];
  shuffle(shuffled);
  return shuffled;
}

/**
 * Calculates the angular size of an object at a given distance IN RADIANS.
 * @param {number} objectSize - The size of the object.
 * @param {number} distance - The distance from the observer to the object.
 */
export function angularSize(objectSize, distance) {
  return objectSize > 0 ? 2 * Math.atan2(objectSize/2, distance) : 0;
}

/**
 * Checks if a target point is within the view cone of an observer
 * @param {Vector3} observerPosition
 * @param {Vector3} observerViewDirection
 * @param {Vector3} targetPoint
 * @param {number} maxAngle
 * @param {number} [objectSize=0]
 */
export function isPointInViewCone(observerPosition, observerViewDirection, targetPoint, maxAngle, objectSize = 0) {
  const observerToTargetVect = {
    x: targetPoint.x - observerPosition.x,
    y: targetPoint.y - observerPosition.y,
    z: targetPoint.z - observerPosition.z,
  };
  
  const distance = Math.hypot(observerToTargetVect.x, observerToTargetVect.y, observerToTargetVect.z);
  
  const angularSizeRadians = angularSize(objectSize, distance);
    
  // Add the angular size to the max angle
  const effectiveMaxAngleRadians = (maxAngle * Math.PI) / 180 + angularSizeRadians;
  const cosAngle = Math.cos(effectiveMaxAngleRadians);
  
  // Normalize the observer-to-target vector
  const observerToTargetDirection = {
    x: observerToTargetVect.x / distance,
    y: observerToTargetVect.y / distance,
    z: observerToTargetVect.z / distance,
  };

  // Calculate dot product of normalized vectors
  const dotProduct = 
    observerViewDirection.x * observerToTargetDirection.x + 
    observerViewDirection.y * observerToTargetDirection.y + 
    observerViewDirection.z * observerToTargetDirection.z;
    
  return dotProduct > cosAngle;
}


/**
 * Whether a point is being watched by entities.
 * @param {Entity[]} entities
 * @param {Vector3} point
 * @param {number} maxAngle max angle considered to be looking at the golem in degrees
 * @param {number} [objectSize=0] - The size of the object to calculate angular size
 */
export function isBeingWatchedBy(entities, point, maxAngle, objectSize = 0) {
  for (const entity of entities) {
    if (isPointInViewCone(
      entity.location, 
      entity.getViewDirection(), 
      point, 
      maxAngle, 
      objectSize
    )) {
      return true;
    }
  }
  return false;
}

/**
 * Whether a point is close to any of the given entities.
 * @param {Entity[]} entities
 * @param {Vector3} point
 * @param {number} maxDistance
 */
export function isCloseToAny(entities, point, maxDistance) {
  for (const entity of entities) {
    if (Math.hypot(point.x - entity.location.x, point.y - entity.location.y, point.z - entity.location.z) <= maxDistance) {
      return true;
    }
  }
  return false;
}

/**
 * Returns the Euclidean block-distance between two positions.
 *
 * @param {{x:number,y:number,z:number}} from
 * @param {{x:number,y:number,z:number}} to
 * @returns {number}  The straight‐line block distance
 */
export function getBlockDistance(from, to) {
  const dx = to.x - from.x;
  const dy = to.y - from.y;
  const dz = to.z - from.z;
  return Math.sqrt(dx * dx + dy * dy + dz * dz);
}

export function checkBlockAboveFor2BlockHighBlock(checkLocation, player, blocklocation) {
  let BlockAbove = { x: checkLocation.x, y: checkLocation.y + 1, z: checkLocation.z };
  let blockAbove = world.getDimension(player.dimension.id).getBlock(BlockAbove);
  if (blockAbove.typeId == "minecraft:air") {
    let closeEntity = world.getDimension(player.dimension.id).getEntities({
      location: blocklocation,
      maxDistance: 0.5,
      closest: 1,
    });
    if (closeEntity.length === 0 || closeEntity[0] == undefined) {
      return false;
    } else {
      player.runCommand('/tellraw @s {"rawtext":[{"translate":"hb_hr.place_entity_error"}]}');
      return true;
    }
  } else {
    player.runCommand('/tellraw @s {"rawtext":[{"translate":"hb_hr.block_above_error"}]}');
    return true;
  }
}
