import { world, ItemStack, system } from "@minecraft/server";

const recentlyUsed = new WeakSet();

const FaceDirectionMap = {
  Up: { x: 0, y: 1, z: 0 },
  Down: { x: 0, y: -1, z: 0 },
  North: { x: 0, y: 0, z: -1 },
  South: { x: 0, y: 0, z: 1 },
  West: { x: -1, y: 0, z: 0 },
  East: { x: 1, y: 0, z: 0 },
};

const rotationMap = {
  north: 0,
  east: 90,
  south: 180,
  west: 270,
};

const rotationEnumMap = {
  180: "None",
  270: "Rotate90",
  0: "Rotate180",
  90: "Rotate270",
};

// Replaceable but require waterlogging
const REPLACEABLE_WATER_BLOCKS = ["minecraft:water", "minecraft:seagrass"];

// Replaceable, treated like air
const REPLACEABLE_AIR_BLOCKS = [
  "minecraft:air",
  "minecraft:fern",
  "minecraft:large_fern",
  "minecraft:short_grass",
  "minecraft:tall_grass",
  "minecraft:short_dry_grass",
  "minecraft:tall_dry_grass",
  "minecraft:bush",
  "minecraft:deadbush",
  "minecraft:leaf_litter",
  "minecraft:nether_sprouts",
  "minecraft:crimson_roots",
  "minecraft:warped_roots",
  "minecraft:glow_lichen",
  "minecraft:vine",
  "minecraft:snow_layer",
];

function isReplaceable(block) {
  const id = block?.typeId ?? "";
  return (
    REPLACEABLE_AIR_BLOCKS.includes(id) || REPLACEABLE_WATER_BLOCKS.includes(id)
  );
}

function getNextFreeId(player) {
  const raw = player.getDynamicProperty("usedChestIds");
  const usedIds = raw ? new Set(JSON.parse(raw)) : new Set();

  let id = 1;
  while (usedIds.has(id)) id++;

  return { id, usedIds };
}

function getPlayerFacing(player) {
  const yaw = player.getRotation().y % 360;
  if (yaw < 0)
    return getPlayerFacing({ getRotation: () => ({ y: yaw + 360 }) });
  if (yaw >= 45 && yaw < 135) return "west";
  if (yaw >= 135 && yaw < 225) return "north";
  if (yaw >= 225 && yaw < 315) return "east";
  return "south";
}

export function handleEmptyChestJack(player, heldItem, event) {
  if (recentlyUsed.has(player)) return;
  recentlyUsed.add(player);
  system.runTimeout(() => recentlyUsed.delete(player), 4);

  const chest = event.block;
  const location = chest.location;
  const dimension = chest.dimension;

  const playerId = player.id;
  const { id: chestId, usedIds } = getNextFreeId(player);
  const structureName = `pb:${playerId}_${chestId}`;

  system.run(async () => {
    const inventoryComp = chest.getComponent("minecraft:inventory");
    if (inventoryComp) {
      const container = inventoryComp.container;

      if (container.size > 27) {
        player.sendMessage(
          "§4->§r Double chests are not supported by the Chest Jack. Please use a single chest."
        );
        return;
      }

      for (let i = 0; i < container.size; i++) {
        const item = container.getItem(i);
        if (item?.typeId === "pb_gat:gadget_chest_jack_loaded") {
          player.sendMessage(
            "§4->§r You can't pick up a chest containing a loaded Chest Jack!"
          );
          return;
        }
      }
    }

    try {
      const waterlogExists = chest.isWaterlogged;
      chest.setWaterlogged(false);

      await world.structureManager.createFromWorld(
        structureName,
        dimension,
        location,
        location
      );

      const facing = chest.permutation.getState("minecraft:cardinal_direction");

      if (inventoryComp) {
        const container = inventoryComp.container;
        for (let i = 0; i < container.size; i++) {
          container.setItem(i, undefined);
        }
      }

      usedIds.add(chestId);
      player.setDynamicProperty("usedChestIds", JSON.stringify([...usedIds]));

      chest.setType(waterlogExists ? "minecraft:water" : "minecraft:air");
      player.playSound("dig.wood", {
        volume: 1,
        pitch: 0.8,
        location: player.location,
      });
      player.sendMessage(
        "§4->§r Your speed has decreased due to carrying a heavy load!"
      );
      const loadedJack = new ItemStack("pb_gat:gadget_chest_jack_loaded", 1);
      loadedJack.setDynamicProperty("structure_name", structureName);
      loadedJack.setDynamicProperty("facing", facing);

      const equippable = player.getComponent("equippable");
      equippable.getEquipmentSlot("Mainhand")?.setItem(loadedJack);
    } catch (error) {
      player.sendMessage("§4->§r Failed to store chest. Try again.");
    }
  });
}

export function handleLoadedChestJack(player, heldItem, event) {
  if (recentlyUsed.has(player)) return;
  recentlyUsed.add(player);
  system.runTimeout(() => recentlyUsed.delete(player), 4);

  const structureName = heldItem.getDynamicProperty("structure_name");
  const originalFacing = heldItem.getDynamicProperty("facing");

  if (!structureName || !originalFacing) {
    player.sendMessage("§4->§r This chest jack is missing data.");
    return;
  }

  const face = event.blockFace;
  const baseBlock = event.block;
  const dimension = baseBlock.dimension;

  const offset = FaceDirectionMap[face];
  if (!offset) {
    player.sendMessage("§4->§rInvalid face direction.");
    return;
  }

  let targetPos = {
    x: baseBlock.location.x + offset.x,
    y: baseBlock.location.y + offset.y,
    z: baseBlock.location.z + offset.z,
  };

  let targetBlock = dimension.getBlock(targetPos);

  // If the interacted block is replaceable, use that instead
  if (isReplaceable(baseBlock)) {
    targetPos = baseBlock.location;
    targetBlock = baseBlock;
  }

  system.run(async () => {
    try {
      const chestInWater = REPLACEABLE_WATER_BLOCKS.includes(
        targetBlock.typeId
      );
      const playerFacing = getPlayerFacing(player);
      const originalAngle = rotationMap[originalFacing] ?? 0;
      const targetAngle = rotationMap[playerFacing] ?? 0;
      const angle = (targetAngle - originalAngle + 360) % 360;
      const rotation = rotationEnumMap[angle] ?? "None";

      await world.structureManager.place(structureName, dimension, targetPos, {
        rotation,
      });
      player.playSound("dig.wood", {
        volume: 1,
        pitch: 0.6,
        location: player.location,
      });

      if (chestInWater) {
        const placedBlock = dimension.getBlock(targetPos);
        placedBlock.setWaterlogged(true);
      }

      const match = structureName.match(/_(\d+)$/);
      if (match) {
        const id = parseInt(match[1]);
        const raw = player.getDynamicProperty("usedChestIds");
        const usedIds = raw ? new Set(JSON.parse(raw)) : new Set();
        usedIds.delete(id);
        player.setDynamicProperty("usedChestIds", JSON.stringify([...usedIds]));
        await world.structureManager.delete(structureName);
      }

      const emptyJack = new ItemStack("pb_gat:gadget_chest_jack_empty", 1);
      const equippable = player.getComponent("equippable");
      equippable.getEquipmentSlot("Mainhand")?.setItem(emptyJack);
    } catch (error) {
      player.sendMessage("§4->§r Failed to place chest. Try again.");
    }
  });
}
