// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::{sync::Arc, collections::BTreeMap};

use egui::{Window, Frame, Ui, FontId, FontFamily, RichText, Color32, Context};

use super::world_label;

use crate::protocol::message::scores::{Scores, ScoreOrdering};
use crate::world::{World, colour::Colour, player::PlayerId, team::TeamId};
use crate::app::gui::{style::{SCORE_H1_SIZE, SCORE_H2_SIZE, LABEL_COLOUR}, layout_job_helper::LayoutJobHelper};
use crate::utils::max_len::MaxLenArcStr;

pub(super) const GREY: Color32 = LABEL_COLOUR;
pub(super) const MARGIN: f32 = 16.0;

pub fn render(ctx: &Context, scores: &Scores, player_id: Option<PlayerId>, team_id: Option<TeamId>, world: &World) {
	let (player_scores, team_scores) = (scores.get_player_scores(), scores.get_team_scores());

	let score_count = (!player_scores.is_empty()) as u32 + (!team_scores.is_empty()) as u32;
	if score_count == 0 { return; }

	Window::new("scores")
		.frame(Frame::none())
		.fixed_pos([MARGIN, MARGIN])
		.min_width(f32::MAX)
		.resizable(false)
		.title_bar(false)
		.show(ctx, |ui| {
			if !team_scores.is_empty() {
				let styles = |id| world.get_team(id).map(|team| (Some(team.get_name_owned()), team.get_colour()));
				add_scores(ui, team_id, if score_count == 2 { "Team Scores" } else { "Scores" }, team_scores, scores.get_team_ordering(), styles);
			}

			if score_count == 2 {
				ui.add_space(SCORE_H1_SIZE * 2.0);
			}

			if !player_scores.is_empty() {
				let styles = |id| world.get_player_style(id).map(|style| (style.get_name().map(MaxLenArcStr::inner), style.get_colour()));
				add_scores(ui, player_id, if score_count == 2 { "Player Scores" } else { "Scores" }, player_scores, scores.get_player_ordering(), styles);
			}
		});
}

fn add_scores<T, S>(ui: &mut Ui, current_id: Option<T>, header: &str, scores: &BTreeMap<T, i32>, ordering: ScoreOrdering, styles: S)
where
	T: Copy + Ord,
	S: Fn(T) -> Option<(Option<Arc<str>>, Colour)>,
{
	ui.label(RichText::new(header).color(GREY).font(FontId::new(SCORE_H1_SIZE, FontFamily::Name(Arc::from("scores")))));
	ui.add_space(8.0);

	let mut rendered_scores = scores.iter().map(|(&id, &score)| (id, score)).collect::<Vec<_>>();

	// Sorts the scores, if equal the BTreeMap results in them being then sorted by id in ascending order
	match ordering {
		ScoreOrdering::Ascending => rendered_scores.sort_by_key(|e| e.1),
		ScoreOrdering::Descending => rendered_scores.sort_by_key(|e| !e.1), // Not rather than negation to avoid overflow with i32::MIN
	}
	rendered_scores.truncate(16);

	let rs_has_current = current_id.is_some_and(|id| rendered_scores.iter().any(|e| e.0 == id));
	let truncated = rendered_scores.len() < scores.len();

	for (id, score) in rendered_scores {
		add_text(ui, score, styles(id), Some(id) == current_id);
		ui.add_space(4.0);
	}

	if truncated { // If the scores are truncated
		ui.colored_label(GREY, "…");
	}

	// Always shows the current player's score even if not in the top
	if !rs_has_current && let Some((id, &score)) = current_id.and_then(|id| Some(id).zip(scores.get(&id))) {
		ui.add_space(4.0);
		add_text(ui, score, styles(id), true);
	}
}

fn add_text(ui: &mut Ui, score: i32, style: Option<(Option<Arc<str>>, Colour)>, indent: bool) {
	let (name, num_colour) = match &style {
		Some((Some(name), colour)) => (name.as_ref(), colour.name_colour_dark()),
		Some((None, _)) => return,
		None => ("?", Color32::WHITE),
	};

	let mut job = LayoutJobHelper::with_font_id(FontId::new(SCORE_H2_SIZE, FontFamily::Name("scores".into())));
	if indent {
		job = job.indent(8.0);
	}

	let name_colour = Colour::name_colour_normal(num_colour);

	ui.label(job
		.add(&score.to_string(), num_colour)
		.add(&format!(" {}", world_label::process(name)), name_colour)
		.build());
}
