import { world, system, EquipmentSlot, EntityDamageCause, ItemStack, Entity, Player } from '@minecraft/server';
import { addVec, approxEquals, distanceSquared, normalizeVec, relativePos, scaleVec } from './math';

const runs = new Map()

world.afterEvents.itemStartUse.subscribe(e => {
	const { itemStack, source } = e;
	const hasBatteryPack = source.getComponent("equippable").getEquipment(EquipmentSlot.Chest)?.typeId == "compycraft_alien:battery_pack" ? true : false;
	switch (itemStack.typeId) {
		case "compycraft_alien:laser_minigun":
			if (hasBatteryPack)
				runs.set(source.id, system.runInterval(() => {
					fireBlaster(source, { damage: 3, deviation: 0.05, maxDistance: 48, soundId: "cc.minigun", pitch: 1.5, speed: 3, gravity: 0, offset: [-0.8, 1.25, 2], particleId: 'compycraft_alien:laser_main' })
				}, 2))
			else failed([{ translate: 'action.hint.compycraft_alien.requires_batterypack' }, { translate: 'item.compycraft_alien:battery_pack' }], source)
			break;
		case "compycraft_alien:laser_ray":
			if (hasBatteryPack)
				runs.set(source.id, system.runInterval(() => {
					fireBlasterWithRay(source, { damage: 3, deviation: 0, maxDistance: 12, soundId: "cc.beam", pitch: 2, speed: 3, gravity: 0, offset: [-0.8, 1.25, 2.5], particleId: 'compycraft_alien:laser_main' })
				}, 2))
			else failed([{ translate: 'action.hint.compycraft_alien.requires_batterypack' }, { translate: 'item.compycraft_alien:battery_pack' }], source)
			break;
	}
});

world.afterEvents.itemStopUse.subscribe(e => {
	const { itemStack, source } = e;
	switch (itemStack.typeId) {
		case "compycraft_alien:laser_minigun": if (runs.has(source.id)) system.clearRun(runs.get(source.id))
			break;
		case "compycraft_alien:laser_ray": if (runs.has(source.id)) system.clearRun(runs.get(source.id))
			break;
	}
})

/**
 * @typedef BlasterOptions
 * @prop {number} damage
 * @prop {number} deviation
 * @prop {number} speed
 * @prop {number} maxDistance
 * @prop {number} gravity
 * @prop {string} particleId
 * @prop {string} soundId
 * @prop {number} pitch
 * @prop {[number, number, number]} offset
 */

world.beforeEvents.worldInitialize.subscribe(e => {
	const { itemComponentRegistry } = e;
	itemComponentRegistry.registerCustomComponent("compycraft_alien:on_use", {
		onUse(i) {
			const { itemStack, source } = i;
			const hasBatteryPack = source.getComponent("equippable").getEquipment(EquipmentSlot.Chest)?.typeId == "compycraft_alien:battery_pack" ? true : false;
			switch (itemStack.typeId) {
				case "compycraft_alien:laser_pistol":
				case "compycraft_alien:laser_rifle":
					fireBlaster(source, { damage: 4, deviation: 0.035, soundId: "cc.blaster", pitch: 1.5, gravity: 0, maxDistance: 48, speed: 3, offset: [-1, 1.25, 0.75], particleId: 'compycraft_alien:laser_main' })
					itemStack.getComponent("cooldown").startCooldown(source);
					break;
				case "compycraft_alien:laser_sniper":
					fireBlasterWithRay(source, { damage: 20, deviation: 0, soundId: "cc.sniper", pitch: 2, gravity: 0, maxDistance: 128, speed: 6, offset: [-0.5, 1.25, 0.75], particleId: 'compycraft_alien:laser_main' })
					itemStack.getComponent("cooldown").startCooldown(source);
					break;
				case "compycraft_alien:laser_launcher":
					if (hasBatteryPack) {
						fireBlaster(source, { damage: 30, deviation: 0, maxDistance: 1000, soundId: "cc.launcher", pitch: 0.5, gravity: 0.5, speed: 1.5, offset: [-1, 1.25, 0.75], particleId: 'compycraft_alien:laser_main' })
						itemStack.getComponent("cooldown").startCooldown(source);
					} else failed([{ translate: 'action.hint.compycraft_alien.requires_batterypack' }, { translate: 'item.compycraft_alien:battery_pack' }], source)
					break;
			}
		}
	});
	itemComponentRegistry.registerCustomComponent('compycraft_alien:light_on_fire', {
		onHitEntity(e) {
			const { hadEffect, hitEntity } = e;
			if (!hadEffect) return;
			hitEntity.setOnFire(1)
		}
	});
	itemComponentRegistry.registerCustomComponent('compycraft_alien:guidebook', {
		onUse(event) {
			const { source, itemStack } = event;

			const currentPage = parseInt(itemStack.typeId.slice(23));
			let nextPage = currentPage + 1;

			if (source.isSneaking) nextPage = currentPage - 1;

			if (nextPage > 15) nextPage = 1;
			else if (nextPage < 1) nextPage = 15;

			source.getComponent('equippable').setEquipment(EquipmentSlot.Mainhand, new ItemStack(`compycraft_alien:guide_${nextPage}`));

			source.playSound("cc.beep");
		}
	})
});

/**
 * @param {Player} player
 * @param {BlasterOptions} options
 */
export function fireBlaster(player, options) {

	const { damage, deviation, pitch, soundId, gravity, maxDistance, speed, offset, particleId } = options;

	const xRot = player.getRotation().x
	const diff = -xRot < 0 ? (1 / 64) : (1 / -64);
	const start = relativePos(player, offset[0], offset[1] + (-xRot * diff), offset[2])

	const direction = normalizeVec(addVec(player.getViewDirection(), { x: (Math.random() - 0.5) * deviation, y: (Math.random() - 0.5) * deviation, z: (Math.random() - 0.5) * deviation }), 1)
	const dimension = player.dimension;
	const headLocation = player.getHeadLocation()
	const maxDistanceSquared = maxDistance ** 2;

	const entity = dimension.spawnEntity('compycraft_alien:bullet<as_' + damage + '>', start)
	const projectile = entity.getComponent('projectile')
	projectile.owner = player;
	projectile.gravity = 0;

	const headentity = dimension.spawnEntity('compycraft_alien:bullet<as_' + damage + '>', headLocation)
	const headprojectile = headentity.getComponent('projectile')
	headprojectile.owner = player;
	headprojectile.gravity = 0;

	const velocity = scaleVec(direction, speed)
	projectile.shoot(velocity)
	headprojectile.shoot(velocity)

	let dy = 0
	let previousLocation = start;
	const run = system.runInterval(() => {

		const isEntity = entity.isValid(); const isHeadEntity = headentity.isValid();

		if (!isEntity && !isHeadEntity) { system.clearRun(run); return }

		const newVelocity = addVec(scaleVec(direction, speed), { x: 0, y: -dy, z: 0 });

		if (isEntity) {
			if (distanceSquared(start, previousLocation) > maxDistanceSquared) { entity.remove(); return }
			entity.clearVelocity();
			entity.applyImpulse(newVelocity);

			const currentLocation = entity.location
			for (let i = 0; i <= 10; i += 0.5) {
				const t = i / 10;
				const point = {
					x: previousLocation.x + (currentLocation.x - previousLocation.x) * t,
					y: previousLocation.y + (currentLocation.y - previousLocation.y) * t,
					z: previousLocation.z + (currentLocation.z - previousLocation.z) * t
				};
				try { dimension.spawnParticle(particleId, point) } catch (error) { }
			}

			previousLocation = currentLocation;
		}

		if (isHeadEntity) {
			if (distanceSquared(start, headentity.location) > maxDistanceSquared) { headentity.remove(); return }
			headentity.clearVelocity();
			headentity.applyImpulse(newVelocity)
		}

		dy += gravity * 0.05;

	})

	dimension.playSound(soundId, headLocation, { pitch: pitch })

	if (damage == 30) return; // No need to spawn 4 explosions imo

	const lowerEntity = dimension.spawnEntity('compycraft_alien:bullet<as_' + damage + '>', start)
	const lowerProjectile = lowerEntity.getComponent('projectile')
	lowerProjectile.owner = player;
	lowerProjectile.gravity = 0;

	const lowerHeadEntity = dimension.spawnEntity('compycraft_alien:bullet<as_' + damage + '>', headLocation)
	const lowerHeadProjectile = lowerHeadEntity.getComponent('projectile')
	lowerHeadProjectile.owner = player;
	lowerHeadProjectile.gravity = 0;

	const lowerVelocity = scaleVec(direction, 1)
	lowerProjectile.shoot(lowerVelocity)
	lowerHeadProjectile.shoot(lowerVelocity)

	let lowerDy = 0;
	let lowerPreviousLocation = start;
	const lowerDistanceRun = system.runInterval(() => {

		const isEntity = lowerEntity.isValid(); const isHeadEntity = lowerHeadEntity.isValid();

		if (!isEntity && !isHeadEntity) { system.clearRun(lowerDistanceRun); return }

		const newVelocity = addVec(scaleVec(direction, 1), { x: 0, y: -lowerDy, z: 0 });

		if (isEntity) {
			if (distanceSquared(start, lowerPreviousLocation) > speed) { lowerEntity.remove(); return }
			lowerEntity.clearVelocity();
			lowerEntity.applyImpulse(newVelocity)
			lowerPreviousLocation = lowerEntity.location;
		}

		if (isHeadEntity) {
			try {
				if (distanceSquared(start, headentity.location) > speed) { lowerHeadEntity.remove(); return }
				lowerHeadEntity.clearVelocity();
				lowerHeadEntity.applyImpulse(newVelocity)
			} catch (error) { }
		}

		lowerDy += gravity * 0.05;

	})

}

/**
 * @param {Player} player
 * @param {BlasterOptions} options
 */
export function fireBlasterWithRay(player, options) {

	const { damage, deviation, pitch, soundId, gravity, maxDistance, speed, offset, particleId } = options;

	const xRot = player.getRotation().x
	const diff = -xRot < 0 ? (1 / 64) : (1 / -64);
	const start = relativePos(player, offset[0], offset[1] + (-xRot * diff), offset[2])

	const direction = normalizeVec(addVec(player.getViewDirection(), { x: (Math.random() - 0.5) * deviation, y: (Math.random() - 0.5) * deviation, z: (Math.random() - 0.5) * deviation }), 1)
	const dimension = player.dimension;
	const headLocation = player.getHeadLocation()
	const maxDistanceSquared = maxDistance ** 2;

	const viewedEntity = player.getEntitiesFromViewDirection({ maxDistance: maxDistance, includePassableBlocks: false, includeLiquidBlocks: false })[0]?.entity
	const healthComponent = viewedEntity?.getComponent('health')
	const originalHealth = healthComponent?.currentValue

	const entity = dimension.spawnEntity('compycraft_alien:bullet<as_' + damage + '>', start)
	const projectile = entity.getComponent('projectile')
	projectile.owner = player;
	projectile.gravity = 0;

	const headentity = dimension.spawnEntity('compycraft_alien:bullet<as_' + damage + '>', headLocation)
	const headprojectile = headentity.getComponent('projectile')
	headprojectile.owner = player;
	headprojectile.gravity = 0;

	const velocity = scaleVec(direction, speed)
	projectile.shoot(velocity)
	headprojectile.shoot(velocity)

	let dy = 0
	let previousLocation = start;
	const run = system.runInterval(() => {

		const isEntity = entity.isValid(); const isHeadEntity = headentity.isValid();

		if (!isEntity && !isHeadEntity) {
			if (viewedEntity && viewedEntity.isValid() && healthComponent?.currentValue == originalHealth) viewedEntity.applyDamage(21, { cause: EntityDamageCause.entityAttack, damagingEntity: player })
			system.clearRun(run);
			return
		}

		const newVelocity = addVec(scaleVec(direction, speed), { x: 0, y: -dy, z: 0 });

		if (isEntity) {
			if (distanceSquared(start, previousLocation) > maxDistanceSquared) { entity.remove(); return }
			entity.clearVelocity();
			entity.applyImpulse(newVelocity);

			const currentLocation = entity.location
			for (let i = 0; i <= 10; i += 0.5) {
				const t = i / 10;
				const point = {
					x: previousLocation.x + (currentLocation.x - previousLocation.x) * t,
					y: previousLocation.y + (currentLocation.y - previousLocation.y) * t,
					z: previousLocation.z + (currentLocation.z - previousLocation.z) * t
				};
				try { dimension.spawnParticle(particleId, point) } catch (error) { }
			}

			previousLocation = currentLocation;
		}

		if (isHeadEntity) {
			if (distanceSquared(start, headentity.location) > maxDistanceSquared) { headentity.remove(); return }
			headentity.clearVelocity();
			headentity.applyImpulse(newVelocity)
		}

		dy += gravity * 0.05;

	})

	dimension.playSound(soundId, headLocation, { pitch: pitch })

}

/**
 * @param {(import("@minecraft/server").RawMessage | string)[] | import("@minecraft/server").RawMessage | string} msg 
 * @param {Player} player 
 */
function failed(msg, player) {
	player.onScreenDisplay.setActionBar(msg)
	player.playSound('note.bass', { pitch: 1, volume: 0.8, location: player.location })
}