use std::collections::HashMap;

use itertools::Itertools;
use rinja::Template;
use serde::Deserialize;

use crate::create_file;

#[derive(Debug, Deserialize, PartialEq, Eq)]
enum Character {
	#[serde(alias = "A")]
	Aqua,
	#[serde(alias = "V")]
	Ventus,
	#[serde(alias = "T")]
	Terra,
}

#[derive(Debug, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct Ability {
	name: String,
	from: String,
	category: String,
	max: u8,
	types: Vec<char>,
}

#[derive(Debug, Deserialize)]
struct Command {
	name: String,
	category: String,
	char: Vec<char>,
	#[serde(default)]
	starting: Option<bool>,
	#[serde(default)]
	info: Option<String>,
	#[serde(default)]
	recipes: Vec<CommandRecipe>,
}

impl Command {}

#[derive(Debug, Deserialize)]
struct CommandRecipe {
	char: Vec<Character>,
	r#type: char,
	ingredients: (String, String),
	chance: u8,
	#[serde(skip_deserializing)]
	abilities: Vec<(String, Ability)>,
}

impl CommandRecipe {
	pub fn get_ability(&self, crystal: &str) -> Option<Ability> {
		if self.r#type == '-' {
			return None;
		}

		for ability in self.abilities.iter() {
			if ability.0 == crystal {
				return Some(ability.1.clone());
			}
		}

		// This should never happen unless the json files are wrong
		panic!(
			"No ability found for {} + {} and {}",
			self.ingredients.0, self.ingredients.1, crystal
		);
	}

	pub fn can_unlock(&self, char: Character) -> bool {
		self.char.contains(&char)
	}

	pub fn get_unlock_chars(&self) -> String {
		let mut id = String::new();
		if self.can_unlock(Character::Terra) {
			id += "T"
		}

		if self.can_unlock(Character::Ventus) {
			if !id.is_empty() {
				id += " ";
			}
			id += "V";
		}

		if self.can_unlock(Character::Aqua) {
			if !id.is_empty() {
				id += " ";
			}
			id += "A";
		}

		id
	}

	pub fn set_abilities(&mut self, abilities: &[Ability]) {
		let mut vec: Vec<(String, Ability)> = vec![];

		for ability in abilities.iter() {
			if ability.types.contains(&self.r#type) {
				vec.push((ability.from.clone(), ability.clone()));
			}
		}
		vec.sort();
		self.abilities = vec;
	}
}

#[derive(Debug, Deserialize)]
struct Finisher {
	name: String,
	char: Vec<Character>,
	level: u8,
	#[serde(default)]
	follows: Vec<String>,
	goal: String,
	color: String,
}

#[derive(Template)]
#[template(path = "pages/bbs/melding.html")]
struct CommandsTemplate {
	pub commands: Vec<Command>,
	pub crystals: Vec<String>,
}

const ABILITIES_PATH: &str = "./input/bbs/abilities.json";
const FINISHERS_PATH: &str = "./input/bbs/finish-commands.json";
const COMMANDS_PATH: &str = "./input/bbs/commands.json";

pub fn init() {
	tracing::info!("Loading abilities data from {}", ABILITIES_PATH);
	let abilities_str = std::fs::read_to_string(ABILITIES_PATH).unwrap();
	let abilities = serde_json::from_str::<Vec<Ability>>(&abilities_str).unwrap();

	tracing::info!("Loading finishers data from {}", ABILITIES_PATH);
	let finishers_str = std::fs::read_to_string(FINISHERS_PATH).unwrap();
	let finishers = serde_json::from_str::<HashMap<String, Finisher>>(&finishers_str).unwrap();

	tracing::info!("Loading commands data from {}", ABILITIES_PATH);
	let commands_str = std::fs::read_to_string(COMMANDS_PATH).unwrap();
	let mut commands = serde_json::from_str::<Vec<Command>>(&commands_str).unwrap();

	// Create a vec with all the crystal variants found in abilities
	let crystals = abilities
		.iter()
		.map(|x| x.from.clone())
		.unique()
		.sorted()
		.collect();

	// Create a vec of crystals and what ability they give for each recipe
	for cmd in commands.iter_mut() {
		for recipe in cmd.recipes.iter_mut() {
			recipe.set_abilities(&abilities);
		}
	}

	tracing::info!("Generating the BBS melding table template");
	let melding_template = CommandsTemplate { commands, crystals };

	create_file("./out/bbs", "melding", melding_template.render().unwrap()).unwrap();
}
