import {Config} from "./Config.js";
import {Consts} from "./Consts.js";

class UtilActors {
	static init () {
		UtilActors.VALID_DAMAGE_TYPES = Object.keys(MiscUtil.get(CONFIG, "DND5E", "damageTypes") || {});
		UtilActors.VALID_CONDITIONS = Object.keys(MiscUtil.get(CONFIG, "DND5E", "conditionTypes") || {});
	}

	static doShowSheetItem (evt, actorId, itemId) {
		const actor = CONFIG.Actor.collection.instance.get(actorId);
		if (!actor) return;

		// Alternately, we could show the item with `item.sheet.render(true);` TODO(Future) make this an option
		const item = actor.items.get(itemId);
		if (!item) return;

		return item.roll();
	}

	/**
	 * @param [actor]
	 */
	static getActorSpellItemOpts (actor) {
		const opts = {
			isActorItem: true,
			isPrepared: !!Config.get("importSpell", "prepareActorSpells"),
			preparationMode: Config.get("importSpell", "actorSpellPreparationMode"),
		};

		// Avoid setting these options from an in-progress actor import
		if (!actor || MiscUtil.get(actor, "data", "name") === Consts.ACTOR_TEMP_NAME) return opts;

		const spellcastingAbility = MiscUtil.get(actor, "data", "data", "attributes", "spellcasting");
		if (spellcastingAbility) opts.abilityAbv = spellcastingAbility.value;

		return opts;
	}

	static getSpellItemItemOpts () {
		const opts = {};

		opts.isPrepared = !!Config.get("importSpell", "prepareSpellItems");
		opts.preparationMode = Config.get("importSpell", "spellItemPreparationMode");

		return opts;
	}

	static async pAddActorItems (actor, itemArray, {isTemporary = false} = {}, createEmbeddedDocOpts) {
		if (!itemArray?.length) return [];
		return this._pAddActorEmbeddedDocuments({
			actor,
			embeds: itemArray,
			isTemporary,
			propData: "items",
			ClsEmbed: Item,
			embedName: "Item",
			createEmbeddedDocOpts,
		});
	}

	static async pAddActorEffects (actor, effectArray, {isTemporary = false} = {}) {
		if (!effectArray?.length) return [];
		return this._pAddActorEmbeddedDocuments({
			actor,
			embeds: effectArray,
			isTemporary,
			propData: "effects",
			ClsEmbed: ActiveEffect,
			embedName: "ActiveEffect",
		});
	}

	static async _pAddActorEmbeddedDocuments ({actor, embeds, isTemporary, propData, ClsEmbed, embedName, createEmbeddedDocOpts}) {
		let createdEmbeds;

		if (isTemporary) {
			createdEmbeds = await ClsEmbed.create(embeds, {temporary: true});

			createdEmbeds.forEach(createdEmbed => {
				// Create fake IDs for our temp entities, so we can add them to the actor
				createdEmbed.data._id = createdEmbed.data._id || CryptUtil.uid();
				actor[propData].set(createdEmbed.id, createdEmbed);

				// If we have attached effects, manually attach them to the actor, because (as of 2021-05-02 it doesn't do
				//   this itself...)
				(createdEmbed.effects || []).forEach(effect => {
					effect.data._id = effect.data._id || CryptUtil.uid();
					actor.effects.set(effect.id, effect);
				});
			});
		} else {
			createdEmbeds = await actor.createEmbeddedDocuments(embedName, embeds, {...createEmbeddedDocOpts});
		}

		if (embeds.length !== createdEmbeds.length) throw new Error(`Number of returned items did not match number of input items!`); // Should never occur
		return embeds.map((raw, i) => new UtilActors.ImportedEmbeddedDocument({raw, document: createdEmbeds[i]}))
	}

	static getMappedTool (str) {
		str = str.toLowerCase().trim();
		return this.VALID_TOOL_PROFICIENCIES[str];
	}

	static getUnmappedTool (str) {
		if (!str) return null;
		return Parser._parse_bToA(this.VALID_TOOL_PROFICIENCIES, str, null);
	}

	static getMappedLanguage (str) {
		str = str.toLowerCase().trim();
		return this.VALID_LANGUAGES[str];
	}

	static getMappedCasterType (str) {
		if (!str) return str;
		return this._VET_CASTER_TYPE_TO_FVTT[str];
	}

	static getMappedArmorProficiency (str) {
		if (!str) return null;
		return Parser._parse_aToB(this.VALID_ARMOR_PROFICIENCIES, str, null);
	}

	static getUnmappedArmorProficiency (str) {
		if (!str) return null;
		return Parser._parse_bToA(this.VALID_ARMOR_PROFICIENCIES, str, null);
	}

	static getMappedWeaponProficiency (str) {
		if (!str) return null;
		return Parser._parse_aToB(this.VALID_WEAPON_PROFICIENCIES, str, null);
	}

	static getUnmappedWeaponProficiency (str) {
		if (!str) return null;
		return Parser._parse_bToA(this.VALID_WEAPON_PROFICIENCIES, str, null);
	}

	static getItemUIdFromWeaponProficiency (str) {
		if (!str) return null;
		str = str.trim();
		if (/^{@item [^}]+}$/.exec(str)) return str;
		return Parser._parse_aToB(this._WEAPON_PROFICIENCIES_TO_ITEM_UIDS, str, null);
	}

	/**
	 * Loop over a data model and collect all the possible attributes which could be used in token bars. This is (currently
	 * as of 2020-01-26) what Foundry does to generate the available bar list.
	 *
	 * Based on Foundry's `_getBarAttributes`
	 */
	static getModelBarAttributes (model) {
		function _searchBarAttributes (stack, data, path) {
			for (let [k, v] of Object.entries(data)) {
				const nxtPath = [...path, k];

				if (v instanceof Object) {
					const isBar = Number.isFinite(parseFloat(v.value)) && Number.isFinite(parseFloat(v.max));

					if (isBar) stack.push(nxtPath);
					else _searchBarAttributes(stack, data[k], nxtPath);
				} else if (Number.isFinite(v)) {
					stack.push(nxtPath);
				}
			}
		}

		const stack = [];
		_searchBarAttributes(stack, model, []);
		return stack.map(v => v.join("."));
	}

	/**
	 * Based on the Foundry token editor method `getBarAttributeChoices`
	 * @param actor An actor, although a wrapped model works equally well.
	 */
	static getActorBarAttributes (actor) {
		if (!actor) return [];
		const attributes = TokenConfig.getTrackedAttributes(actor.data.data, []);
		attributes.bar = attributes.bar.map(v => v.join("."));
		attributes.bar.sort((a, b) => a.localeCompare(b));
		attributes.value = attributes.value.map(v => v.join("."));
		attributes.value.sort((a, b) => a.localeCompare(b));
		return {
			[game.i18n.localize("TOKEN.BarAttributes")]: attributes.bar,
			[game.i18n.localize("TOKEN.BarValues")]: attributes.value,
		};
	}

	static getTotalClassLevels (actor) {
		return actor.items
			.filter(it => it.type === "class")
			.map(it => it.data.data.levels || 0)
			.reduce((a, b) => a + b, 0)
	}

	static isLevelUp (actor) {
		let xpCur = Number(actor?.data?.data?.details?.xp?.value);
		if (isNaN(xpCur)) xpCur = 0;

		const lvlTarget = actor.items.filter(it => it.type === "class").map(it => it.data.data.levels || 0).sum();
		let xpMax = Parser.LEVEL_XP_REQUIRED[lvlTarget];
		if (isNaN(xpMax)) xpMax = Number.MAX_SAFE_INTEGER;

		return xpCur >= xpMax;
	}
}
UtilActors.SKILL_ABV_TO_FULL = {
	acr: "acrobatics",
	ani: "animal handling",
	arc: "arcana",
	ath: "athletics",
	dec: "deception",
	his: "history",
	ins: "insight",
	itm: "intimidation",
	inv: "investigation",
	med: "medicine",
	nat: "nature",
	prc: "perception",
	prf: "performance",
	per: "persuasion",
	rel: "religion",
	slt: "sleight of hand",
	ste: "stealth",
	sur: "survival",
};
UtilActors.PROF_TO_ICON_CLASS = {
	"1": "fa-check",
	"2": "fa-check-double",
	"0.5": "fa-adjust",
};
UtilActors.VET_SIZE_TO_ABV = {
	[SZ_TINY]: "tiny",
	[SZ_SMALL]: "sm",
	[SZ_MEDIUM]: "med",
	[SZ_LARGE]: "lg",
	[SZ_HUGE]: "huge",
	[SZ_GARGANTUAN]: "grg",
};
UtilActors.VET_SPELL_SCHOOL_TO_ABV = {
	A: "abj",
	C: "con",
	D: "div",
	E: "enc",
	V: "evo",
	I: "ill",
	N: "nec",
	T: "trs",
};
UtilActors.CASTER_TYPE_TO_PROGRESSION = {
	"full": [
		[2, 0, 0, 0, 0, 0, 0, 0, 0],
		[3, 0, 0, 0, 0, 0, 0, 0, 0],
		[4, 2, 0, 0, 0, 0, 0, 0, 0],
		[4, 3, 0, 0, 0, 0, 0, 0, 0],
		[4, 3, 2, 0, 0, 0, 0, 0, 0],
		[4, 3, 3, 0, 0, 0, 0, 0, 0],
		[4, 3, 3, 1, 0, 0, 0, 0, 0],
		[4, 3, 3, 2, 0, 0, 0, 0, 0],
		[4, 3, 3, 3, 1, 0, 0, 0, 0],
		[4, 3, 3, 3, 2, 0, 0, 0, 0],
		[4, 3, 3, 3, 2, 1, 0, 0, 0],
		[4, 3, 3, 3, 2, 1, 0, 0, 0],
		[4, 3, 3, 3, 2, 1, 1, 0, 0],
		[4, 3, 3, 3, 2, 1, 1, 0, 0],
		[4, 3, 3, 3, 2, 1, 1, 1, 0],
		[4, 3, 3, 3, 2, 1, 1, 1, 0],
		[4, 3, 3, 3, 2, 1, 1, 1, 1],
		[4, 3, 3, 3, 3, 1, 1, 1, 1],
		[4, 3, 3, 3, 3, 2, 1, 1, 1],
		[4, 3, 3, 3, 3, 2, 2, 1, 1],
	],
	"artificer": [
		[2, 0, 0, 0, 0],
		[2, 0, 0, 0, 0],
		[3, 0, 0, 0, 0],
		[3, 0, 0, 0, 0],
		[4, 2, 0, 0, 0],
		[4, 2, 0, 0, 0],
		[4, 3, 0, 0, 0],
		[4, 3, 0, 0, 0],
		[4, 3, 2, 0, 0],
		[4, 3, 2, 0, 0],
		[4, 3, 3, 0, 0],
		[4, 3, 3, 0, 0],
		[4, 3, 3, 1, 0],
		[4, 3, 3, 1, 0],
		[4, 3, 3, 2, 0],
		[4, 3, 3, 2, 0],
		[4, 3, 3, 3, 1],
		[4, 3, 3, 3, 1],
		[4, 3, 3, 3, 2],
		[4, 3, 3, 3, 2],
	],
	"1/2": [
		[0, 0, 0, 0, 0],
		[2, 0, 0, 0, 0],
		[3, 0, 0, 0, 0],
		[3, 0, 0, 0, 0],
		[4, 2, 0, 0, 0],
		[4, 2, 0, 0, 0],
		[4, 3, 0, 0, 0],
		[4, 3, 0, 0, 0],
		[4, 3, 2, 0, 0],
		[4, 3, 2, 0, 0],
		[4, 3, 3, 0, 0],
		[4, 3, 3, 0, 0],
		[4, 3, 3, 1, 0],
		[4, 3, 3, 1, 0],
		[4, 3, 3, 2, 0],
		[4, 3, 3, 2, 0],
		[4, 3, 3, 3, 1],
		[4, 3, 3, 3, 1],
		[4, 3, 3, 3, 2],
		[4, 3, 3, 3, 2],
	],
	"1/3": [
		[0, 0, 0, 0],
		[0, 0, 0, 0],
		[2, 0, 0, 0],
		[3, 0, 0, 0],
		[3, 0, 0, 0],
		[3, 0, 0, 0],
		[4, 2, 0, 0],
		[4, 2, 0, 0],
		[4, 2, 0, 0],
		[4, 3, 0, 0],
		[4, 3, 0, 0],
		[4, 3, 0, 0],
		[4, 3, 2, 0],
		[4, 3, 2, 0],
		[4, 3, 2, 0],
		[4, 3, 3, 0],
		[4, 3, 3, 0],
		[4, 3, 3, 0],
		[4, 3, 3, 1],
		[4, 3, 3, 1],
	],
	"pact": [
		[1, 0, 0, 0, 0],
		[2, 0, 0, 0, 0],
		[0, 2, 0, 0, 0],
		[0, 2, 0, 0, 0],
		[0, 0, 2, 0, 0],
		[0, 0, 2, 0, 0],
		[0, 0, 0, 2, 0],
		[0, 0, 0, 2, 0],
		[0, 0, 0, 0, 2],
		[0, 0, 0, 0, 2],
		[0, 0, 0, 0, 3],
		[0, 0, 0, 0, 3],
		[0, 0, 0, 0, 3],
		[0, 0, 0, 0, 3],
		[0, 0, 0, 0, 3],
		[0, 0, 0, 0, 3],
		[0, 0, 0, 0, 4],
		[0, 0, 0, 0, 4],
		[0, 0, 0, 0, 4],
		[0, 0, 0, 0, 4],
	],
};

UtilActors.PACT_CASTER_MAX_SPELL_LEVEL = 5;

UtilActors.VALID_DAMAGE_TYPES = null;
UtilActors.VALID_CONDITIONS = null;

// Taken from 5etools' JSON schema
UtilActors.TOOL_PROFICIENCIES = [
	"alchemist's supplies",
	"artisan's tools",
	"cartographer's tools",
	"disguise kit",
	"forgery kit",
	"gaming set",
	"herbalism kit",
	"musical instrument",
	"navigator's tools",
	"thieves' tools",
	"vehicles (land)",
	"vehicles (water)",
];
UtilActors.VALID_TOOL_PROFICIENCIES = {
	"artisan's tools": "art",
	// TODO(Future): map these when Foundry has better support
	// "alchemist's supplies": "art",
	// "brewer's supplies": "art",
	// "calligrapher's supplies": "art",
	// "carpenter's tools": "art",
	// "cartographer's tools": "art",
	// "cobbler's tools": "art",
	// "cook's utensils": "art",
	// "glassblower's tools": "art",
	// "jeweler's tools": "art",
	// "leatherworker's tools": "art",
	// "mason's tools": "art",
	// "painter's supplies": "art",
	// "potter's tools": "art",
	// "smith's tools": "art",
	// "tinker's tools": "art",
	// "weaver's tools": "art",
	// "woodcarver's tools": "art",

	"disguise kit": "disg",

	"forgery kit": "forg",

	"gaming set": "game",
	// TODO(Future): map these when Foundry has better support
	// "dice set": "game",
	// "dragonchess set": "game",
	// "playing card set": "game",
	// "three-dragon ante set": "game",

	"herbalism kit": "herb",

	"musical instrument": "music",
	// TODO(Future): map these when Foundry has better support
	// "bagpipes": "music",
	// "drum": "music",
	// "dulcimer": "music",
	// "flute": "music",
	// "lute": "music",
	// "lyre": "music",
	// "horn": "music",
	// "pan flute": "music",
	// "shawm": "music",
	// "viol": "music",

	"navigator's tools": "navg",

	"poisoner's kit": "pois",

	"thieves' tools": "thief",

	"vehicle (land or water)": "vehicle",
	// TODO(Future): map these when Foundry has better support
	// "vehicle (land)": "vehicle",
	// "vehicle (water)": "vehicle",
};
UtilActors.VALID_LANGUAGES = {
	"common": "common",
	"aarakocra": "aarakocra",
	"abyssal": "abyssal",
	"aquan": "aquan",
	"auran": "auran",
	"celestial": "celestial",
	"deep speech": "deep",
	"draconic": "draconic",
	"druidic": "druidic",
	"dwarvish": "dwarvish",
	"elvish": "elvish",
	"giant": "giant",
	"gith": "gith",
	"gnomish": "gnomish",
	"goblin": "goblin",
	"gnoll": "gnoll",
	"halfling": "halfling",
	"ignan": "ignan",
	"infernal": "infernal",
	"orc": "orc",
	"primordial": "primordial",
	"sylvan": "sylvan",
	"terran": "terran",
	"thieves' cant": "cant",
	"undercommon": "undercommon",
};
UtilActors._VET_CASTER_TYPE_TO_FVTT = {
	"full": "full",
	"1/2": "half",
	"1/3": "third",
	"pact": "pact",
};
// Taken from 5etools' JSON schema
UtilActors.ARMOR_PROFICIENCIES = [
	"light",
	"medium",
	"heavy",
	"shields",
];
UtilActors.VALID_ARMOR_PROFICIENCIES = {
	"light": "lgt",
	"medium": "med",
	"heavy": "hvy",
	"shields": "shl",
};
UtilActors.WEAPON_PROFICIENCIES = [
	"battleaxe|phb",
	"club|phb",
	"dagger|phb",
	"flail|phb",
	"glaive|phb",
	"greataxe|phb",
	"greatclub|phb",
	"greatsword|phb",
	"halberd|phb",
	"handaxe|phb",
	"javelin|phb",
	"lance|phb",
	"light hammer|phb",
	"longsword|phb",
	"mace|phb",
	"maul|phb",
	"morningstar|phb",
	"pike|phb",
	"quarterstaff|phb",
	"rapier|phb",
	"scimitar|phb",
	"shortsword|phb",
	"sickle|phb",
	"spear|phb",
	"staff|phb",
	"trident|phb",
	"war pick|phb",
	"warhammer|phb",
	"whip|phb",
	"blowgun|phb",
	"dart|phb",
	"hand crossbow|phb",
	"heavy crossbow|phb",
	"light crossbow|phb",
	"longbow|phb",
	"net|phb",
	"shortbow|phb",
	"sling|phb",
];
UtilActors.VALID_WEAPON_PROFICIENCIES = {
	"simple": "sim",
	"martial": "mar",
};
UtilActors._WEAPON_PROFICIENCIES_TO_ITEM_UIDS = {
	// region Plural
	"battleaxes": "battleaxe|phb",
	"clubs": "club|phb",
	"daggers": "dagger|phb",
	"flails": "flail|phb",
	"glaives": "glaive|phb",
	"greataxes": "greataxe|phb",
	"greatclubs": "greatclub|phb",
	"greatswords": "greatsword|phb",
	"halberds": "halberd|phb",
	"handaxes": "handaxe|phb",
	"javelins": "javelin|phb",
	"lances": "lance|phb",
	"light hammers": "light hammer|phb",
	"longswords": "longsword|phb",
	"maces": "mace|phb",
	"mauls": "maul|phb",
	"morningstars": "morningstar|phb",
	"pikes": "pike|phb",
	"quarterstaffs": "quarterstaff|phb",
	"rapiers": "rapier|phb",
	"scimitars": "scimitar|phb",
	"shortswords": "shortsword|phb",
	"sickles": "sickle|phb",
	"spears": "spear|phb",
	"staffs": "staff|phb",
	"tridents": "trident|phb",
	"war picks": "war pick|phb",
	"warhammers": "warhammer|phb",
	"whips": "whip|phb",

	"blowguns": "blowgun|phb",
	"darts": "dart|phb",
	"hand crossbows": "hand crossbow|phb",
	"heavy crossbows": "heavy crossbow|phb",
	"light crossbows": "light crossbow|phb",
	"longbows": "longbow|phb",
	"nets": "net|phb",
	"shortbows": "shortbow|phb",
	"slings": "sling|phb",
	// endregion

	// region Single
	"battleaxe": "battleaxe|phb",
	"club": "club|phb",
	"dagger": "dagger|phb",
	"flail": "flail|phb",
	"glaive": "glaive|phb",
	"greataxe": "greataxe|phb",
	"greatclub": "greatclub|phb",
	"greatsword": "greatsword|phb",
	"halberd": "halberd|phb",
	"handaxe": "handaxe|phb",
	"javelin": "javelin|phb",
	"lance": "lance|phb",
	"light hammer": "light hammer|phb",
	"longsword": "longsword|phb",
	"mace": "mace|phb",
	"maul": "maul|phb",
	"morningstar": "morningstar|phb",
	"pike": "pike|phb",
	"quarterstaff": "quarterstaff|phb",
	"rapier": "rapier|phb",
	"scimitar": "scimitar|phb",
	"shortsword": "shortsword|phb",
	"sickle": "sickle|phb",
	"spear": "spear|phb",
	"staff": "staff|phb",
	"trident": "trident|phb",
	"war pick": "war pick|phb",
	"warhammer": "warhammer|phb",
	"whip": "whip|phb",

	"blowgun": "blowgun|phb",
	"dart": "dart|phb",
	"hand crossbow": "hand crossbow|phb",
	"heavy crossbow": "heavy crossbow|phb",
	"light crossbow": "light crossbow|phb",
	"longbow": "longbow|phb",
	"net": "net|phb",
	"shortbow": "shortbow|phb",
	"sling": "sling|phb",
	// endregion
};
UtilActors.WEAPONS_MARTIAL = [
	"battleaxe|phb",
	"blowgun|phb",
	"flail|phb",
	"glaive|phb",
	"greataxe|phb",
	"greatsword|phb",
	"halberd|phb",
	"hand crossbow|phb",
	"heavy crossbow|phb",
	"lance|phb",
	"longbow|phb",
	"longsword|phb",
	"maul|phb",
	"morningstar|phb",
	"net|phb",
	"pike|phb",
	"rapier|phb",
	"scimitar|phb",
	"shortsword|phb",
	"trident|phb",
	"war pick|phb",
	"warhammer|phb",
	"whip|phb",
];
UtilActors.WEAPONS_SIMPLE = [
	"club|phb",
	"dagger|phb",
	"dart|phb",
	"greatclub|phb",
	"handaxe|phb",
	"javelin|phb",
	"light crossbow|phb",
	"light hammer|phb",
	"mace|phb",
	"quarterstaff|phb",
	"shortbow|phb",
	"sickle|phb",
	"sling|phb",
	"spear|phb",
];

UtilActors.ImportedEmbeddedDocument = class {
	constructor ({raw, document}) {
		this.raw = raw;
		this.document = document;
	}
}

UtilActors.BG_SKILL_PROFS_CUSTOMIZE = [
	{
		choose: {
			from: Object.keys(Parser.SKILL_TO_ATB_ABV),
			count: 2,
		},
	},
];

UtilActors.LANG_TOOL_PROFS_CUSTOMIZE = [
	{
		anyStandard: 2,
	},
];

export {UtilActors};
