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

use super::{Effect, EffectInfo};

use crate::world::effects::PowerType;

#[enum_dispatch]
pub trait EffectReducer: Clone {
	fn reset(&mut self);
	fn powerup_collected(&mut self, info: EffectInfo);
	fn get_power(&self, effect: Effect) -> Option<f32>;
	fn get_time(&self) -> f32;
	fn update(&mut self, dt: f32);
}

#[enum_dispatch(EffectReducer)]
#[derive(Clone)]
pub enum EffectReducerBehaviour {
	Extend,
	Stack,
}

#[derive(Default, Clone)]
pub struct Extend(EffectInfo);

impl EffectReducer for Extend {
	fn reset(&mut self) {
		self.0.time = 0.0;
	}

	fn powerup_collected(&mut self, info: EffectInfo) {
		self.0.time = self.0.time.max(0.0) + info.time;
		self.0.power = info.power;
	}

	fn get_power(&self, _effect: Effect) -> Option<f32> { self.0.get_power() }
	fn get_time(&self) -> f32 { self.0.time }
	fn update(&mut self, dt: f32) { self.0.time -= dt; }
}

#[derive(Default, Clone)]
pub struct Stack(Vec<EffectInfo>);

impl EffectReducer for Stack {
	fn reset(&mut self) {
		self.0.clear();
	}

	fn powerup_collected(&mut self, info: EffectInfo) {
		self.0.push(info);
	}

	fn get_power(&self, effect: Effect) -> Option<f32> {
		self.0.iter().filter_map(|info| info.get_power()).reduce(|a, b| stack_power(effect, a, b))
	}

	fn get_time(&self) -> f32 {
		self.0.iter().map(|info| info.time).reduce(f32::max).unwrap_or_default()
	}

	fn update(&mut self, dt: f32) {
		self.0.retain_mut(|info| {
			info.time -= dt;
			info.time > 0.0
		});
	}
}

pub fn stack_power(effect: Effect, power_a: f32, power_b: f32) -> f32 {
	match effect.power_type() {
		PowerType::Multiplicative => power_a * power_b,
		PowerType::Additive => power_a + power_b,
	}
}
