#pragma once

// -----------------------------------------------------------------------------
// aimhelper.h
// -----------------------------------------------------------------------------
// Shared aim helpers used by the triggerbot, force-aim and flickshot
// features: angle normalization, angle calculation and velocity prediction
// plus world-to-screen projection. Pure header-only helpers.
// -----------------------------------------------------------------------------

#include <cmath>

#include "../sdk/vector.h"

namespace aim {

// Normalizes an angle: clamps pitch to [-89, 89], wraps yaw to [-180, 180]
// and forces roll to zero. Modifies the vector in place.
inline void Normalize(sdk::Vec3& angle)
{
	if (!std::isfinite(angle.x) || !std::isfinite(angle.y) || !std::isfinite(angle.z)) {
		angle = sdk::Vec3{};
		return;
	}

	while (angle.y < -180.0f) { angle.y += 360.0f; }
	while (angle.y > 180.0f) { angle.y -= 360.0f; }

	if (angle.x > 89.0f) { angle.x = 89.0f; }
	if (angle.x < -89.0f) { angle.x = -89.0f; }
	angle.z = 0.0f;
}

// Moves `current` a fraction (`factor` in 0..1) of the remaining delta
// toward `target`; repeated application converges exponentially, which
// reads as a fast human-like sweep instead of an instant snap. Yaw wrap
// and pitch clamp come from Normalize.
inline sdk::Vec3 SmoothedStep(const sdk::Vec3& current, const sdk::Vec3& target, float factor)
{
	if (factor < 0.0f) {
		factor = 0.0f;
	}
	if (factor > 1.0f) {
		factor = 1.0f;
	}

	sdk::Vec3 delta{ target.x - current.x, target.y - current.y, 0.0f };
	Normalize(delta);
	delta.x *= factor;
	delta.y *= factor;

	sdk::Vec3 result{ current.x + delta.x, current.y + delta.y, 0.0f };
	Normalize(result);
	return result;
}

// Calculates the aim angle from `source` to `target`. No recoil punch
// compensation is applied: the mouse is only ever moved by the explicit
// hotkey features (ALT force aim, CAPS LOCK flickshot, MMB triggerbot),
// never while the player fires normally with the left mouse button.
inline sdk::Vec3 CalculateAngle(const sdk::Vec3& source, const sdk::Vec3& target,
	float viewOriginZ)
{
	sdk::Vec3 delta{
		source.x - target.x,
		source.y - target.y,
		(source.z + viewOriginZ) - target.z,
	};

	const float hyp = std::sqrt(delta.x * delta.x + delta.y * delta.y);
	if (hyp == 0.0f) {
		return sdk::Vec3{};
	}

	sdk::Vec3 angle;
	angle.x = std::atan(delta.z / hyp) * 57.295779513f;
	angle.y = std::atan(delta.y / delta.x) * 57.295779513f;
	angle.z = 0.0f;

	if (delta.x >= 0.0f) {
		angle.y += 180.0f;
	}

	Normalize(angle);
	return angle;
}

// Applies simple linear velocity prediction to the enemy position so the
// aim leads a moving target. `smoothAmount` scales the lead factor.
// Garbage velocity values (stale model data) are clamped and a zero
// smoothness is ignored: otherwise the predicted position could leave the
// world bounds and the aim would point at the sky or the ground.
inline void ApplyVelocityCompensation(sdk::Vec3& enemyPos, const sdk::Vec3& enemyVelocity,
	const sdk::Vec3& myVelocity, int smoothAmount)
{
	if (smoothAmount <= 0) {
		return;
	}

	auto clamp = [](float v) { return (v > 6000.0f) ? 6000.0f : (v < -6000.0f) ? -6000.0f : v; };
	const sdk::Vec3 enemyVel{ clamp(enemyVelocity.x), clamp(enemyVelocity.y), clamp(enemyVelocity.z) };
	const sdk::Vec3 myVel{ clamp(myVelocity.x), clamp(myVelocity.y), clamp(myVelocity.z) };

	const float factor = (100.0f / static_cast<float>(smoothAmount)) / 100.0f;
	enemyPos = enemyPos + enemyVel * factor - myVel * factor;
}

// Approximate vertical height above the player origin of the given aim
// bone, used when the bone array is stale or zeroed (the enemy's model is
// not rendered yet): aiming at the origin alone would aim at the ground,
// adding the per-bone height keeps the shot at body height.
inline float FallbackBoneHeight(int aimBone, int headBone, int stomachBone)
{
	if (aimBone == headBone) {
		return 64.0f;
	}
	if (aimBone == stomachBone) {
		return 45.0f;
	}
	return 57.0f;
}

// Projects a world position onto the screen using the 4x4 view matrix
// (dwViewMatrix, row-major floats). Returns false when the point is behind
// the camera; on success writes the pixel coordinates into outX/outY.
inline bool WorldToScreen(const float* viewMatrix, const sdk::Vec3& world,
	int screenW, int screenH, float& outX, float& outY)
{
	const float w = viewMatrix[12] * world.x + viewMatrix[13] * world.y +
		viewMatrix[14] * world.z + viewMatrix[15];
	if (w < 0.001f) {
		return false;
	}

	const float x = viewMatrix[0] * world.x + viewMatrix[1] * world.y +
		viewMatrix[2] * world.z + viewMatrix[3];
	const float y = viewMatrix[4] * world.x + viewMatrix[5] * world.y +
		viewMatrix[6] * world.z + viewMatrix[7];

	const float invW = 1.0f / w;
	outX = (x * invW + 1.0f) * 0.5f * screenW;
	outY = (1.0f - y * invW) * 0.5f * screenH;
	return true;
}

} // namespace aim