import { world, system, Player } from "@minecraft/server";
import { clamp, debug, Check, getCooldownTime } from "../mathAndCalculations.js";
import {skillRequirements} from '../config.js';

export class CombatManager {
	static attack(eventData) {
		const { player, target } = eventData;
		const status = player.getStatus();
		const headLoc = player.getHeadLocation();
		const loc = player.location;
		const currentTime = Date.now();
		
		const { equippableComp, item, stats } = player.getItemStats();

		const typeId = item?.typeId;
		const source = skillRequirements['items'][typeId];

	if (source && Array.isArray(source.requirements)) {
		let inBetweenText = '';
		let requirementsMet = true;

		source.requirements.forEach((req, index) => {
			const skill = player.getDynamicProperty(req.skill) ?? 0;
			inBetweenText += `${req.skill} ${req.level}`;
			if (index < source.requirements.length - 1) inBetweenText += ' & ';

			if (skill < req.level) {
				requirementsMet = false;
			}
		});

		if (!requirementsMet) {
			player.onScreenDisplay.setActionBar(`§4Required §9Knowledge of ${inBetweenText} §fto use the item`);
			return;
		}
	} else if (source && source.requirements) {
		// Single requirement check remains the same
		const skill = player.getDynamicProperty(source.requirements.skill) ?? 0;
		if (skill < source.requirements.level) {
			player.onScreenDisplay.setActionBar(`§4Required §9Knowledge of ${source.requirements.skill} ${source.requirements.level} §fto use the item`);
			return;
		}
	}

		const specialCheck = Check.specialValid(currentTime, player, stats);
		const dmg = Check.damageCalculation(currentTime, player, target, item, stats);
		const fireAspect = Check.enchantLevel(item, "fire_aspect");
		const crit = Check.criticalHit(currentTime, player, target, stats, { damage: dmg });
		const sprintKnockback = Check.sprintKnockback(currentTime, player, target, stats, { damage: dmg });
		const sweep = Check.sweep(currentTime, player, target, stats, { fireAspect: fireAspect, damage: dmg });
		const inanimate = Check.inanimate(target, {
			excludeTypes: [ "minecraft:armor_stand" ]
		})
		
		// If the player is sprinting, add a knockback level on top of current enchantment knockback level. 
		let knockback = Check.enchantLevel(item, "knockback")??0;
		if (sprintKnockback) knockback += 1;
		
		/** 
		* Checks for target's shield and attacker's axe.
		* Fun fact: entityAttack damage cause can knockback the damagingEntity(attacker)
		* if the victim is holding a shield and has their view towards the attacker.
		* Also the victim's shield can be disabled if the attacker is holding an axe.
		* And for some reason, entityExplosion damage cause behaves the same as entityAttack,
		* except it doesn't knockback the attacker nor checks for axe and
		* disable the shield.
		*/
		status.shieldDelay = 0;
		const shieldBlock = Check.shieldBlock(currentTime, player, target, stats);
		let dmgType = shieldBlock?"entityExplosion":"entityAttack";
		
		/**
		* Check if the iframes have passed or if the base damage done to the victim is larger than the one in the last attack.
		* This is to prevent the attacker from damaging the target rapidly while in iframes.
		*/
		const maxCD = getCooldownTime(player, stats?.attackSpeed).ticks;
		const curCD = status.cooldown;
		const perc = clamp( ((maxCD - curCD) / maxCD), 0.9, 1 );
		
		/**
		* Since applyAttackKnockback knockbacks the entity to the specific location, it would need
		* a calculation to get the direction for getting the coordinates the victim entity will land on.
		*/
		function calculateKnockbackDistance(baseDistance, knockback, perc) {
   		return Math.max(perc * baseDistance * (knockback || 1), 1);
		}
		
		function dir(k) {
			const l = target.location;
			const direction = {
				x: l.x - loc.x,
				y: l.y,
				z: l.z - loc.z
			};
			const length = Math.sqrt(direction.x ** 2 + direction.z ** 2);
			
			return {
				x: l.x + (direction.x / length) * k,
				y: direction.y,
				z: l.z + (direction.z / length) * k
			};
		}
		
		const lastAttack = target.__lastAttack || { damage: 0, time: 0 };
		const timeElapsed = currentTime - lastAttack.time;
		
		if (timeElapsed > 500 || (stats?.damage > lastAttack.damage && specialCheck)) {
			const damageValid = !status.mace ? target.applyDamage(dmg, { cause: dmgType, damagingEntity: player }) : false;
			
			// if the target is inanimate, skip durability reduction
			 if (!inanimate && dmg > 0) Check.durability(player, equippableComp, item, stats);
			
			try {
			    if (damageValid && !shieldBlock) {
			        const knockbackX = knockback > 0 
			            ? calculateKnockbackDistance(2.586, knockback, perc) 
			            : calculateKnockbackDistance(1.552, 1, perc);
			        const knockbackY = target.isOnGround ? (knockback > 0 ? perc * 1 : perc * 0.8125) : 0;
			
			        target.applyAttackKnockback(dir(knockbackX), knockbackY);
			    }
			} catch (e) { 
			    debug("Error during knockback: " + e + ", knockback skipped"); 
			}
			
			if (dmg > 0) {
				specialCheck
				? world.playSound("game.player.attack.strong", headLoc)
				: world.playSound("game.player.attack.weak.se", headLoc);
			}
			
			debug(`damage result\n\nattacked with: ${item?.typeId}\nattempted damage: ${dmg.toFixed(4)}\ntime since last atk: ${currentTime - status.lastAttackTime}ms`);
			
		} else {
			if (dmg > 0) world.playSound("game.player.attack.nodamage.se", headLoc);
			
			debug(`attacked with: ${item?.typeId}\nattempted damage: ${dmg.toFixed(4)} (iframes immunity)\ntime since last atk: ${currentTime - status.lastAttackTime}ms`);
		}
		
		if (dmg > 0) status.lastAttackTime = currentTime;
	}
};
