// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
mod renderer;

pub use renderer::Renderer as HealthBarRenderer;

use glam::{Vec2, Vec3};

use renderer::Instance;

use super::{Camera, world_label::PX_OFF};

use crate::app::playing::view::world::LocalPlayer;
use crate::world::player::Player;
use crate::utils::maths::decay::Decay;

pub struct HealthBar {
	rendered_health: f32,
}

const HUD_WIDTH: f32 = 200.0;
pub const HUD_HEIGHT: f32 = 15.0;

const ABOVE_WIDTH: f32 = 1.0;
const ABOVE_HEIGHT: f32 = 0.125;

pub struct AbovePlayerBuilder<'a> {
	lwinsize: Vec2,
	size: [f32; 2],
	camera: &'a Camera,
}

impl HealthBar {
	pub fn new(health: f32) -> HealthBar { HealthBar { rendered_health: health.clamp(0.0, 2.0) } }
	pub fn reset(&mut self) { self.rendered_health = 1.0; }

	pub fn update(&mut self, player: &Player, dt: f32) {
		let health = player.get_norm_health();

		/*
		 * Decays the health bar faster when the health's lower as up-to-date
		 * information is more important then, which can prevent the player from
		 * overestimating their or others' health.
		 */
		let rate = 1.0 / health + 7.0;
		self.rendered_health.decay_to(health, rate, dt);

		/*
		 * Fixes bug when the normalised rendered health is above one (from
		 * collecting a health powerup), with the actual health being one and this
		 * rendered health never actually decaying to it (happens for some values
		 * of `dt`).
		 *
		 * Usually this isn't an issue because a slight difference in rendered
		 * health isn't noticeable, but this is important when there's a sudden
		 * discontinuity with a rendered health of one, with just above one being
		 * a sudden change in colour.
		 *
		 * The fix is to immediately jump to the actual health when the difference
		 * is sufficiently small so the user won't normally notice.
		 */
		if (health - self.rendered_health).abs() < 1e-4 {
			self.rendered_health = health;
		}

		self.rendered_health = self.rendered_health.clamp(0.0, 2.0);
	}

	/**
	 * Returns the x-pixel coordinates of the position and size of the health bar
	 * rendered on the HUD.
	 */
	pub fn hud_x_px(width: f32) -> (f32, f32) {
		let size = (width - 2.0 * HUD_HEIGHT).clamp(0.0, HUD_WIDTH);
		let pos = (width - size) / 2.0;
		(pos, size)
	}

	pub fn make_hud(&self, lwinsize: Vec2) -> Instance {
		let (px_x, px_width) = HealthBar::hud_x_px(lwinsize.x);
		let ndc_width = 2.0 * px_width / lwinsize.x;
		let ndc_x = 2.0 * px_x / lwinsize.x - 1.0;

		Instance {
			i_pos: [ndc_x, -1.0 + 2.0 * HUD_HEIGHT / lwinsize.y],
			i_size: [ndc_width, 2.0 * HUD_HEIGHT / lwinsize.y],
			i_t: self.get_t(),
			i_colour: self.get_colour(),
		}
	}

	pub fn above_player_builder(lwinsize: Vec2, camera: &Camera) -> AbovePlayerBuilder<'_> {
		let size = camera.get_size();

		AbovePlayerBuilder {
			lwinsize, camera,
			size: [
				2.0 * ABOVE_WIDTH / size.x,
				2.0 * ABOVE_HEIGHT / size.y,
			],
		}
	}

	fn get_colour(&self) -> [f32; 3] {
		let t = self.rendered_health;
		if t < 0.5 {
			Vec3::new(1.0, t * 2.0, 0.0)
		} else if t <= 1.0 {
			Vec3::new(2.0 - t * 2.0, 1.0, 0.0)
		} else {
			Vec3::new(0.0, 0.75, 1.0)
		}.to_array()
	}

	fn get_t(&self) -> f32 {
		if self.rendered_health <= 1.0 { self.rendered_health } else { self.rendered_health - 1.0 }
	}
}

impl AbovePlayerBuilder<'_> {
	/**
	 * Creates the health bar as well as returning the logical pixel position of
	 * the centre of where the name should be rendered.
	 */
	pub fn make(&self, player: &LocalPlayer, hbar: &HealthBar, time_diff: f32) -> (Instance, Vec2) {
		let above_px_pos = self.camera.world_to_pixel_coords_with_size(player.get_pos(time_diff) + Vec2::new(-ABOVE_WIDTH / 2.0, 1.25), self.lwinsize);
		let name_px_pos = self.camera.world_to_pixel_coords_with_size(player.get_pos(time_diff) + Vec2::new(0.0, 1.75), self.lwinsize) + PX_OFF;

		(Instance {
			i_pos: [
				2.0 * above_px_pos.x / self.lwinsize.x - 1.0,
				1.0 - 2.0 * above_px_pos.y / self.lwinsize.y,
			],
			i_size: self.size,
			i_t: hbar.get_t(),
			i_colour: hbar.get_colour(),
		}, name_px_pos)
	}
}
