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

use glium::Frame as GliumFrame;
use serde::Deserialize;
use egui::{CursorIcon, Window, Align2, Color32};
use tokio::fs;

use super::common::{Common, CommonGs};

use crate::app::{App, filesystem::{Filesystem, FsBase}, config};
use crate::app::gui::{GuiState, style::{UiExt, BACKGROUND, WIDGET_BACKGROUND, WIDGET_HOVERED_BACKGROUND, WIDGET_ACTIVE_BACKGROUND, WIDGET_STROKE, WIDGET_HOVERED_STROKE, WIDGET_ACTIVE_STROKE}};
use crate::world::player::HumanStyle;
use crate::server::level::config::Config;
use crate::utils::{task::Task, directory_traversal, ron};

pub struct PsStoryMode {
	common: Common,
	levels: Option<Box<[Level]>>,
	level_task: Task<Box<[Level]>>,
	clear_progress_confirm_open: bool,
}

struct Level {
	name: Box<str>,
	config: Arc<Config>,
	path: PathBuf,
}

#[derive(Deserialize)]
struct LevelInfo {
	name: Box<str>,
	path: PathBuf,
}

impl PsStoryMode {
	pub fn new(fs: &Filesystem) -> PsStoryMode {
		PsStoryMode {
			common: Common::new(),
			levels: None,
			level_task: Task::run(Self::load_levels(fs.get(FsBase::Static, Path::new("levels")).into_boxed_path())),
			clear_progress_confirm_open: false,
		}
	}

	async fn load_levels(level_dir: Box<Path>) -> Result<Box<[Level]>, String> {
		let info_data = fs::read(&level_dir.join("index.ron")).await.map_err(|err| format!("failed loading level index: {err}"))?;
		let index: Box<[LevelInfo]> = ron::deserialise(&info_data).map_err(|err| format!("failed deserialising level index: {err}"))?;

		let mut levels = Vec::with_capacity(index.len());
		for info in index {
			let path = level_dir.join(directory_traversal::prevent(&info.path)?);
			let level_data = fs::read(&path).await.map_err(|err| format!("failed loading level at \"{}\": {err}", path.display()))?;
			let config = Arc::new(ron::deserialise(&level_data).map_err(|err| format!("failed deserialising level at \"{}\": {err}", path.display()))?);
			levels.push(Level { name: info.name, config, path });
		}
		Ok(levels.into_boxed_slice())
	}
}

impl CommonGs for PsStoryMode {
	fn common(&mut self) -> &mut Common { &mut self.common }

	fn loop_iter(&mut self, app: &mut App, frame: &mut GliumFrame) -> bool {
		if let Some(res) = self.level_task.result() {
			self.levels = Some(res.unwrap_or_else(|err| {
				self.common.set_error(format!("failed loading levels: {err}"));
				Box::new([])
			}));
		}

		if self.clear_progress_confirm_open && self.common.get_gui_state().should_exit() {
			self.clear_progress_confirm_open = false;
		}

		let mut config = app.config.borrow_mut();
		let max_index =
			if config.story_mode.unlock_all_levels { usize::MAX }
			else { config.story_mode.completed_levels.last().map_or(0, |index| index + 1) }; // All levels ≤ this index can be played

		let res = GuiState::update(&mut app.gui, "ps-story-mode", &app.window, |ctx, ui| {
			ui.h1("Story Mode").add();

			let starter = self.common.connection_starter();
			let buttons_enabled = starter.is_some() && !self.clear_progress_confirm_open;
			let mut clicked = None;

			if let Some(levels) = &self.levels {
				for (index, level) in levels.iter().enumerate() {
					if index > 0 {
						ui.add_space(4.0);
					}

					let completed = config.story_mode.completed_levels.contains(&index);
					if completed {
						const INACTIVE_STROKE: Color32 = Color32::from_rgb(0x3f, 0x5f, 0x3f);

						let style = ui.style_mut();

						style.visuals.widgets.noninteractive.weak_bg_fill = Color32::from_rgb(0x17, 0x2f, 0x17);
						style.visuals.widgets.inactive.weak_bg_fill = Color32::from_rgb(0x27, 0x4f, 0x27);
						style.visuals.widgets.hovered.weak_bg_fill = Color32::from_rgb(0x1f, 0x5f, 0x1f);
						style.visuals.widgets.active.weak_bg_fill = Color32::from_rgb(0x1f, 0x6f, 0x1f);

						style.visuals.widgets.noninteractive.bg_stroke.color = INACTIVE_STROKE;
						style.visuals.widgets.inactive.bg_stroke.color = INACTIVE_STROKE;
						style.visuals.widgets.hovered.bg_stroke.color = Color32::from_rgb(0x5f, 0xcf, 0x5f);
						style.visuals.widgets.active.bg_stroke.color = Color32::from_rgb(0x5f, 0xef, 0x5f);
					}

					let response = ui.b2_enabled(&format!("Level {}: {}", index + 1, level.name), buttons_enabled && index <= max_index);
					if response.clicked() {
						clicked = Some((index, level));
					}
					if buttons_enabled {
						response.on_disabled_hover_text("Level is locked. Unlocking this level by completing the previous levels or enabling \"Unlock All Levels\".");
					}

					if completed {
						let style = ui.style_mut();

						style.visuals.widgets.noninteractive.weak_bg_fill = BACKGROUND;
						style.visuals.widgets.inactive.weak_bg_fill = WIDGET_BACKGROUND;
						style.visuals.widgets.hovered.weak_bg_fill = WIDGET_HOVERED_BACKGROUND;
						style.visuals.widgets.active.weak_bg_fill = WIDGET_ACTIVE_BACKGROUND;

						style.visuals.widgets.noninteractive.bg_stroke.color = WIDGET_STROKE;
						style.visuals.widgets.inactive.bg_stroke.color = WIDGET_STROKE;
						style.visuals.widgets.hovered.bg_stroke.color = WIDGET_HOVERED_STROKE;
						style.visuals.widgets.active.bg_stroke.color = WIDGET_ACTIVE_STROKE;
					}
				}

				ui.add_space(32.0);

				config::set_changed!(config, ui.checkbox(&mut config.story_mode.unlock_all_levels, "Unlock All Levels").changed());
				ui.add_space(16.0);
				if ui.b2_enabled("Clear Progress", buttons_enabled).clicked() {
					self.clear_progress_confirm_open = true;
				}

				let mut close = false;
				Window::new("Confirm Clearing Progress")
					.open(&mut self.clear_progress_confirm_open)
					.anchor(Align2::CENTER_CENTER, [0.0, 0.0])
					.collapsible(false)
					.resizable(false)
					.show(ctx, |ui| {
						ui.label("Are you sure you want to clear your progress?");
						ui.add_space(16.0);
						ui.horizontal(|ui| {
							if ui.button("Yes").clicked() {
								config.story_mode.completed_levels.clear();
								config.changed();
								close = true;
							}
							ui.add_space(2.0);
							if ui.button("No").clicked() {
								close = true;
							}
						});
					});

				self.clear_progress_confirm_open &= !close;

				if let Some((index, level)) = clicked && let Some(starter) = starter {
					let style = HumanStyle { name: config.name.to_shared(), colour: config.colour };
					starter.connect_story_mode(Arc::clone(&level.config), level.path.clone(), style, index);
				}
			}

			self.common.finish_update(ctx, ui, self.level_task.running().then_some(CursorIcon::Wait));
		});
		self.common.after_gui_update(res);

		app.gui.render(&app.display, frame);

		false
	}
}
