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

use super::{C_COST, D_COST};

pub trait Searcher {
	fn new(start: IVec2, goal: IVec2) -> Self;
	fn found_goal(&mut self, node: IVec2, est: i32) -> Option<IVec2>;
	fn heuristic(a: IVec2, b: IVec2) -> i32;
	fn not_found(self) -> Option<IVec2>;
}

pub struct Goal {
	start: IVec2,
	goal: IVec2,
}

impl Searcher for Goal {
	fn new(start: IVec2, goal: IVec2) -> Goal {
		Goal { start, goal }
	}

	fn found_goal(&mut self, node: IVec2, _est: i32) -> Option<IVec2> {
		(self.goal == node).then_some(node)
	}

	fn heuristic(a: IVec2, b: IVec2) -> i32 {
		heuristic::<2>(a, b)
	}

	fn not_found(self) -> Option<IVec2> {
		log::warn!("failed finding path that was expected to be reachable, start = {}, goal = {}", self.start, self.goal);
		None
	}
}

pub struct Avoid {
	best: IVec2,
	best_est: i32,
	count: usize,
}

const AVOID_SEARCH_COUNT: usize = 40;

impl Searcher for Avoid {
	fn new(_start: IVec2, _goal: IVec2) -> Avoid {
		Avoid { best: IVec2::ZERO, best_est: i32::MAX, count: 0 }
	}

	fn found_goal(&mut self, node: IVec2, est: i32) -> Option<IVec2> {
		if est < self.best_est {
			self.best = node;
			self.best_est = est;
		}
		self.count += 1;
		(self.count >= AVOID_SEARCH_COUNT).then_some(self.best)
	}

	fn heuristic(a: IVec2, b: IVec2) -> i32 {
		heuristic::<-2>(a, b)
	}

	fn not_found(self) -> Option<IVec2> {
		(self.best_est != i32::MAX).then_some(self.best)
	}
}

pub fn octile_distance(a: IVec2, b: IVec2) -> i32 {
	heuristic::<1>(a, b)
}

fn heuristic<const WEIGHT: i32>(a: IVec2, b: IVec2) -> i32 {
	let abs_diff = (a - b).abs();
	(const { C_COST * WEIGHT }) * (abs_diff.x - abs_diff.y).abs() + (const { D_COST * WEIGHT }) * abs_diff.min_element()
}
