import { ExplosionOptions, EntityQueryOptions, world, MinecraftEffectTypes, Vector } from "mojang-minecraft";
import { Harmonious } from "../lib/harmonious/main.js";

let overworld = world.getDimension("overworld");
let nether = world.getDimension("nether");
let theEnd = world.getDimension("the end");
 
const naturalStone = ["minecraft:netherrack","minecraft:basalt","minecraft:blackstone","minecraft:sandstone","minecraft:red_sandstone"];
const naturalStoneTypes = ["stone","granite","diorite","andesite"];

let cmdsToRun = [];

function runCmd(cmd, dim) {
  if(dim == null) {
    dim = overworld;
  }
  try {
    return { error: false, ...dim.runCommand(cmd) };
  } catch {
    return { error: true };
  }
}

function tellraw(player, msg) {
  return runCmd("tellraw " + player + '{"rawtext":[{"text":"' + msg + '"}]}')
}

function getTags(player) {
  const data = runCmd(`tag "${player}" list`);
  if (data.error)
    return [];
  let tags = data.statusMessage.match(/(?<=: ).*$/);
  if (tags) {
    tags = tags[0].split('§r, §a');
    for(let i = 0; i < tags.length; i++)
      tags[i] = String(tags[i].replace(/§./g, '').match(new RegExp(`^${tags[i].replace(/§./g, '')}$`)))
    return tags;
  }
  return [];
}

function hasTag(player, tag) {
  const allTags = getTags(player);
  if (!allTags)
    return false;
  for (const playerTag of allTags)
    if (tag == playerTag)
      return true;
  return false;
}

function addTag(player, tag) {
  return runCmd(`tag "${player}" add "${tag}"`);
}

function removeTag(player, tag) {
  return runCmd(`tag "${player}" remove "${tag}"`);
}

function getScore(player, scoreboard, { minimum, maximum } = {}) {
  const data = runCmd(`scoreboard players test "${player}" ${scoreboard} ${minimum ? minimum : '*'} ${maximum ? maximum : '*'}`);
  if (data.error)
    return;
  return parseInt(data.statusMessage.match(/-?\d+/)[0]);
}

function findPlayerDimension(player) {
  return world.getDimension(["overworld","nether","the end"][getScore(player, "od:dim")]);
}

// Thank you @|| MajestikButter ||#3362 for being big brain!
function setPlayerVelocity(p, velocity, mute = false) {
  // Counter explosion y velocity
  velocity = Vector.add(velocity, new Vector(0, -1, 0));
  
  // Grab the player's health component
  const h = p.getComponent("health");
  // Save the player's health before the explosion for later use
  const hp = h.current;
  // Add instant health to stop damage effects from playing
  p.addEffect(MinecraftEffectTypes.instantHealth, 1, 255);
  
  // Need explosion options for Dimension.createExplosion
  const e = new ExplosionOptions();
  // We don't want our explosion causing damage, do we?
  e.breaksBlocks = false;
  
  // Setting velocity before explosion so that explosion sends custom velocity to client
  p.setVelocity(velocity);
  // Create explosion to send velocity to client
  p.dimension.createExplosion(p.location, 0.05, e);
  // Stop sounds that are produced by the explosion
  if (mute) p.runCommand("stopsound @s random.explode");
  
  // Remove instant health since we don't wanna continuously heal the player
  p.runCommand("effect @s instant_health 0 0 true");
  // Reset health using previously saved health
  h.setCurrent(hp);
}

Harmonious.customCommands.addCommand({
  name: "info", // Commands Name: string
  hidden: false, // Hide Command in 'prefix'help: bool
  aliases: [], // Command Aliases: string[]
  description: "See information about the Origins addon.", // Command Description: string
  usage: ["info"], // Command Usage: string[]
  examples: [], // Command Examples: string[]
  args: [], // Command Arguments: string[]
  run: (data, args) => {
    tellraw(data.sender.nameTag, "§dOrigins §aaddon created by §blammas123 §aon §cYouTube\n§bDiscord §aServer: §fdiscord.gg/Mypr3MqpcP\n§aVersion: §fv1.3.7");
  } // Code to Run: function
});

Harmonious.customCommands.addCommand({
  name: "my_origin", // Commands Name: string
  hidden: false, // Hide Command in 'prefix'help: bool
  aliases: [], // Command Aliases: string[]
  description: "See what origin you have and it's description and powers.", // Command Description: string
  usage: ["my_origin"], // Command Usage: string[]
  examples: [], // Command Examples: string[]
  args: [], // Command Arguments: string[]
  run: (data, args) => {
    addTag(data.sender.nameTag, "origins:display_viewing_ui");
    tellraw(data.sender.nameTag, "Close the chat and move to view your origin.");
    runCmd("execute " + data.sender.nameTag + " ~~~ tp @e[type=origins:ui,c=1] ~ -63~", findPlayerDimension(data.sender.nameTag));
  } // Code to Run: function
});

Harmonious.customCommands.addCommand({
  name: "origin_of", // Commands Name: string
  hidden: false, // Hide Command in 'prefix'help: bool
  aliases: [], // Command Aliases: string[]
  description: "See what origin someone has.", // Command Description: string
  usage: ["origin_of <player>"], // Command Usage: string[]
  examples: [], // Command Examples: string[]
  args: ["spaced_string"], // Command Arguments: string[]
  run: (data, args) => {
    if(args[0].substring(0,1) == "@" && args[0].length > 2) {
      tellraw(data.sender.nameTag, "§cCan't use selector brackets.");
      return;
    }
    if(args[0].substring(0,2) == "@e") {
      tellraw(data.sender.nameTag, "§cCan't use @e selector.");
      return;
    }
    if(args[0].substring(0,2) == "@s" || args[0].substring(0,2) == "@p") {
      args[0] = data.sender.nameTag;
    }
    if(args[0].substring(0,1) != "@") {
      args[0] = "\"" + args[0] + "\"";
    }
    addTag(data.sender.nameTag, "origins:view_origin_of");
    addTag(args[0], "origins:tell_origin_to");
  } // Code to Run: function
});

world.events.tick.subscribe(data => {
  for(let i = cmdsToRun.length-1; i >= 0; i--) {
    cmdsToRun[i][1]--;
    if(cmdsToRun[i][1] == 0) {
      if(cmdsToRun[i].length == 2) {
        runCmd(cmdsToRun[i][0]);
      } else {
        runCmd(cmdsToRun[i][0], cmdsToRun[i][2]);
      }
      cmdsToRun.splice(i, 1);
    }
  }
  runCmd("tag @e remove origins:power_bloodthirsty_entity");
  let players = Array.from(world.getPlayers());
  for(let i = 0; i < players.length; i++) {
    if(hasTag(players[i].nameTag, "origins:power_phasing_crouch_start")) {
      runCmd("execute " + players[i].nameTag + " ~~~ structure save origins:power_phasing_player" + getScore(players[i].nameTag, "origin:playerid") + " ~~1.5~ ~~1.5~", findPlayerDimension(players[i].nameTag));
      runCmd("execute " + players[i].nameTag + " ~~~ setblock ~~1.5~ leaves 3", findPlayerDimension(players[i].nameTag));
      removeTag(players[i].nameTag, "origins:power_phasing_crouch_start");
    } else if(hasTag(players[i].nameTag, "origins:power_phasing_crouch_end")) {
      runCmd("execute " + players[i].nameTag + " ~~~ structure load origins:power_phasing_player" + getScore(players[i].nameTag, "origin:playerid") + " ~~1.5~", findPlayerDimension(players[i].nameTag));
      removeTag(players[i].nameTag, "origins:power_phasing_crouch_end");
    } else if(hasTag(players[i].nameTag, "origins:power_bloodthirsty")) {
      let query = new EntityQueryOptions();
      query.location = players[i].location;
      query.maxDistance = 15;
      query.families = ["mob"];
      query.excludeFamilies = ["monster"];
      query.excludeTypes = ["ender_dragon"];
      let entities = Array.from(players[i].dimension.getEntities(query));
      entities.forEach(entity => {
        const h = entity.getComponent("health");
        if(h.current < h.value) entity.addTag("origins:power_bloodthirsty_entity");
      });
    }
    let tags = getTags(players[i].nameTag);
    for(let t = 0; t < tags.length; t++) {
      if(tags[t].startsWith("origins:set_velocity_forward_")) {
        let v = Number(tags[t].slice(29));
        setPlayerVelocity(players[i], new Vector(players[i].viewVector.x * v, players[i].viewVector.y * v, players[i].viewVector.z * v));
        removeTag(players[i].nameTag, tags[t]);
      } else if(tags[t].startsWith("origins:set_velocity_")) {
        let v = tags[t].slice(21).split("_");
        if(v[0]=="~")v[0]=players[i].velocity.x;
        if(v[1]=="~")v[1]=players[i].velocity.y;
        if(v[2]=="~")v[2]=players[i].velocity.z;
        setPlayerVelocity(players[i], new Vector(Number(v[0]), Number(v[1]), Number(v[2])));
        removeTag(players[i].nameTag, tags[t]);
      }
    }
  }
});

world.events.blockBreak.subscribe(data => {
  let player = data.player;
  let dimension = data.dimension;
  let permutation = data.brokenBlockPermutation;
  let block = permutation.type;
  let pos = data.block.location;
  if(hasTag(player.nameTag, "origins:power_weak_arms")) {
    if(!runCmd(`testfor @a[name="${player.nameTag}",m=c]`, dimension).error) {
      return;
    }
    if(player.getEffect(MinecraftEffectTypes.strength) != undefined) {
      return;
    }
    if(naturalStone.includes(block.id) || (block.id == "minecraft:stone" && naturalStoneTypes.includes(permutation.getProperty("stone_type").value))) { 
      let adjacentStones = 0;
      if(naturalStone.includes(dimension.getBlock(pos.offset(1, 0, 0)).id) || (dimension.getBlock(pos.offset(1, 0, 0)).id == "minecraft:stone" && naturalStoneTypes.includes(dimension.getBlock(pos.offset(1, 0, 0)).permutation.getProperty("stone_type").value))) {
        adjacentStones++;
      }
      if(naturalStone.includes(dimension.getBlock(pos.offset(-1, 0, 0)).id) || (dimension.getBlock(pos.offset(-1, 0, 0)).id == "minecraft:stone" && naturalStoneTypes.includes(dimension.getBlock(pos.offset(-1, 0, 0)).permutation.getProperty("stone_type").value))) {
        adjacentStones++;
      }
      if(naturalStone.includes(dimension.getBlock(pos.offset(0, 1, 0)).id) || (dimension.getBlock(pos.offset(0, 1, 0)).id == "minecraft:stone" && naturalStoneTypes.includes(dimension.getBlock(pos.offset(0, 1, 0)).permutation.getProperty("stone_type").value))) {
        adjacentStones++;
      }
      if(naturalStone.includes(dimension.getBlock(pos.offset(0, -1, 0)).id) || (dimension.getBlock(pos.offset(0, -1, 0)).id == "minecraft:stone" && naturalStoneTypes.includes(dimension.getBlock(pos.offset(0, -1, 0)).permutation.getProperty("stone_type").value))) {
        adjacentStones++;
      }
      if(naturalStone.includes(dimension.getBlock(pos.offset(0, 0, 1)).id) || (dimension.getBlock(pos.offset(0, 0, 1)).id == "minecraft:stone" && naturalStoneTypes.includes(dimension.getBlock(pos.offset(0, 0, 1)).permutation.getProperty("stone_type").value))) {
        adjacentStones++;
      }
      if(naturalStone.includes(dimension.getBlock(pos.offset(0, 0, -1)).id) || (dimension.getBlock(pos.offset(0, 0, -1)).id == "minecraft:stone" && naturalStoneTypes.includes(dimension.getBlock(pos.offset(0, 0, -1)).permutation.getProperty("stone_type").value))) {
        adjacentStones++;
      }
      if(adjacentStones > 2) {
         if(naturalStone.includes(block.id)) {
           runCmd(`setblock ${pos.x} ${pos.y} ${pos.z} ${block.id}`, dimension);
         } else {
          let stoneData = permutation.getProperty("stone_type").value;
          if(stoneData == "stone") {
            stoneData = 0;
          } else if(stoneData == "granite") {
            stoneData = 1;
          } else if(stoneData == "diorite") {
            stoneData = 3;
          } else if(stoneData == "andesite") {
            stoneData = 5;
          }
          runCmd(`setblock ${pos.x} ${pos.y} ${pos.z} stone ${stoneData}`, dimension);
        }
        cmdsToRun.push([`execute "${player.nameTag}" ${pos.x} ${pos.y} ${pos.z} kill @e[type=item,c=1,r=2]`, 1, dimension]);
      }
    }
  } else if(hasTag(player.nameTag, "origins:power_break_stone") && getScore(player.nameTag, "od:hold") === 0 && runCmd(`testfor @a[name="${player.nameTag}",m=c]`, dimension).error && (naturalStone.includes(block.id) || (block.id == "minecraft:stone" && naturalStoneTypes.includes(permutation.getProperty("stone_type").value)))) {
    if(naturalStone.includes(block.id)) {
      runCmd(`setblock ${pos.x} ${pos.y} ${pos.z} ${block.id}`, dimension);
    } else {
      let stoneData = permutation.getProperty("stone_type").value;
      if(stoneData == "stone") {
        stoneData = 0;
      } else if(stoneData == "granite") {
        stoneData = 1;
      } else if(stoneData == "diorite") {
        stoneData = 3;
      } else if(stoneData == "andesite") {
        stoneData = 5;
      }
      runCmd(`setblock ${pos.x} ${pos.y} ${pos.z} stone ${stoneData}`, dimension);
    }
    runCmd(`setblock ${pos.x} ${pos.y} ${pos.z} air 0 destroy`, dimension);
  }
});