import { world, system, Player, BlockPermutation, Dimension } from "@minecraft/server";
import { ActionFormData, MessageFormData, ModalFormData } from "@minecraft/server-ui";
import { ScriptEventListener } from "../utils/scriptevent.js";
import { Mathf, Easing } from "../utils/math";
import { detectLoadedChunk } from "../utils/chunk.js";
import { playerSafeTeleport } from "../utils/safe.js";
import { delay, delayCycle, intervalTimer } from "../utils/time.js";
import { Rawtext } from "../utils/mce.js";
import { Debugger } from "../utils/debug.js";
import { ITEM_LORES } from "./lore.js";
import "../utils/jigcs.js";
import { Jigcs } from "../utils/jigcs.js";

const debug = new Debugger('mythicus:warpstone');
debug.enabled = false;
debug.priority = 1;

const waystoneReachDist = 10;
const waystoneAmountMax = 30;
const waystoneAmountMin = 5;
const waystoneIconCount = 7;
const waystoneDefaultName = "waystone.mythicus:ui.name.default";
const waystoneChatPrefix = "waystone.mythicus:chat.prefix";
const waystoneUIPrefix = "waystone.mythicus:ui.prefix";

const dimension_ids = [
	'minecraft:overworld',
	'minecraft:nether',
	'minecraft:the_end',
]

const waystone_permutations = [

	// basic
	'mythicus_ws:waystone.andesite',
	'mythicus_ws:waystone.blackstone',
	'mythicus_ws:waystone.brickstone',
	'mythicus_ws:waystone.end',
	'mythicus_ws:waystone.sandstone',
	'mythicus_ws:waystone.stone',

	// custom
	'mythicus_ws:waystone.allay',
	'mythicus_ws:waystone.candles',
	'mythicus_ws:waystone.creeper',
	'mythicus_ws:waystone.easter_egg',
	'mythicus_ws:waystone.end_crystal',
	'mythicus_ws:waystone.horse',
	'mythicus_ws:waystone.trident',
	'mythicus_ws:waystone.villager',
	'mythicus_ws:waystone.warden',

	'mythicus_ws:waystone.cute.axolotl',
	'mythicus_ws:waystone.cute.cat',
	'mythicus_ws:waystone.cute.creeper',
	'mythicus_ws:waystone.cute.dragon',
	'mythicus_ws:waystone.cute.enderman',
	'mythicus_ws:waystone.cute.ghast',
	'mythicus_ws:waystone.cute.skeleton',
	'mythicus_ws:waystone.cute.warden',
	'mythicus_ws:waystone.cute.wolf',
	'mythicus_ws:waystone.cute.zombie',

].flatMap(block => {
	const north = BlockPermutation.resolve(block, { "minecraft:cardinal_direction": "north" });
	const south = BlockPermutation.resolve(block, { "minecraft:cardinal_direction": "south" });
	const east = BlockPermutation.resolve(block, { "minecraft:cardinal_direction": "east" });
	const west = BlockPermutation.resolve(block, { "minecraft:cardinal_direction": "west" });

	return [north, south, east, west];
})

const waystonePrices = [
	1, 3, 5, 7, 9, 11, 15, 15, 17, 20,
	22, 24, 26, 28, 30, 30, 30, 30, 30, 30,
	30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
]

function vectDist(v1, v2) {
	return Math.sqrt((v2.x - v1.x) ** 2 + (v2.y - v1.y) ** 2 + (v2.z - v1.z) ** 2);
}
function encodeWaypoint(x, y, z, dim, delimiter = '_') {
	return `${dimensions_display[dim] ?? '0'}${delimiter}${x.toString(36)}${delimiter}${y.toString(36)}${delimiter}${z.toString(36)}`;
}

function waystoneUnlockPrice(waystoneAmountUnl) {
	const index = waystoneAmountUnl - waystoneAmountMin;
	return index < 0 ? 1 : waystonePrices[index];
}

function randomNearbyGround(dim, loc, rw, rh=rw) {
	const locations = [];
	const [xL, yL, zL] = [(loc.x >> 0), (loc.y >> 0), (loc.z >> 0)];
	const { min: yMin, max: yMax } = dim.heightRange;

	for (let x = xL - rw; x <= xL + rw; x++) {
		for (let z = zL - rw; z <= zL + rw; z++) {
			for (let y = yL - rh; y <= yL + rh; y++) {
				if (y < yMin || yMax < y) continue;

				const block = dim.getBlock({ x: x, y: y, z: z });
				if (block?.isAir !== false) continue;
				if (y < (yMax - 1) && block.above(2)?.isAir === false) {
					y++;
					continue;
				} else if (y < yMax && block.above(1)?.isAir === false) {
					continue;
				}

				locations.push({ x: x, y: y + 1, z: z });
			}
		}
	}

	if (locations.length === 0) return undefined;

	return locations[Mathf.randomInt(locations.length - 1)];
}



function calcTpCost(dim, dist) {
	switch (dim) {
		case "minecraft:overworld":
			return Math.ceil(Easing.inCirc(Mathf.clamp(dist, 0, 16000) / 16000) * 64);
		case "minecraft:nether":
			return Math.ceil(Easing.inCirc(Mathf.clamp(dist, 0, 16000) / 16000) * 64);
		case "minecraft:the_end":
			return Math.ceil(Easing.inCirc(Mathf.clamp(dist, 0, 16000) / 16000) * 64);
	}
}

let waystoneParticles = new Set();
export async function waystoneSetParticles() {

	// place particles
	for (let particle of waystoneParticles) {
		let { x, y, z, dim } = particle;

		await world.getDimension(dim).runCommandAsync(`particle mythicus_ws:waystone.basic ${x} ${y} ${z}`);

	}

};

async function waystoneParseParticles() {

	waystoneParticles = new Set();

	const waystones = world.getDynamicPropertyIds().filter(property => property.startsWith('mythicus:waystoneOwner_'));

	let iterations = 0;
	for (let i = 0; i < waystones.length; i++) {
		iterations = await delayCycle(2, iterations, 5);

		const waystone = waystones[i];
		const waystoneData = waystone.split('_');

		const dim = dimension_ids[waystoneData[1]];
		const x = parseInt(waystoneData[2], 36);
		const y = parseInt(waystoneData[3], 36);
		const z = parseInt(waystoneData[4], 36);

		const loc = { x: x, y: y, z: z };

		if (!world.getDimension(dim).getBlock(loc)) continue;

		waystoneParticles.add({ ...loc, dim: dim });
	}

};

//system.runInterval(waystoneSetParticles, 100);
//system.runInterval(waystoneParseParticles, 200);



world.afterEvents.entityHitEntity.subscribe(event => {
	const player = event.damagingEntity;
	const anchor = event.hitEntity;

	if (player.typeId !== "minecraft:player" || anchor.typeId !== "mythicus_ws:waystone.anchor") return;

	const hitBlock = player.dimension.getBlock(anchor.location);
	if (hitBlock?.hasTag("mythicus_ws:waystone") !== true) return;

	const hitLoc = hitBlock.location;
	const hitDim = player.dimension.id;

	player.setDynamicProperty("mythicus:waystoneActiveLocation", hitLoc);
	player.setDynamicProperty("mythicus:waystoneActiveDimension", hitDim);

	if (world.getDynamicProperty(`mythicus:waystoneOwner_${encodeWaypoint(hitLoc.x, hitLoc.y, hitLoc.z, hitDim)}`) == undefined) {
		hitBlock.setPermutation(BlockPermutation.resolve("minecraft:air"));
		return;
	}

	waystone_ui_tp(player, hitLoc, hitDim);

})

world.afterEvents.itemUse.subscribe(event => {

	const player = event.source;

	if (event.itemStack.typeId === "mythicus_ws:waystone.interact") {
		waystone_ui_tp(player, { x: 0, y: -1000, z: 0 }, player.dimension);
		return;
	};

});



const icons_list = [
	{ rawtext: [{ translate: "waystone.mythicus:icon.names.house" }] },
	{ rawtext: [{ translate: "waystone.mythicus:icon.names.castle" }] },
	{ rawtext: [{ translate: "waystone.mythicus:icon.names.flag" }] },
	{ rawtext: [{ translate: "waystone.mythicus:icon.names.x" }] },
	{ rawtext: [{ translate: "waystone.mythicus:icon.names.skull" }] },
	{ rawtext: [{ translate: "waystone.mythicus:icon.names.chest" }] },
	{ rawtext: [{ translate: "waystone.mythicus:icon.names.cave" }] },
	{ rawtext: [{ translate: "waystone.mythicus:icon.names.portal" }] },
	{ rawtext: [{ translate: "waystone.mythicus:icon.names.village" }] },
	{ rawtext: [{ translate: "waystone.mythicus:icon.names.town" }] },
]

const dimensions_display = {
	'minecraft:overworld': '0',
	'minecraft:nether': '1',
	'minecraft:the_end': '2',
}

const dimensions_color = {
	'minecraft:overworld': "§2",
	'minecraft:nether': "§c",
	'minecraft:the_end': "§6",
}

async function waystone_ui_tp(player, loc, dim, bodyText="") {
	const isItemMenu = loc.y === -1000;
	const isWaystoneOwner = (world.getDynamicProperty(`mythicus:waystoneOwner_${encodeWaypoint(loc.x, loc.y, loc.z, dim)}`) || "0::0").split("::")[0] === player.id;
	const isWaystoneAdded = !isWaystoneOwner && hasWaystone(player, dim, loc.x, loc.y, loc.z);

	if (bodyText === "" && !isWaystoneOwner && !isWaystoneAdded && !isItemMenu) {
		bodyText = { rawtext: [{ translate: "waystone.mythicus:ui.state.unadded" }] };
	}

	const waystone_form = new ActionFormData();

	waystone_form.title({ rawtext: [{ translate: "waystone.mythicus:ui.form.default" }] });
	if (bodyText) waystone_form.body(bodyText);
	if (!isItemMenu) waystone_form.button({ rawtext: [{ translate: "waystone.mythicus:ui.button.settings" }] }, "textures/mythicus/waystone/ui/icon/settings/settings");

	const waystoneAmountUnl = player.getDynamicProperty("mythicus:waystoneAmountUnl") ?? waystoneAmountMin;
	const waystoneAmountCur = player.getDynamicProperty("mythicus:waystoneAmountCur") ?? 0;
	let waystoneAmountCurCheck = 0;
	for (let i = 0; i < waystoneAmountUnl; i++) {
		const waystoneLoc = player.getDynamicProperty(`mythicus:waystoneLoc_${i}`);
		const waystoneDim = player.getDynamicProperty(`mythicus:waystoneDim_${i}`);

		if (waystoneLoc == null || waystoneDim == null) {
			waystone_form.button({ rawtext: [{ translate: "waystone.mythicus:ui.button.unset", with: [`${i + 1}`] }] }, "textures/mythicus/waystone/ui/icon/settings/map_default");
			continue;
		}

		waystoneAmountCurCheck++;
		const tpDist = vectDist(waystoneLoc, player.location);
		const tpCost = calcTpCost(waystoneDim, tpDist);
		const waystoneSymbol = player.getDynamicProperty(`mythicus:waystoneSym_${i}`) ?? 0;
		const waystoneName = player.getDynamicProperty(`mythicus:waystoneName_${i}`) ?? `${waystoneDefaultName}`;
		const waystoneDimName = dimensions_display[waystoneDim] ?? '0';

		//waystone_form.button(`${waystoneName} (${waystoneLoc.x}, ${waystoneLoc.y}, ${waystoneLoc.z}) [${waystoneDimName}] §2xp: ${tpCost}`, `textures/mythicus/waystone/ui/icon/${waystoneDim.split(":")[1]}/${waystoneSymbol}`);
		let waystoneCostString = "";
		//if (player.runCommand(`execute if entity @s[hasitem={item=mythicus_ws:waystone.energy.creative,quantity=1..}]`).successCount === 0)
		//	waystoneCostString = `§8[${dimensions_color[waystoneDim] ?? "§8"}${tpCost}§8]`;
		if (waystoneName === waystoneDefaultName) {
			waystone_form.button({ rawtext: [{ translate: "waystone.mythicus:ui.button.default", with: { rawtext: [{ text: waystoneCostString }, { translate: `${waystoneDefaultName}` }, { text: `${i + 1}` }] } }] }, `textures/mythicus/waystone/ui/icon/${waystoneDim.split(":")[1]}/${waystoneSymbol}`);
		} else {
			waystone_form.button({ rawtext: [{ translate: "waystone.mythicus:ui.button.custom", with: [waystoneCostString, `${waystoneName}`] }] }, `textures/mythicus/waystone/ui/icon/${waystoneDim.split(":")[1]}/${waystoneSymbol}`);
		}
	};

	if (waystoneAmountCur !== waystoneAmountCurCheck) {
		player.setDynamicProperty("mythicus:waystoneAmountCur", waystoneAmountCurCheck);
	}

	waystone_form.show(player).then(async (response) => {

		if (response.selection == null) {
			return;
		}

		const responseId = isItemMenu ? response.selection + 1 : response.selection;
		if (responseId === 0) {
			waystone_ui_settings(player, loc, dim);
			return;
		}

		waystone_try_teleport(player, loc, dim, responseId - 1);

	});
}

async function waystone_ui_settings(player, loc, dim, bodyText="") {
	const isWaystoneOwner = (world.getDynamicProperty(`mythicus:waystoneOwner_${encodeWaypoint(loc.x, loc.y, loc.z, dim)}`) || "0::0").split("::")[0] === player.id;
	const canRemoveWaystone = !isWaystoneOwner && hasWaystone(player, dim, loc.x, loc.y, loc.z);

	const uiBodyText = bodyText ? bodyText.rawtext[0].translate : "";

	const waystone_form = new ActionFormData();

	waystone_form.title({ rawtext: [{ translate: "waystone.mythicus:ui.form.settings" }] });
	if (bodyText) waystone_form.body(bodyText);

	if (canRemoveWaystone)
		waystone_form.button({ rawtext: [{ translate: "waystone.mythicus:ui.button.remove" }] }, "textures/mythicus/waystone/ui/icon/settings/remove_waystone");

	waystone_form.button({ rawtext: [{ translate: "waystone.mythicus:ui.button.icons" }] }, "textures/mythicus/waystone/ui/icon/settings/change_icons");
	waystone_form.button({ rawtext: [{ translate: "waystone.mythicus:ui.button.names" }] }, "textures/mythicus/waystone/ui/icon/settings/change_names");

	const waystoneAmountUnl = player.getDynamicProperty("mythicus:waystoneAmountUnl") ?? waystoneAmountMin;
	if (waystoneAmountUnl < waystoneAmountMax) {
		waystone_form.button({ rawtext: [{ translate: "waystone.mythicus:ui.button.unlock", with: [`${waystoneUnlockPrice(waystoneAmountUnl)}`] }] }, "textures/mythicus/waystone/ui/icon/settings/buy");
	}

	if (canRemoveWaystone) {
		waystone_form.button({ rawtext: [{ translate: "waystone.mythicus:ui.button.guide" }] }, "textures/mythicus/waystone/ui/icon/settings/guide_book");
	} else {
		if (uiBodyText !== "waystone.mythicus:ui.break.ask")
			waystone_form.button({ rawtext: [{ translate: "waystone.mythicus:ui.button.break" }] }, "textures/mythicus/waystone/ui/icon/settings/trash");
		else
			waystone_form.button({ rawtext: [{ translate: "waystone.mythicus:ui.button.breakConfirm" }] }, "textures/mythicus/waystone/ui/icon/settings/trash");
	}

	waystone_form.button({ rawtext: [{ translate: "waystone.mythicus:ui.button.contact" }] }, "textures/mythicus/waystone/ui/icon/settings/contact");

	waystone_form.show(player).then(async (response) => {
		let selection = response.selection;
		if (canRemoveWaystone) selection -= 1;
		switch (selection) {
			case -1:
				player.sendMessage(Rawtext.create("waystone.mythicus:ui.remove", [waystoneChatPrefix]));
				removeWaystone(player, dim, loc.x, loc.y, loc.z, false);
				waystone_ui_settings(player, loc, dim, Rawtext.create("waystone.mythicus:ui.remove", [waystoneUIPrefix]));
				return;

			case 0:
				waystone_ui_icon(player, loc, dim);
				return;

			case 1:
				waystone_ui_name(player, loc, dim);
				return;

			case 2:
				if (waystoneAmountUnl < waystoneAmountMax) {
					bodyText = waystone_try_purchase(player);
					waystone_ui_settings(player, loc, dim, bodyText);
				} else {
					if (canRemoveWaystone) {
						player.sendMessage(Rawtext.create("waystone.mythicus:ui.guide", [waystoneChatPrefix]));
						player.runCommand("give @s mythicus_ws:waystone.guidebook 1");
						return;
					}

					if (uiBodyText !== "waystone.mythicus:ui.break.ask") {
						waystone_ui_settings(player, loc, dim, Rawtext.create("waystone.mythicus:ui.break.ask", [waystoneUIPrefix]));
						return;
					} else {
						breakWaystone(dim, loc);
						//waystone_ui_settings(player, loc, dim, Rawtext.create("waystone.mythicus:ui.break.confirm", [waystoneUIPrefix]));
						return;
					}
				}
				return;

			case 3:
				if (waystoneAmountUnl < waystoneAmountMax) {
					if (canRemoveWaystone) {
						player.sendMessage(Rawtext.create("waystone.mythicus:ui.guide", [waystoneChatPrefix]));
						player.runCommand("give @s mythicus_ws:waystone.guidebook 1");
						return;
					}

					if (uiBodyText !== "waystone.mythicus:ui.break.ask") {
						waystone_ui_settings(player, loc, dim, Rawtext.create("waystone.mythicus:ui.break.ask", [waystoneUIPrefix]));
						return;
					} else {
						breakWaystone(dim, loc);
						//waystone_ui_settings(player, loc, dim, Rawtext.create("waystone.mythicus:ui.break.confirm", [waystoneUIPrefix]));
						return;
					}
				} else {
					player.sendMessage(Rawtext.create("waystone.mythicus:ui.contact", ["mythicus:chat.prefix"]));
					player.sendMessage("§9https://www.mythicus.games");
				}
				return;

			case 4:
				player.sendMessage(Rawtext.create("waystone.mythicus:ui.contact", ["mythicus:chat.prefix"]));
				player.sendMessage("§9https://www.mythicus.games");
				return;

			default:
				waystone_ui_tp(player, loc, dim);
				return;
		}
	});

}

async function waystone_ui_icon(player, loc, dim, bodyText="") {

	const waystone_form = new ModalFormData();

	waystone_form.title(Rawtext.create("waystone.mythicus:ui.form.icons"));
	if (bodyText) waystone_form.body(bodyText);

	const waystoneAmountUnl = player.getDynamicProperty("mythicus:waystoneAmountUnl") ?? waystoneAmountMin;
	for (let i = 0; i < waystoneAmountUnl; i++) {
		const waystoneSym = player.getDynamicProperty(`mythicus:waystoneSym_${i}`) ?? 0;
		const waystoneName = player.getDynamicProperty(`mythicus:waystoneName_${i}`) ?? `${waystoneDefaultName}`;

		if (waystoneName === waystoneDefaultName) {
			waystone_form.dropdown({ rawtext: [{ translate: "waystone.mythicus:ui.waystone.name", with: { rawtext: [{ translate: `${waystoneDefaultName}` }, { text: `${i + 1}` }] } }] }, icons_list, waystoneSym);
		} else {
			waystone_form.dropdown(`${waystoneName}`, icons_list, waystoneSym);
		}
	};

	waystone_form.show(player).then((response) => {

		if (response.canceled == true) {
			waystone_ui_settings(player, loc, dim, Rawtext.create("waystone.mythicus:ui.icons.unchanged", [waystoneUIPrefix]));
			return;
		}

		const formValues = response.formValues;
		for (let i = 0; i < formValues.length; i++) {
			player.setDynamicProperty(`mythicus:waystoneSym_${i}`, formValues[i]);
		};

		waystone_ui_tp(player, loc, dim, Rawtext.create("waystone.mythicus:ui.icons.changed", [waystoneUIPrefix]));

	});

}

async function waystone_ui_name(player, loc, dim, bodyText="") {
	const waystone_form = new ModalFormData();

	waystone_form.title(Rawtext.create("waystone.mythicus:ui.form.names"));
	if (bodyText) waystone_form.body(bodyText);

	const waystoneAmountUnl = player.getDynamicProperty("mythicus:waystoneAmountUnl") ?? waystoneAmountMin;
	for (let i = 0; i < waystoneAmountUnl; i++) {
		const waystoneName = player.getDynamicProperty(`mythicus:waystoneName_${i}`) ?? `${waystoneDefaultName}`;
		if (waystoneName === waystoneDefaultName) {
			waystone_form.textField({ rawtext: [{ translate: "waystone.mythicus:ui.waystone.name", with: { rawtext: [{ translate: `${waystoneDefaultName}` }, { text: `${i + 1}` }] } }] }, { rawtext: [{ translate: "waystone.mythicus:ui.textField.placeholder" }] });
		} else {
			waystone_form.textField(waystoneName, { rawtext: [{ translate: "waystone.mythicus:ui.textField.placeholder" }] }, waystoneName);
		}
	};

	waystone_form.show(player).then((response) => {

		if (response.canceled == true) {
			waystone_ui_settings(player, loc, dim, Rawtext.create("waystone.mythicus:ui.names.unchanged", [waystoneUIPrefix]));
			return;
		}

		const formValues = response.formValues;
		for (let i = 0; i < formValues.length; i++) {
			player.setDynamicProperty(`mythicus:waystoneName_${i}`, formValues[i] === "" ? waystoneDefaultName : formValues[i]);
		};

		waystone_ui_tp(player, loc, dim, Rawtext.create("waystone.mythicus:ui.names.changed", [waystoneUIPrefix]));

	});
}

function waystone_try_purchase(player) {
	const waystoneAmountUnl = player.getDynamicProperty("mythicus:waystoneAmountUnl") ?? waystoneAmountMin;
	if (waystoneAmountUnl >= waystoneAmountMax) return;

	const waystonePrice = waystoneUnlockPrice(waystoneAmountUnl);
	if (player.level < waystonePrice) {
		player.sendMessage(Rawtext.create("waystone.mythicus:ui.unlock.failure", [waystoneChatPrefix]));
		player.playSound("mythicus.waystone.ui.buy.fail", { volume: 1.0, pitch: 1.0 });
		return Rawtext.create("waystone.mythicus:ui.unlock.failure", [waystoneUIPrefix]);
	} else {
		player.addLevels(-waystonePrice);
		player.setDynamicProperty("mythicus:waystoneAmountUnl", waystoneAmountUnl + 1);
		player.sendMessage(Rawtext.create("waystone.mythicus:ui.unlock.success", [waystoneChatPrefix, `${waystoneAmountUnl + 1}`]));
		player.playSound("mythicus.waystone.ui.buy.success", { volume: 1.0, pitch: 1.0 });
		return Rawtext.create("waystone.mythicus:ui.unlock.success", [waystoneUIPrefix, `${waystoneAmountUnl + 1}`]);
	}
}

async function waystone_try_teleport(player, loc, dim, selection) {
	const isItemMenu = loc.y === -1000;
	const waystoneLoc = player.getDynamicProperty(`mythicus:waystoneLoc_${selection}`);
	const waystoneDim = player.getDynamicProperty(`mythicus:waystoneDim_${selection}`);

	let bodyText = "";
	let bodyChatText = "";
	if (waystoneLoc == null || waystoneDim == null) {
		if (!isItemMenu && setWaystone(player, selection, dim, loc.x, loc.y, loc.z, false)) {
			bodyText = Rawtext.create("waystone.mythicus:ui.set.success", [waystoneUIPrefix]);
			bodyChatText = Rawtext.create("waystone.mythicus:ui.set.success", [waystoneChatPrefix]);
		} else if (!isItemMenu) {
			bodyText = Rawtext.create("waystone.mythicus:ui.set.failure", [waystoneUIPrefix]);
			bodyChatText = Rawtext.create("waystone.mythicus:ui.set.failure", [waystoneChatPrefix]);
		} else {
			bodyText = Rawtext.create("waystone.mythicus:ui.tp.failure", [waystoneUIPrefix]);
			bodyChatText = Rawtext.create("waystone.mythicus:ui.tp.failure", [waystoneChatPrefix]);
		}

		if (bodyChatText) player.sendMessage(bodyChatText);
		waystone_ui_tp(player, loc, dim, bodyText);
		return;
	}

	let waystoneDimObj;
	try {
		waystoneDimObj = world.getDimension(waystoneDim);
	} catch (e) {
		debug.error(`Invalid warpstone dimension: ${e.message}`);
		return;
	}

	if (waystoneDim === dim && waystoneLoc.x === loc.x && waystoneLoc.y === loc.y && waystoneLoc.z === loc.z) {
		player.sendMessage(Rawtext.create("waystone.mythicus:state.here", [waystoneChatPrefix]));
		return;
	}

	const teleportDist = vectDist(waystoneLoc, player.location);
	const teleportCost = calcTpCost(waystoneDim, teleportDist);

	debug.log(player, `dist: ${teleportDist}, cost: ${teleportCost}, level: ${player.level}`);

	// check price
	let waystoneCurrency = "creative";
	//let waystoneCurrency = "default";
	//const waystoneDimString = waystoneDim.split(":")[1];
	//if (player.runCommand(`execute if entity @s[hasitem={item=mythicus_ws:waystone.energy.creative,quantity=1..}]`).successCount === 1) waystoneCurrency = "creative";
	//if (waystoneCurrency !== "creative" && player.runCommand(`execute if entity @s[hasitem={item=mythicus_ws:waystone.energy.${waystoneDimString},quantity=${teleportCost}..}]`).successCount === 0) {

	//	// check universal price
	//	if (player.runCommand(`execute if entity @s[hasitem={item=mythicus_ws:waystone.energy.universal,quantity=${Math.ceil(teleportCost / 2)}..}]`).successCount === 0) {
	//		player.sendMessage(Rawtext.create("waystone.mythicus:state.costly", [waystoneChatPrefix, `${dimensions_color[waystoneDim] ?? "§8"}${teleportCost}§r`, `dimension.dimensionName${dimensions_display[waystoneDim] ?? 0}`]));
	//		player.playSound("random.anvil_land", { volume: 0.5, pitch: 0.5 });
	//		return;
	//	}

	//	waystoneCurrency = "universal";

	//}

	// unlisted
	if (world.getDynamicProperty(`mythicus:waystoneOwner_${encodeWaypoint(waystoneLoc.x, waystoneLoc.y, waystoneLoc.z, waystoneDim)}`) == undefined) {
		player.sendMessage(Rawtext.create("waystone.mythicus:state.missing", [waystoneChatPrefix]));
		removeWaystoneAll(waystoneDim, waystoneLoc.x, waystoneLoc.y, waystoneLoc.z);
		return;
	}

	// loaded but missing
	if (waystoneDimObj.getBlock(waystoneLoc) && !waystone_permutations.includes(waystoneDimObj.getBlock(waystoneLoc).permutation)) {
		player.sendMessage(Rawtext.create("waystone.mythicus:state.missing", [waystoneChatPrefix]));
		removeWaystoneAll(waystoneDim, waystoneLoc.x, waystoneLoc.y, waystoneLoc.z);
		return;
	}

	// --- Initial teleport goes through here ---

	// take waystone currency
	//if (waystoneCurrency === "default") {
	//	player.runCommand(`clear @s mythicus_ws:waystone.energy.${waystoneDimString} 0 ${teleportCost}`);
	//} else {
	//	player.runCommand(`clear @s mythicus_ws:waystone.energy.universal 0 ${Math.ceil(teleportCost / 2)}`);
	//}

	const playerInitLoc = player.location;
	const playerInitDim = player.dimension;

	await playerSafeTeleport(player, { x: waystoneLoc.x, y: waystoneLoc.y + 1, z: waystoneLoc.z }, waystoneDimObj);

	if (!player.isValid()) return;   // fail-safe if player disconnects

	const isChunkLoaded = await detectLoadedChunk(waystoneLoc, waystoneDimObj, 5, waystoneDim == playerInitDim.id ? 100 : 200);

	if (!player.isValid()) return;   // fail-safe if player disconnects

	if (isChunkLoaded.status != 'resolved') {
		debug.error(`Error loading chunk: ${isChunkLoaded.data}`);
		//player.teleport(playerInitLoc, {dimension: playerInitDim});   // slow computers should always teleport?
		return;
	}

	// the block should always be loaded at this point, but we double-check here as a fail-safe
	if (waystoneDimObj.getBlock(waystoneLoc) && !waystone_permutations.includes(waystoneDimObj.getBlock(waystoneLoc)?.permutation)) {
		player.sendMessage(Rawtext.create("waystone.mythicus:state.missing", [waystoneChatPrefix]));
		removeWaystoneAll(waystoneDim, waystoneLoc.x, waystoneLoc.y, waystoneLoc.z);

		// delay if switching dimensions
		if (playerInitDim.id != waystoneDim) {
			await delay(40);
			if (!player.isValid()) return;   // fail-safe if player disconnects
		}

		// return currency to player
		//if (waystoneCurrency === "default") {
		//	player.runCommand(`give @s mythicus_ws:waystone.energy.${waystoneDimString} ${teleportCost}`);
		//} else {
		//	player.runCommand(`give @s mythicus_ws:waystone.energy.universal ${Math.ceil(teleportCost / 2)}`);
		//}

		await playerSafeTeleport(player, playerInitLoc, playerInitDim);

		return;
	}


	debug.log(player, `Success: teleported to (${waystoneLoc.x}, ${waystoneLoc.y}, ${waystoneLoc.z}) [${waystoneDim}]. ${isChunkLoaded.data}`);

	// resistance effect
	const playerEffect = player.getEffect("resistance");
	let playerEffectObj = undefined;
	if (playerEffect) playerEffectObj = { typeId: playerEffect.typeId, duration: playerEffect.duration, amplifier: playerEffect.amplifier };
	system.runTimeout(() => {
		if (player.isValid() === true && playerEffectObj !== undefined)
			player.addEffect(playerEffectObj.typeId, playerEffectObj.duration, { amplifier: playerEffectObj.amplifier });
	}, 105);
	player.runCommand("/effect @s resistance 5 10 true");

	/*let entityParticle;*/
	player.playSound("portal.trigger");
	const validLoc = randomNearbyGround(waystoneDimObj, waystoneLoc, 2, 2);
	if (validLoc !== undefined) {
		await playerSafeTeleport(player, validLoc, waystoneDimObj);

		//system.runTimeout(async () => {
		//	entityParticle = waystoneDimObj.spawnEntity("mythicus_ws:waystone.teleport", validLoc).setRotation(player.getRotation());
		//}, 5);

		//player.runCommand("/effect @s invisibility 2 10 true");
		//player.runCommand("/execute anchored eyes rotated ~ 0 run camera @s set minecraft:free ease 0.05 linear pos ^ ^1 ^-3 facing @s");
		//system.runTimeout(async () => {
		//	if (!player.isValid()) return;
		//	player.runCommand("/execute anchored eyes rotated ~ 0 run camera @s set minecraft:free ease 0.3 linear pos ^ ^ ^ facing ^ ^ ^1");
		//	await delay(6);

		//	if (!player.isValid()) return;
		//	player.camera.clear();
		//}, 40);
	}
}



// Sets a new waystone for a player at given index (returns true/false)
function setWaystone(player, index, dimension, x, y, z, allowDuplicates=true) {
	const waystoneAmountUnl = player.getDynamicProperty("mythicus:waystoneAmountUnl") ?? waystoneAmountMin;
	const waystoneAmountCur = player.getDynamicProperty("mythicus:waystoneAmountCur") ?? 0;

	if (waystoneAmountCur >= waystoneAmountMax) return false;
	if (index < 0 || waystoneAmountMax <= index) return false;

	const waystoneLoc = player.getDynamicProperty(`mythicus:waystoneLoc_${index}`);
	const waystoneDim = player.getDynamicProperty(`mythicus:waystoneDim_${index}`);

	if (waystoneLoc != null && waystoneDim != null) return false;

	if (allowDuplicates === false) {
		for (let i = 0; i < waystoneAmountUnl; i++) {
			const waystoneLoc = player.getDynamicProperty(`mythicus:waystoneLoc_${i}`);
			const waystoneDim = player.getDynamicProperty(`mythicus:waystoneDim_${i}`);

			if (waystoneLoc == null || waystoneDim == null) continue;

			if (waystoneDim === dimension && waystoneLoc.x === x && waystoneLoc.y === y && waystoneLoc.z === z)
				return false;
		}
	}

	player.setDynamicProperty("mythicus:waystoneAmountCur", waystoneAmountCur + 1);
	player.setDynamicProperty(`mythicus:waystoneLoc_${index}`, { x: x, y: y, z: z });
	player.setDynamicProperty(`mythicus:waystoneDim_${index}`, dimension);
	return true;
}

// Automatically adds a new waystone for a player (returns true/false)
function addWaystone(player, dimension, x, y, z, isCreator=true, blockId=0) {
	const waystoneAmountUnl = player.getDynamicProperty("mythicus:waystoneAmountUnl") ?? waystoneAmountMin;
	const waystoneAmountCur = player.getDynamicProperty("mythicus:waystoneAmountCur") ?? 0;

	if (waystoneAmountCur >= waystoneAmountMax) return false;

	for (let i = 0; i < waystoneAmountUnl; i++) {
		const waystoneLoc = player.getDynamicProperty(`mythicus:waystoneLoc_${i}`);
		const waystoneDim = player.getDynamicProperty(`mythicus:waystoneDim_${i}`);

		if (waystoneLoc != null && waystoneDim != null) continue;

		//player.sendMessage(`Waystone ${i + 1} set at (${x}, ${y}, ${z}) [${dimension}].`);
		player.sendMessage(Rawtext.create("waystone.mythicus:state.set", [waystoneChatPrefix, waystoneDefaultName, `${i + 1}`, `dimension.dimensionName${dimensions_display[dimension] ?? 0}`]));
		if (isCreator) world.setDynamicProperty(`mythicus:waystoneOwner_${encodeWaypoint(x, y, z, dimension)}`, player.id + "::" + blockId.toString());
		player.setDynamicProperty("mythicus:waystoneAmountCur", waystoneAmountCur + 1);
		player.setDynamicProperty(`mythicus:waystoneLoc_${i}`, { x: x, y: y, z: z });
		player.setDynamicProperty(`mythicus:waystoneDim_${i}`, dimension);

		Jigcs.add("created", 1);
		Jigcs.add("active", 1);
		if (dimension === 'minecraft:overworld') Jigcs.add("overworld", 1);
		else if (dimension === 'minecraft:nether') Jigcs.add("nether", 1);
		else Jigcs.add("the_end", 1);

		return true;
	}

	return false;
}

// Checks if a player has a waystone (returns true/false)
function hasWaystone(player, dimension, x, y, z) {
	const waystoneAmountUnl = player.getDynamicProperty("mythicus:waystoneAmountUnl") ?? waystoneAmountMin;
	for (let i = 0; i < waystoneAmountUnl; i++) {
		const waystoneLoc = player.getDynamicProperty(`mythicus:waystoneLoc_${i}`);
		const waystoneDim = player.getDynamicProperty(`mythicus:waystoneDim_${i}`);

		if (waystoneDim != dimension) {
			continue;
		} else if (waystoneLoc == null || !(waystoneLoc.x == x && waystoneLoc.y == y && waystoneLoc.z == z)) {
			continue;
		}

		return true;
	}

	return false;
}

// Removes a waystone for a player
function removeWaystone(player, dimension, x, y, z, includeMsg=false) {
	const waystoneAmountUnl = player.getDynamicProperty("mythicus:waystoneAmountUnl") ?? waystoneAmountMin;
	for (let i = 0; i < waystoneAmountUnl; i++) {
		const waystoneLoc = player.getDynamicProperty(`mythicus:waystoneLoc_${i}`);
		const waystoneDim = player.getDynamicProperty(`mythicus:waystoneDim_${i}`);

		if (waystoneDim != dimension) {
			continue;
		} else if (waystoneLoc == null || !(waystoneLoc.x == x && waystoneLoc.y == y && waystoneLoc.z == z)) {
			continue;
		}

		const waystoneName = player.getDynamicProperty(`mythicus:waystoneName_${i}`) ?? `${waystoneDefaultName}`;
		const waystoneAmountCur = player.getDynamicProperty("mythicus:waystoneAmountCur") ?? 0;
		player.setDynamicProperty("mythicus:waystoneAmountCur", Mathf.clamp(waystoneAmountCur - 1, 0, waystoneAmountMax));
		player.setDynamicProperty(`mythicus:waystoneLoc_${i}`, null);
		player.setDynamicProperty(`mythicus:waystoneDim_${i}`, null);
		player.setDynamicProperty(`mythicus:waystoneSym_${i}`, 0);
		player.setDynamicProperty(`mythicus:waystoneName_${i}`, `${waystoneDefaultName}`);
		if (includeMsg) {
			if (waystoneName === waystoneDefaultName)
				player.sendMessage(Rawtext.create("waystone.mythicus:state.broken", [waystoneChatPrefix, waystoneDefaultName, `${i + 1}`, `dimension.dimensionName${dimensions_display[dimension] ?? 0}`]));
			else
				player.sendMessage(Rawtext.create("waystone.mythicus:state.brokenCustom", [waystoneChatPrefix, waystoneDefaultName, waystoneName, `dimension.dimensionName${dimensions_display[dimension] ?? 0}`]));
		}
		//break;   // might have multiple waystones set to same location
	}
}

// Removes a waystone from the world and all associated players
function removeWaystoneAll(dimension, x, y, z) {
	world.setDynamicProperty(`mythicus:waystoneOwner_${encodeWaypoint(x, y, z, dimension)}`, undefined);
	world.getPlayers().forEach((player) => {
		removeWaystone(player, dimension, x, y, z, true);
	})
}

// Prevent player's breaking non-owned waystones
world.beforeEvents.playerBreakBlock.subscribe(event => {
	const { x, y, z } = event.block;
	const dimension = event.block.dimension.id;
	const player = event.player;

	const waystoneOwner = world.getDynamicProperty(`mythicus:waystoneOwner_${encodeWaypoint(x, y, z, dimension)}`);
	if (waystoneOwner == undefined) {
		event.block.dimension.runCommandAsync(`setblock ${x} ${y} ${z} air`);
		event.cancel = true;
		return;
	}

	if (waystoneOwner.split("::")[0] !== player.id) {
		player.sendMessage(Rawtext.create("waystone.mythicus:action.breakUnowned", [waystoneChatPrefix]));
		event.cancel = true;
	}
}, { permutations: waystone_permutations });

// Remove broken waystones
world.afterEvents.playerBreakBlock.subscribe(event => {
	const { x, y, z } = event.block;
	const dim = event.block.dimension;
	const dimension = dim.id;

	if (event.block?.above()?.permutation?.matches("minecraft:barrier") === true) {
		dim.runCommand(`setblock ${x} ${y + 1} ${z} air`);
	}

	for (const entity of dim.getEntitiesAtBlockLocation(event.block.location)) {
		if (entity.typeId === "mythicus_ws:waystone.anchor") {
			entity.remove();
		}
	}

	Jigcs.add("active", -1);
	if (dimension === 'minecraft:overworld') Jigcs.add("overworld", -1);
	else if (dimension === 'minecraft:nether') Jigcs.add("nether", -1);
	else Jigcs.add("the_end", -1);

	removeWaystoneAll(dimension, x, y, z);
}, { permutations: waystone_permutations });

function breakWaystone(dimension, loc) {
	const dim = world.getDimension(dimension);
	const block = dim.getBlock(loc);
	if (!block) return;

	if (block.hasTag("mythicus_ws:waystone.cute") === false && block?.above()?.permutation?.matches("minecraft:barrier") === true) {
		dim.runCommand(`setblock ${loc.x} ${loc.y + 1} ${loc.z} air`);
	}

	for (const entity of dim.getEntitiesAtBlockLocation(loc)) {
		if (entity.typeId === "mythicus_ws:waystone.anchor") {
			entity.remove();
		}
	}

	Jigcs.add("active", -1);
	if (dimension === 'minecraft:overworld') Jigcs.add("overworld", -1);
	else if (dimension === 'minecraft:nether') Jigcs.add("nether", -1);
	else Jigcs.add("the_end", -1);

	dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} air destroy`);
	removeWaystoneAll(dimension, loc.x, loc.y, loc.z);
}

// Try to place a new waystone
world.afterEvents.playerPlaceBlock.subscribe(event => {
	const { x, y, z } = event.block;
	const dim = event.block.dimension;
	const dimension = dim.id;
	const player = event.player;
	const waystoneSmall = event.block.hasTag("mythicus_ws:waystone.cute");

	if (event.block?.above()?.isAir === false || dim.heightRange.max - 2 < y) {
		dim.runCommand(`setblock ${x} ${y} ${z} air destroy`);
		player.sendMessage(Rawtext.create("waystone.mythicus:state.invalid", [waystoneChatPrefix]));
		return;
	}

	if (dimension === "minecraft:the_end" && 95 <= x && x <= 105 && 45 <= y && y <= 55 && -5 <= z && z <= 5) {
		dim.runCommand(`setblock ${x} ${y} ${z} air destroy`);
		player.sendMessage(Rawtext.create("waystone.mythicus:state.endplatform", [waystoneChatPrefix]));
		return;
	}

	const waystoneBlockId = waystone_permutations.indexOf(event.block.permutation) || 0;
	if (addWaystone(player, dimension, x, y, z, true, waystoneBlockId)) {
		if (waystoneSmall === false) dim.runCommand(`setblock ${x} ${y + 1} ${z} barrier`);
		const anchorLoc = event.block.bottomCenter();
		const anchor = waystoneSmall ?
			dim.spawnEntity("mythicus_ws:waystone.anchor<mythicus_ws:size.small>", anchorLoc)
			:
			dim.spawnEntity("mythicus_ws:waystone.anchor", anchorLoc);
		anchor.setDynamicProperty("mythicus:waystoneBlockPos", anchorLoc);
		return;
	}

	dim.runCommand(`setblock ${x} ${y} ${z} air destroy`);
	player.sendMessage(Rawtext.create("waystone.mythicus:state.maximum", [waystoneChatPrefix]));
}, { permutations: waystone_permutations });



const pistonOffsetLocations = [
	{ x: -1, y: 0, z: 0 },
	{ x: 1, y: 0, z: 0 },
	{ x: 0, y: 0, z: -1 },
	{ x: 0, y: 0, z: 1 },
	{ x: 0, y: -1, z: 0 },
];

// Piston
ScriptEventListener.subscribe("mythicus:waystone.replace", ({ sourceEntity }) => {

	const location = sourceEntity.getDynamicProperty("mythicus:waystoneBlockPos");
	if (location == undefined) {
		sourceEntity.triggerEvent("mythicus_ws:instant_despawn");
		return;
	}

	const waystoneBlock = sourceEntity.dimension.getBlock(location);
	if (waystoneBlock === undefined) return;

	// ensure block is waystone
	const loc = waystoneBlock.location;
	const dimId = sourceEntity.dimension.id;
	let waystoneOwner = world.getDynamicProperty(`mythicus:waystoneOwner_${encodeWaypoint(loc.x, loc.y, loc.z, dimId)}`);
	if (waystoneOwner == undefined) {
		sourceEntity.triggerEvent("mythicus_ws:instant_despawn");
		return;
	}

	// clear nearby invalid waystones
	for (const offset of pistonOffsetLocations) {
		const block = waystoneBlock.offset(offset);
		if (block === undefined || block.hasTag("mythicus_ws:waystone") === false) continue;
		if (world.getDynamicProperty(`mythicus:waystoneOwner_${encodeWaypoint(block.x, block.y, block.z, dimId)}`) == undefined) {
			block.setPermutation(BlockPermutation.resolve("minecraft:air"));
		}
	}

	system.runTimeout(() => {

		// clear nearby invalid waystones
		for (const offset of pistonOffsetLocations) {
			const block = waystoneBlock.offset(offset);
			if (block === undefined || block.hasTag("mythicus_ws:waystone") === false) continue;
			if (world.getDynamicProperty(`mythicus:waystoneOwner_${encodeWaypoint(block.x, block.y, block.z, dimId)}`) == undefined) {
				block.setPermutation(BlockPermutation.resolve("minecraft:air"));
			}
		}

		// break block in waystone position
		if (waystoneBlock.hasTag("mythicus_ws:waystone")) {
			waystoneBlock.setPermutation(BlockPermutation.resolve("minecraft:air"));
		} else {
			waystoneBlock.dimension.runCommand(`/execute positioned ${location.x} ${location.y} ${location.z} unless block ~ ~ ~ air run setblock ~ ~ ~ air destroy`);
		}

		// replace waystone
		waystoneOwner = world.getDynamicProperty(`mythicus:waystoneOwner_${encodeWaypoint(loc.x, loc.y, loc.z, dimId)}`);
		if (waystoneOwner == undefined) return;

		const waystoneBlockId = parseInt(waystoneOwner.split("::")[1] || 0);
		waystoneBlock.setPermutation(waystone_permutations[waystoneBlockId]);

	}, 1);

	

}, { entityTypeIds: ["mythicus_ws:waystone.anchor"] });

ScriptEventListener.subscribe("mythicus:waystone.reposition", ({ sourceEntity }) => {
	const loc = sourceEntity.getDynamicProperty("mythicus:waystoneBlockPos");
	if (loc == undefined) {
		sourceEntity.triggerEvent("mythicus_ws:instant_despawn");
		return;
	}

	sourceEntity.teleport(loc);
}, { entityTypeIds: ["mythicus_ws:waystone.anchor"] });