import { Evaluated } from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/client/dice/roll";
import { M5Character } from "../actors/M5Character";
import { M5EwResult, M5RollData, M5RollResult, M5SkillUnlearned } from "../M5Base";

export class M5Roll {
	// extends Roll<M5RollData>
	static readonly TEMPLATE_PATH = "systems/midgard5/templates/chat/roll-m5.hbs";

	public _evaluated: boolean = false;
	public _total: number = 0;
	public pool: PoolTerm = null;

	constructor(public data: M5RollData, public actor: any, public label: string) {
		//super(null)
		//this.data = rollData
	}

	// @ts-ignore
	//override evaluate(options?: InexactPartial<RollTerm.EvaluationOptions>): Evaluated<Roll<M5RollData>> | Promise<Evaluated<Roll<M5RollData>>> {
	evaluate() {
		const indexMap = new Map<number, string>();
		const rollNames = Object.keys(this.data.rolls);
		const rolls = rollNames
			.filter((rollName) => this.data.rolls[rollName].enabled)
			.map((rollName, index) => {
				indexMap.set(index, rollName);
				const formula = this.data.rolls[rollName];
				const roll = new Roll(formula.formula, this.data);
				return roll;
			});

		this.pool = PoolTerm.fromRolls(rolls);
		console.log("evaluate", this._evaluated, this.pool);
		return this.pool.evaluate({ async: true }).then((results) => {
			this._total = 0;

			results.rolls.forEach((roll, index) => {
				const rollIndex = indexMap.get(index);
				const rollResult = this.data.rolls[rollIndex] as M5RollResult;

				rollResult.result = roll.result;
				rollResult.total = roll.total;
				rollResult.totalStr = roll.total.toString();

				this._total += roll.total;

				let rowRes = M5EwResult.TBD;
				let face100 = -1;

				roll.dice.forEach((d, dIndex) => {
					rollResult.dice[dIndex.toString()] = d.total;

					if (rowRes === M5EwResult.TBD && dIndex === 0) {
						if (d.faces === 20) {
							//if (rollResult.type === "ew") {
							if (d.total === 1) rowRes = M5EwResult.FUMBLE;
							else if (d.total === 20) rowRes = M5EwResult.CRITICAL;
							else if (d.total >= 16) rowRes = M5EwResult.HIGH;
						} else if (d.faces === 100) {
							face100 = d.total as number;
						}
					}
				});

				const parseResult = M5Roll.parseDiceSides(rollResult.formula);
				//console.log("evaluate roll", parseResult)
				if (parseResult?.sides === 20) {
					if (roll.total < 20) {
						if (rowRes === M5EwResult.TBD || rowRes === M5EwResult.HIGH) rowRes = M5EwResult.FAIL;
					} else {
						if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.PASS;
					}
				} else if (face100 >= 0) {
					const threshold100 = roll.total + face100;
					const threshold = Math.floor(threshold100 / 10);
					if (face100 === 100) {
						if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.FUMBLE;
					} else if (roll.total < 0) {
						if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.FAIL;
					} else if (face100 <= threshold) {
						if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.CRITICAL;
					} else {
						if (rowRes === M5EwResult.TBD) rowRes = M5EwResult.PASS;
					}
				}
				rollResult.css = rowRes;
			});

			this.data.res.label = this.label;

			this._evaluated = true;
			return this;
		});
	}

	async render(): Promise<string> {
		return renderTemplate(M5Roll.TEMPLATE_PATH, this.data);
	}

	async toMessage() {
		if (!this._evaluated) await this.evaluate();

		const rMode = (game as Game).settings.get("core", "rollMode");

		const chatData = {
			type: CONST.CHAT_MESSAGE_TYPES.ROLL,
			content: await this.render(),
			speaker: ChatMessage.getSpeaker({ actor: this.actor }),
			sound: CONFIG.sounds.dice,
			roll: Roll.fromTerms([this.pool]),
		};

		ChatMessage.applyRollMode(chatData, rMode);
		return ChatMessage.create(chatData);
	}

	static fromAttribute(actor: any, attributeKey: string) {
		const character = actor as M5Character;
		const attribute = character.attribute(attributeKey);

		const rollData = actor.getRollData() as M5RollData;
		rollData.i = attribute.value + attribute.bonus;
		rollData.rolls["0"] = {
			formula: "@i - 1d100",
			enabled: true,
			label: (game as Game).i18n.localize("midgard5.pw"),
			result: "",
			total: 0,
			totalStr: "",
			dice: {},
			css: "",
		} as M5RollResult;

		return new M5Roll(rollData, actor, (game as Game).i18n.localize(`midgard5.actor-${attributeKey}-long`));
	}

	static fromAttributeValue(actor: any, attributeKey: string, attributeValue: number) {
		const rollData = actor.getRollData() as M5RollData;
		rollData.i = attributeValue;
		rollData.rolls["0"] = {
			formula: "@i - 1d100",
			enabled: true,
			label: (game as Game).i18n.localize("midgard5.pw"),
			result: "",
			total: 0,
			totalStr: "",
			dice: {},
			css: "",
		} as M5RollResult;

		return new M5Roll(rollData, actor, (game as Game).i18n.localize(`midgard5.actor-${attributeKey}-long`));
	}

	static fromUnlearnedSkill(actor: any, skill: M5SkillUnlearned, skillName: string) {
		const rollData = actor.getRollData() as M5RollData;
		rollData.i = {
			fw: skill.fw,
			bonus: actor.system.calc?.attributes[skill.attribute]?.bonus ?? 0,
		};
		rollData.iType = "skill";

		rollData.rolls["0"] = {
			formula: "1d20 + @i.fw + @i.bonus",
			enabled: true,
			label: (game as Game).i18n.localize("midgard5.pw"),
			result: "",
			total: 0,
			totalStr: "",
			dice: {},
			css: "",
		} as M5RollResult;

		return new M5Roll(rollData, actor, (game as Game).i18n.localize(`midgard5.${skillName}`));
	}

	static brawl(actor: any) {
		const rollData = actor.getRollData() as M5RollData;
		rollData.i = {
			attackBonus: 0,
			damageBonus: 0,
		};

		rollData.rolls["0"] = {
			formula: "1d20 + @c.calc.stats.brawlEw",
			enabled: true,
			label: (game as Game).i18n.localize("midgard5.attack"),
			result: "",
			total: 0,
			totalStr: "",
			dice: {},
			css: "",
		} as M5RollResult;

		rollData.rolls["1"] = {
			formula: "1d6 - 4 + @c.calc.stats.damageBonus.value",
			enabled: true,
			label: (game as Game).i18n.localize("midgard5.damage"),
			result: "",
			total: 0,
			totalStr: "",
			dice: {},
			css: "",
		} as M5RollResult;

		return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.brawl"));
	}

	static defense(actor: any) {
		const rollData = actor.getRollData() as M5RollData;
		rollData.i = {
			defenseBonus: 0,
		};

		rollData.rolls["0"] = {
			formula: "1d20 + @c.calc.stats.defense.value + @c.calc.stats.defenseBonus.value",
			enabled: true,
			label: (game as Game).i18n.localize("midgard5.defense"),
			result: "",
			total: 0,
			totalStr: "",
			dice: {},
			css: "",
		} as M5RollResult;

		return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.defense"));
	}

	static resistanceMind(actor: any) {
		const rollData = actor.getRollData() as M5RollData;
		rollData.i = {
			defenseBonus: 0,
		};

		rollData.rolls["0"] = {
			formula: "1d20 + @c.calc.stats.resistanceMind.value",
			enabled: true,
			label: (game as Game).i18n.localize("midgard5.resistanceMind"),
			result: "",
			total: 0,
			totalStr: "",
			dice: {},
			css: "",
		} as M5RollResult;

		return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.resistanceMind"));
	}

	static async initativeRoll(actor: any) {
		let newInitiative; // the initiative value for the combatant

		// ...
		// The intnitiative value is calculated here
		// ...

		let combatant = game["combat"].getCombatantByActor(actor._id);
		if (!combatant) {
			await game["combat"].createEmbeddedDocuments("Combatant", [{ actorId: actor._id }]);
			combatant = game["combat"].getCombatantByActor(actor._id);
		}

		const initiatives = {
			_id: combatant._id,
			initiative: actor.system.calc.attributes.gw.value,
		};

		await game["combat"].updateEmbeddedDocuments("Combatant", [initiatives]);
	}

	static resistanceBody(actor: any) {
		const rollData = actor.getRollData() as M5RollData;
		rollData.i = {
			defenseBonus: 0,
		};

		rollData.rolls["0"] = {
			formula: "1d20 + @c.calc.stats.resistanceBody.value",
			enabled: true,
			label: (game as Game).i18n.localize("midgard5.resistanceBody"),
			result: "",
			total: 0,
			totalStr: "",
			dice: {},
			css: "",
		} as M5RollResult;

		return new M5Roll(rollData, actor, (game as Game).i18n.localize("midgard5.resistanceBody"));
	}

	static parseDiceSides(formula: string): FormulaParseResult {
		const ewMatcher: RegExp = /\d*[dD]20/g;
		const pwMatcher: RegExp = /(\d+)\s*\-\s*\d*[dD]100/g;

		let res = formula.match(ewMatcher);
		if (res && !!res[0]) {
			return {
				sides: 20,
				type: "ew",
				threshold: null,
			};
		}

		res = formula.match(pwMatcher);
		if (res && !!res[1]) {
			return {
				sides: 100,
				type: "pw",
				threshold: parseInt(res[1]),
			};
		}

		return null;
	}
}

interface FormulaParseResult {
	sides: number;
	type: string;
	threshold: number;
}
