/*
 * SoulFire
 * Copyright (C) 2026  AlexProgrammerDE
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package com.soulfiremc.server.script.nodes.data;

import com.soulfiremc.server.script.*;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.Identifier;
import net.minecraft.world.phys.Vec3;
import reactor.core.publisher.Mono;

import java.util.Map;

/// Data node that finds the nearest block of a specific type.
/// Input: blockType (string, e.g., "minecraft:diamond_ore")
/// Input: maxDistance (int, maximum search radius in blocks)
/// Outputs: found (boolean), position (Vec3), distance (double)
public final class FindBlockNode extends AbstractScriptNode {
  public static final NodeMetadata METADATA = NodeMetadata.builder()
    .type("data.find_block")
    .displayName("Find Block")
    .category(CategoryRegistry.DATA)
    .addInputs(
      PortDefinition.execIn(),
      PortDefinition.botIn(),
      PortDefinition.inputWithDefault("blockType", "Block Type", PortType.STRING, "\"minecraft:diamond_ore\"", "Block ID to search for"),
      PortDefinition.inputWithDefault("maxDistance", "Max Distance", PortType.NUMBER, "16", "Maximum search radius")
    )
    .addOutputs(
      PortDefinition.execOut(),
      PortDefinition.output("found", "Found", PortType.BOOLEAN, "Whether a block was found"),
      PortDefinition.output("position", "Position", PortType.VECTOR3, "Block position"),
      PortDefinition.output("distance", "Distance", PortType.NUMBER, "Distance to the block")
    )
    .description("Finds the nearest block of a specific type within range")
    .icon("cube")
    .color("#9C27B0")
    .addKeywords("find", "block", "search", "nearest", "ore", "locate")
    .build();

  @Override
  public Mono<Map<String, NodeValue>> executeReactive(NodeRuntime runtime, Map<String, NodeValue> inputs) {
    var bot = requireBot(inputs);
    var level = bot.minecraft().level;
    var player = bot.minecraft().player;
    var blockTypeInput = getStringInput(inputs, "blockType", "minecraft:diamond_ore");
    var maxDistance = getIntInput(inputs, "maxDistance", 16);

    if (level == null || player == null) {
      return completedMono(notFoundResult());
    }

    // Parse block type
    var blockTypeId = blockTypeInput.contains(":") ? blockTypeInput : "minecraft:" + blockTypeInput;
    var blockType = BuiltInRegistries.BLOCK.getValue(Identifier.parse(blockTypeId));

    if (blockType == null) {
      return completedMono(notFoundResult());
    }

    var playerBlockPos = player.blockPosition();
    BlockPos nearestPos = null;
    double nearestDistSq = Double.MAX_VALUE;

    // Search in a cube around the player
    for (int dx = -maxDistance; dx <= maxDistance; dx++) {
      for (int dy = -maxDistance; dy <= maxDistance; dy++) {
        for (int dz = -maxDistance; dz <= maxDistance; dz++) {
          var checkPos = playerBlockPos.offset(dx, dy, dz);
          var blockState = level.getBlockState(checkPos);

          if (blockState.getBlock() == blockType) {
            var distSq = checkPos.distSqr(playerBlockPos);
            if (distSq < nearestDistSq) {
              nearestDistSq = distSq;
              nearestPos = checkPos;
            }
          }
        }
      }
    }

    if (nearestPos == null) {
      return completedMono(notFoundResult());
    }

    return completedMono(results(
      "found", true,
      "position", new Vec3(nearestPos.getX(), nearestPos.getY(), nearestPos.getZ()),
      "distance", Math.sqrt(nearestDistSq)
    ));
  }

  private Map<String, NodeValue> notFoundResult() {
    return results(
      "found", false,
      "position", Vec3.ZERO,
      "distance", -1.0
    );
  }
}
