import { world, system, ItemStack, MolangVariableMap } from "@minecraft/server";
import { GAMGroupManager } from "./GAMGroupManager.js";
import {
  getMultiblockEntity,
  getBlockFromLocation,
  mbEntityGiveItem,
} from "./multiblockUtility.js";
import {
  getHeldItemsFromGroup,
  matchRecipe,
} from "./GAMLeverInteractionManager.js";

/**
 * Utility: Given a dimension and entity ID, return the live entity instance or null if not found.
 */
function getEntityById(dim, id) {
  if (!id) return null;
  const entities = dim.getEntities();
  for (const entity of entities) {
    if (entity.id === id) return entity;
  }
  return null;
}

/**
 * Get the lever entity of a group, or null if not found.
 */
export function getLeverEntity(group, dim) {
  return getEntityById(dim, group.lever.id);
}

/**
 * Get all the item entities of a group as live entity objects (filtering out missing).
 */
export function getItemEntities(group, dim) {
  if (!group || !group.itemEntities) return [];
  return group.itemEntities
    .map((item) => getEntityById(dim, item.id))
    .filter((e) => e !== null);
}

export function runMultiblockProcess(groupId, player, leverEntity) {
  const group = GAMGroupManager.getGroup(groupId);
  if (!group) return;

  const dim = player.dimension;
  const items = getHeldItemsFromGroup(group, dim);
  const recipe = matchRecipe(items);
  if (!recipe) return;

  // Step 1: Visuals
  const allBlockLocs = [group.master, ...group.blocks];

  // Play animation on lever
  system.run(() => {
    leverEntity.playAnimation("animation.pb_gat.gam_lever.pull", {
      blendOutTime: 1,
      stopExpression: "query.any_animation_finished",
    });
  });

  // Spawn particles from item entities
  const itemEntities = getItemEntities(group, dim);
  for (const itemEntity of itemEntities) {
    system.run(() => {
      system.runTimeout(() => {
        itemEntity.playAnimation("animation.pb_gat.mb_gam_entity.shrink", {
          blendOutTime: 0,
          stopExpression: "query.blocking",
        });
      }, 10);

      const dummyFacing = itemEntity.getProperty("pb_gat:direction");
      let dx = 0;
      let dz = 0;

      switch (dummyFacing) {
        case "north":
          dz = 1;
          break;
        case "south":
          dz = -1;
          break;
        case "east":
          dx = -1;
          break;
        case "west":
          dx = 1;
          break;
      }

      const particleDirection = new MolangVariableMap();
      particleDirection.setFloat("variable.direction_x", dx);
      particleDirection.setFloat("variable.direction_z", dz);

      player.spawnParticle(
        "pb_gat:mb_gam_item_particle",
        itemEntity.location,
        particleDirection
      );
    });
  }

  // Step 2: Delay
  system.runTimeout(() => {
    // Step 3: Revalidate structure
    const stillValid = validateGroupStillIntact(group, dim);
    if (!stillValid) {
      player.sendMessage("§cStructure was broken mid-process.");
      group.running = false;
      GAMGroupManager.updateGroup(group);
      return;
    }

    // Step 4: Remove input items
    for (const itemEntity of group.itemEntities) {
      const entities = dim.getEntitiesAtBlockLocation(itemEntity);
      for (const e of entities) {
        if (e.id === itemEntity.id) {
          e.remove(); // Remove the entity
        }
      }
    }
    group.itemEntities = [];

    // Step 5a: Determine output block and spawn location
    let outputBlock = null;

    // Look through all blocks (including master) to find the top_right block
    for (const loc of [group.master, ...group.blocks]) {
      const block = getBlockFromLocation(dim, loc);
      if (!block) continue;

      const connState = block.permutation.getState("pb_gat:mb_connection");
      if (connState === "top_right") {
        outputBlock = block;
        break;
      }
    }

    // Fallback to master if top_right not found
    if (!outputBlock) {
      outputBlock = getBlockFromLocation(dim, group.master);
    }

    const spawnLocation = {
      x: outputBlock.location.x + 0.5,
      y: outputBlock.location.y,
      z: outputBlock.location.z + 0.5,
    };

    // Update block state to reflect it has an item (only on output)
    try {
      const perm = outputBlock.permutation.withState(
        "pb_gat:mb_has_item",
        true
      );
      outputBlock.setPermutation(perm);
    } catch (e) {
    }

    // Clear 'has_item' on all other blocks
    for (const loc of allBlockLocs) {
      const block = dim.getBlock(loc);
      if (!block) continue;

      const isOutput =
        block.location.x === outputBlock.location.x &&
        block.location.y === outputBlock.location.y &&
        block.location.z === outputBlock.location.z;

      if (!isOutput) {
        try {
          const perm = block.permutation.withState("pb_gat:mb_has_item", false);
          block.setPermutation(perm);
        } catch (e) {
        }
      }
    }

    // Step 5b: Play result particle immediately
    let dx = 0;
    let dz = 0;
    switch (group.direction) {
      case "north":
        dz = 1;
        break;
      case "south":
        dz = -1;
        break;
      case "east":
        dx = -1;
        break;
      case "west":
        dx = 1;
        break;
    }

    const particleDirection = new MolangVariableMap();
    particleDirection.setFloat("variable.direction_x", dx);
    particleDirection.setFloat("variable.direction_z", dz);

    player.spawnParticle(
      "pb_gat:mb_gam_item_particle",
      spawnLocation,
      particleDirection
    );
    // Step 5c: Delay spawning result entity by 1 tick to sync with visuals
    system.runTimeout(() => {
      const resultEntity = dim.spawnEntity(
        "pb_gat:mb_gam_entity",
        spawnLocation
      );
      if (resultEntity) {
        resultEntity.setProperty("pb_gat:position", "top_right");
        resultEntity.setProperty("pb_gat:direction", group.direction);
        mbEntityGiveItem(resultEntity, new ItemStack(recipe.result, 1));
        world.playSound("smithing_table.use", resultEntity.location);
        GAMGroupManager.addItemEntity(groupId, resultEntity, group.direction);
      }
    }, 10); // Delay by 10 tick

    // Step 6: Update group
    group.running = false;
    group.hasResult = true;
    GAMGroupManager.updateGroup(group);
    player.sendMessage("§aProcess complete!");
  }, 60); // Delay (60 ticks = 3 seconds)
}

function validateGroupStillIntact(group, dim) {
  const allLocs = [group.master, ...group.blocks];
  return allLocs.every((loc) => {
    const block = dim.getBlock(loc);
    return (
      block && block.permutation.getState("pb_gat:mb_connection") !== "single"
    );
  });
}
