import { M5Item } from "../items/M5Item";
import { M5Attribute, M5CharacterCalculatedData, M5ItemMod, M5ModOperation, M5ModResult, M5RollData, M5Skill, M5SkillCalculated, M5SkillLearned } from "../M5Base";
import M5ModAggregate from "./M5ModAggregate";

export class M5Character extends Actor {
	// constructor(
	// 	data: ConstructorParameters<typeof foundry.documents.BaseActor>[0],
	// 	context?: ConstructorParameters<typeof foundry.documents.BaseActor>[1]
	// ) {
	// 	super(data, context)
	// 	this.prepareDerivedData()
	// }

	static attributeMinMax(attribute: M5Attribute) {
		return Math.min(100, Math.max(0, attribute.value + attribute.bonus));
	}

	static attributeBonus(attribute: M5Attribute) {
		const value = this.attributeMinMax(attribute);
		if (value > 95) return 2;
		if (value > 80) return 1;
		if (value > 20) return 0;
		if (value > 5) return -1;
		return -2;
	}

	derivedData(
		skip: {
			mods?: boolean;
			skills?: boolean;
			weapons?: boolean;
			defensiveWeapons?: boolean;
			armor?: boolean;
			items?: boolean;
			spells?: boolean;
			effects?: boolean;
			kampfkuenste?: boolean;
		} = {}
	): M5CharacterCalculatedData {
		let ret: M5CharacterCalculatedData = {
			level: 0,
			attributes: {
				st: { value: 0, bonus: 0, mods: [] },
				gs: { value: 0, bonus: 0, mods: [] },
				gw: { value: 0, bonus: 0, mods: [] },
				ko: { value: 0, bonus: 0, mods: [] },
				in: { value: 0, bonus: 0, mods: [] },
				zt: { value: 0, bonus: 0, mods: [] },
				au: { value: 0, bonus: 0, mods: [] },
				pa: { value: 0, bonus: 0, mods: [] },
				wk: { value: 0, bonus: 0, mods: [] },
			},
			stats: {
				lp: { value: 0, mods: [] },
				ap: { value: 0, mods: [] },
				lpProtection: { value: 0, mods: [] },
				apProtection: { value: 0, mods: [] },
				defense: { value: 0, mods: [] },
				damageBonus: { value: 0, mods: [] },
				attackBonus: { value: 0, mods: [] },
				defenseBonus: { value: 0, mods: [] },
				movement: { value: 0, mods: [] },
				resistanceMind: { value: 0, mods: [] },
				resistanceBody: { value: 0, mods: [] },
				spellCasting: { value: 0, mods: [] },
				brawl: { value: 0, mods: [] },
				brawlEw: 0,
				poisonResistance: { value: 0, mods: [] },
				enduranceBonus: 0,
			},
			skillMods: {},
			skills: {
				innate: {},
				general: {},
				combat: {},
				language: {},
				custom: {},
			},
			gear: {
				weapons: {},
				defensiveWeapons: {},
				armor: {},
				items: {},
				effects: {},
			},
			spells: {},
			kampfkuenste: {},
		} as M5CharacterCalculatedData;

		const context = this as any;
		if (!context) return null;

		const data = (this as any).system;
		if (!data) return null;

		ret.level = M5Character.levelFromExp(data.es);

		ret.attributes.st.value = M5Character.attributeMinMax(data.attributes.st); // TODO item effects
		ret.attributes.gs.value = M5Character.attributeMinMax(data.attributes.gs);
		ret.attributes.gw.value = M5Character.attributeMinMax(data.attributes.gw);
		ret.attributes.ko.value = M5Character.attributeMinMax(data.attributes.ko);
		ret.attributes.in.value = M5Character.attributeMinMax(data.attributes.in);
		ret.attributes.zt.value = M5Character.attributeMinMax(data.attributes.zt);
		ret.attributes.au.value = M5Character.attributeMinMax(data.attributes.au);
		ret.attributes.pa.value = M5Character.attributeMinMax(data.attributes.pa);
		ret.attributes.wk.value = M5Character.attributeMinMax(data.attributes.wk);

		ret.attributes.st.bonus = M5Character.attributeBonus(data.attributes.st);
		ret.attributes.gs.bonus = M5Character.attributeBonus(data.attributes.gs);
		ret.attributes.gw.bonus = M5Character.attributeBonus(data.attributes.gw);
		ret.attributes.ko.bonus = M5Character.attributeBonus(data.attributes.ko);
		ret.attributes.in.bonus = M5Character.attributeBonus(data.attributes.in);
		ret.attributes.zt.bonus = M5Character.attributeBonus(data.attributes.zt);
		ret.attributes.au.bonus = M5Character.attributeBonus(data.attributes.au);
		ret.attributes.pa.bonus = M5Character.attributeBonus(data.attributes.pa);
		ret.attributes.wk.bonus = M5Character.attributeBonus(data.attributes.wk);

		ret.stats.lp = this.modResult(data.lp);
		ret.stats.ap = this.modResult(data.ap);
		ret.stats.lpProtection = this.modResult(0);
		ret.stats.apProtection = this.modResult(0);
		ret.stats.defense = this.modResult(M5Character.defenseFromLevel(ret.level));
		ret.stats.damageBonus = this.modResult(Math.floor(ret.attributes.st.value / 20) + Math.floor(ret.attributes.gs.value / 30) - 3);
		ret.stats.attackBonus = this.modResult(ret.attributes.gs.bonus);
		ret.stats.defenseBonus = this.modResult(ret.attributes.gw.bonus);
		ret.stats.movement = this.modResult(data.movement);
		ret.stats.resistanceMind = this.modResult(
			(data.info.magicUsing ? 2 : 0) + ret.stats.defense.value + (data.info.race === "Mensch" ? ret.attributes.in.bonus : this.raceBonus(data.info.race))
		);
		ret.stats.resistanceBody = this.modResult(
			(data.info.magicUsing ? 2 : 1) + ret.stats.defense.value + (data.info.race === "Mensch" ? ret.attributes.ko.bonus : this.raceBonus(data.info.race))
		);
		ret.stats.spellCasting = this.modResult((data.info.magicUsing ? M5Character.spellCastingFromLevel(ret.level) : 3) + ret.attributes.zt.bonus);
		ret.stats.brawl = this.modResult(Math.floor((ret.attributes.st.value + ret.attributes.gw.value) / 20));
		ret.stats.brawlEw = ret.stats.brawl.value + ret.stats.attackBonus.value + (data.info.race === "Zwerg" ? 1 : 0);
		ret.stats.poisonResistance = this.modResult(30 + Math.floor(ret.attributes.ko.value / 2));
		ret.stats.enduranceBonus = Math.floor(ret.attributes.ko.value / 10) + Math.floor(ret.attributes.st.value / 20);

		if (!skip?.mods) {
			const aggregate = new M5ModAggregate(data, ret);

			context.items
				?.filter((item) => (item.type === "item" || item.type === "effect" || item.type === "armor") && item.system.equipped)
				.forEach((item) => {
					const mods = item.system.mods;
					//console.log("Actor item mods", mods)
					Object.keys(mods).forEach((modIndex) => {
						const mod = mods[modIndex] as M5ItemMod;
						aggregate.push(mod, item.name);
					});
				});

			ret.skillMods = aggregate.calculate();
		}

		if (!skip?.items) {
			context.items
				?.filter((item) => item.type === "item")
				.forEach((item) => {
					item.prepareDerivedData();

					let label = item.name;
					if (item.system.magic) {
						label += "*";
					}

					ret.gear.items[item.id] = {
						label: label,
						magic: item.system.magic,
						calc: item.system.calc,
						equipped: item.system?.equipped,
					};
				});
		}

		if (!skip?.items) {
			context.items
				?.filter((item) => item.type === "effect")
				.forEach((item) => {
					item.prepareDerivedData();

					let label = item.name;
					if (item.system.magic) {
						label += "*";
					}

					ret.gear.effects[item.id] = {
						label: label,
						magic: item.system.magic,
						calc: item.system.calc,
						equipped: item.system?.equipped,
					};
				});
		}

		if (!skip?.skills) {
			context.items
				?.filter((item) => item.type === "skill")
				.forEach((item) => {
					item.prepareDerivedData();
					const skillMap = ret.skills[item.system.type];
					skillMap[item.id] = {
						label: item.name,
						fw: item.system.fw,
						attribute: item.system.attribute,
						pp: item.system.pp,
						calc: item.system.calc,
					} as M5SkillCalculated;
				});
		}

		if (!skip?.weapons) {
			context.items
				?.filter((item) => item.type === "weapon")
				.forEach((item) => {
					item.prepareDerivedData();

					let label = item.name;
					if (item.system.magic) {
						label +=
							"*(" +
							(item.system.stats.attackBonus < 0 ? "" : "+") +
							item.system.stats.attackBonus +
							"/" +
							(item.system.stats.damageBonus < 0 ? "" : "+") +
							item.system.stats.damageBonus +
							")";
					}

					ret.gear.weapons[item.id] = {
						label: label,
						skillId: item.system.skillId,
						magic: item.system.magic,
						calc: item.system.calc,
					};
				});
		}

		if (!skip?.defensiveWeapons) {
			context.items
				?.filter((item) => item.type === "defensiveWeapon")
				.forEach((item) => {
					item.prepareDerivedData();

					let label = item.name;
					if (item.system.magic) {
						label += "*(" + (item.system.stats.defenseBonus < 0 ? "" : "+") + item.system.stats.defenseBonus + ")";
					}

					ret.gear.defensiveWeapons[item.id] = {
						label: label,
						skillId: item.system.skillId,
						magic: item.system.magic,
						calc: item.system.calc,
					};
				});
		}

		if (!skip?.armor) {
			context.items
				?.filter((item) => item.type === "armor")
				.forEach((item) => {
					item.prepareDerivedData();

					let label = item.name;
					if (item.system.magic) {
						label += "*";
					}

					ret.gear.armor[item.id] = {
						label: label,
						magic: item.system.magic,
						calc: item.system.calc,
						equipped: item.system?.equipped,
					};
				});
		}

		if (!skip?.spells) {
			context.items
				?.filter((item) => item.type === "spell")
				.forEach((item) => {
					item.prepareDerivedData();

					ret.spells[item.id] = {
						label: item.name,
						process: "midgard5.spell-process-" + item.system.process,
						calc: item.system.calc,
					};
				});
		}

		if (!skip?.kampfkuenste) {
			context.items
				?.filter((item) => item.type === "kampfkunst")
				.forEach((item) => {
					item.prepareDerivedData();

					ret.kampfkuenste[item.id] = {
						label: item.name,
						isKido: item.system.isKido,
						type: item.system.type,
						variante: item.system.variante,
						calc: item.system.calc,
					};
				});
		}

		return ret;
	}

	raceBonus(race: string) {
		switch (race) {
			case "Elf":
				return 2;
			case "Gnom":
				return 4;
			case "Halbling":
				return 4;
			case "Zwerg":
				return 3;
			default:
				return 0;
		}
	}

	prepareDerivedData() {
		console.log("M5Character", "prepareDerivedData");
		const data = (this as any).system;
		data.calc = this.derivedData({});
	}

	override getRollData(): any {
		return {
			c: (this as any).system,
			i: null,
			iType: null,
			rolls: {},
			res: {},
		} as M5RollData;
	}

	static readonly levelThreshold: Array<number> = [
		0, 100, 250, 500, 750, 1000, 1250, 1500, 1750, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 6000, 7000, 8000, 9000, 10000, 12500, 15000, 17500, 20000, 22500, 25000, 30000, 35000, 40000,
		45000, 50000, 55000, 60000, 65000, 70000, 75000, 80000, 85000, 90000, 95000, 100000, 105000, 110000, 115000, 120000, 125000, 130000, 135000, 140000, 145000, 150000, 155000, 160000,
		165000, 170000, 175000, 180000, 185000, 190000, 195000, 200000, 205000, 210000, 215000, 220000, 225000, 230000, 235000, 240000, 245000, 250000, 255000, 260000, 265000, 270000, 275000,
		280000,
	];
	static levelFromExp(exp: number): number {
		const ret = M5Character.levelThreshold.findIndex((val) => val > exp);
		return ret === -1 ? M5Character.levelThreshold.length : ret;
	}

	static readonly defenseThreshold: Array<[number, number]> = [
		[30, 18],
		[25, 17],
		[20, 16],
		[15, 15],
		[10, 14],
		[5, 13],
		[2, 12],
		[1, 11],
	];
	static defenseFromLevel(lvl: number): number {
		const ret = M5Character.defenseThreshold.find((val) => val[0] <= lvl);
		return ret ? ret[1] : M5Character.defenseThreshold[M5Character.defenseThreshold.length - 1][1];
	}

	static readonly spellCastingThreshold: Array<[number, number]> = [
		[20, 18],
		[15, 17],
		[10, 16],
		[8, 15],
		[6, 14],
		[4, 13],
		[2, 12],
		[1, 11],
	];
	static spellCastingFromLevel(lvl: number): number {
		const ret = M5Character.spellCastingThreshold.find((val) => val[0] <= lvl);
		return ret ? ret[1] : M5Character.spellCastingThreshold[M5Character.spellCastingThreshold.length - 1][1];
	}

	skillBonus(skill: M5Skill, skillName?: string) {
		const data = (this as any).system;
		return data.calc?.attributes[skill.attribute]?.bonus ?? 0;
	}

	skillEw(skill: M5Skill, skillName?: string) {
		const bonus = this.skillBonus(skill, skillName);
		return skill.fw + bonus;
	}

	attribute(name: string): M5Attribute {
		const data = (this as any).system;
		return data?.attributes[name];
	}

	createSkill(skillName: string): Promise<M5Item> {
		const itemData = {
			name: skillName,
			type: "skill",
		};

		return (this as any).createEmbeddedDocuments("Item", [itemData]).then((docs) => {
			const item = docs[0];
			return item;
		});
	}

	getItem(itemId: string): any {
		if (!(this as any).items) return null;
		return (this as any).getEmbeddedDocument("Item", itemId);
	}

	private modResult(value: number): M5ModResult {
		return {
			value: value,
			mods: [
				{
					item: (game as Game).i18n.localize("TYPES.Actor.character"),
					operation: M5ModOperation.SET,
					value: value,
				},
			],
		};
	}
}
