import { GameSystemConfig, MODULE_ID, SystemString, allAttackTypes, debugEnabled, error, geti18nOptions, i18n, i18nFormat, warn } from "../../midi-qol.js";
import { socketlibSocket } from "../GMAction.js";
import { ActiveDefenceWorkflow, Workflow } from "../Workflow.js";
import { OnUseMacros } from "../apps/Item.js";
import { TroubleShooter } from "../apps/TroubleShooter.js";
import { checkMechanic, checkRule, configSettings, safeGetGameSetting, targetConfirmation } from "../settings.js";
import { installedModules } from "../setupModules.js";
import { busyWait } from "../tests/setupTest.js";
import { saveUndoData } from "../undo.js";
import { activityHasAreaTarget, asyncHooksCall, canSee, canSense, checkActivityRange, checkIncapacitated, createConditionData, displayDSNForRoll, evalActivationCondition, evalCondition, getAutoRollAttack, getAutoRollDamage, getRemoveAttackButtons, getRemoveDamageButtons, getSpeaker, getToken, activityHasAutoPlaceTemplate, hasUsedBonusAction, hasUsedReaction, initializeVision, autoConsumeResource, isInCombat, logIncapacitatedCheckResult, needsBonusActionCheck, needsReactionCheck, processDamageRollBonusFlags, setBonusActionUsed, setReactionUsed, sumRolls, tokenForActor, getOrCreateTokenForActor, validTargetTokens, activityHasEmanationNoTemplate, getActivityAutoTargetAction, areMidiKeysPressed, getActor, setRangedTargets, updateUserTargets, isValidTarget, needsAOOCheck, addRollTo } from "../utils.js";
import { preTemplateTargets, removeFlanking, selectTargets, setDamageRollMinTerms } from "./activityHelpers.js";
export class MidiConditionField extends globalThis.dnd5e.dataModels.fields.FormulaField {
	_validateType(value) {
		return typeof value === "string";
	}
	constructor(options) {
		super(options);
	}
	static get _defaults() {
		return foundry.utils.mergeObject(super._defaults, {
			deterministic: false
		});
	}
}
export var MidiActivityMixin = Base => {
	return class MidiActivityMixin extends Base {
		get workflow() {
			warn("MidiActivityMixin | activity.workflow is deprecated. activities no long store workflow references");
			return Workflow.getWorkflowByActivityUuid(this.uuid);
		}
		set workflow(value) {
			error("MidiActivityMixin | activity.workflow = is deprecated. activities no long store workflow references");
			``;
		}
		get targets() {
			foundry.utils.logCompatibilityWarning("MidiActivityMixin | activity.targets is deprecated. Use workflow.targets instead");
			return Workflow.getWorkflowByActivityUuid(this.uuid)?.targets ?? new Set();
		}
		set targets(value) {
			foundry.utils.logCompatibilityWarning("MidiActivityMixin | setting activity.targets is not supported. Use workflow.targets instead");
		}
		static LOCALIZATION_PREFIXES = ["midi-qol.SHARED", ...super.LOCALIZATION_PREFIXES];
		static defineSchema() {
			const { StringField, ArrayField, BooleanField, SchemaField, ObjectField, SetField } = foundry.data.fields;
			const { FormulaField } = globalThis.dnd5e.dataModels.fields;
			const schema = {
				...super.defineSchema(),
				// flags: new ObjectField(),
				useConditionText: new MidiConditionField({ name: "useCondition", initial: "" }),
				useConditionReason: new StringField({ name: "useConditionReason", initial: "" }),
				effectConditionText: new MidiConditionField({ name: "effectCondition", initial: "" }),
				// disabled pending a way to make it work 
				// useSystemActivity: new BooleanField({ name: "useSystemActivity", initial: false }),
				macroData: new SchemaField({
					name: new StringField({ name: "name", initial: "" }),
					command: new StringField({ name: "command", initial: "" }),
				}),
				ignoreTraits: new SchemaField({
					idi: new BooleanField({ name: "idi", initial: false }),
					idr: new BooleanField({ name: "idr", initial: false }),
					idv: new BooleanField({ name: "idv", initial: false }),
					ida: new BooleanField({ name: "ida", initial: false })
				}),
				midiProperties: new SchemaField({
					ignoreTraits: new SetField(new StringField(), { initial: [] }),
					triggeredActivityId: new StringField({ name: "triggeredActivity", initial: "none" }),
					//@ts-expect-error
					triggeredActivityConditionText: new MidiConditionField({ name: "triggeredActivityCondition", initial: "" }),
					triggeredActivityTargets: new StringField({ name: "triggeredActivityTargets", initial: "targets" }),
					triggeredActivityRollAs: new StringField({ name: "triggeredActivityRollAs", initial: "self" }),
					forceDialog: new BooleanField({ name: "forceDialog", initial: false }),
					confirmTargets: new StringField({ name: "confirmTargets", initial: "default" }),
					autoTargetType: new StringField({ name: "autoTargetType", initial: "any" }),
					autoTargetAction: new StringField({ name: "autoTargetAction", initial: "default" }),
					automationOnly: new BooleanField({ name: "automationOnly", initial: false }),
					otherActivityCompatible: new BooleanField({ name: "otherActivityCompatible", initial: true }),
					identifier: new StringField({ name: "identifier", initial: "", required: false }),
					displayActivityName: new BooleanField({ name: "displayActivityName", initial: false }),
					rollMode: new StringField({ name: "rollMode", initial: "default" }),
					chooseEffects: new BooleanField({ name: "chooseEffects", initial: false }),
					toggleEffect: new BooleanField({ name: "toggleEffects", initial: false }),
					ignoreFullCover: new BooleanField({ name: "ignoreFullCover", initial: false }),
				}),
				isOverTimeFlag: new BooleanField({ name: "isOverTimeFlag", initial: false }),
				overTimeProperties: new SchemaField({
					turnChoice: new StringField({ blank: false, choices: ["start", "end"], default: "start" }),
					saveRemoves: new BooleanField({ initial: true }),
					//@ts-expect-error find out why there is a type error here
					preRemoveConditionText: new MidiConditionField({ name: "removeCondition", initial: "" }),
					//@ts-expect-error find out why there is a type error here
					postRemoveConditionText: new MidiConditionField({ name: "postRemoveCondition", initial: "" }),
				})
			};
			return schema;
		}
		/**
		* @type {boolean}
		* @memberof MidiActivityMixin
		* @readonly
		* @returns {boolean}
		* @description Is this activity suitable as an other activity. It must be a possible other activity (default false) and the otherActivityCompatible flag must be set to true
		*/
		get isOtherActivityCompatible() {
			if (!this.possibleOtherActivity)
				return false;
			return this.midiProperties.otherActivityCompatible;
		}
		/**
		* @type {boolean}
		* @memberof MidiActivityMixin
		* @readonly
		* @returns {boolean}
		* @description Is this activity suitable as a triggerable activity. Default is true and must be overridden in the subclass
		* Examples of non triggerable activities are the Enchant activity
		*/
		get isTriggerableActivity() {
			return true;
		}
		/**
		* @type {boolean}
		* @memberof MidiActivityMixin
		* @readonly
		* @returns {boolean}
		* @description Is this activity only triggerable by the actor that owns it.
		* Default is false and must be overridden in the subclass
		* Examples of self triggerOnly are the Cast and Forward activities
		*/
		get selfOnlyTriggerActivity() {
			return false;
		}
		get forcedTargetConfirmation() {
			return undefined;
		}
		/**
		* @type {boolean}
		* @memberof MidiActivityMixin
		* @readonly
		* @returns {boolean}
		* @description Is this activity a candidate to be used as an "other" activity. Default is false and must be overridden in the subclass
		*/
		get possibleOtherActivity() {
			return false;
		}
		get canUseOtherActivity() {
			return false;
		}
		getOnUseMacros({ onlyOnUseItemMacros = false } = {}) {
			const onUseMacros = new OnUseMacros();
			this.ammunitionOnUseMacros = new OnUseMacros();
			const itemOnUseMacros = foundry.utils.getProperty(this.item ?? {}, `flags.${MODULE_ID}.onUseMacroParts`) ?? new OnUseMacros();
			const ammunitionOnUseMacros = foundry.utils.getProperty(this.ammunitionItem ?? {}, `flags.${MODULE_ID}.onUseMacroParts`) ?? new OnUseMacros();
			const actorOnUseMacros = foundry.utils.getProperty(this.actor ?? {}, `flags.${MODULE_ID}.onUseMacroParts`) ?? new OnUseMacros();
			if (onlyOnUseItemMacros) {
				onUseMacros.items = [...itemOnUseMacros.items];
			}
			else {
				onUseMacros.items = [...itemOnUseMacros.items, ...actorOnUseMacros.items];
			}
			this.ammunitionOnUseMacros.items = ammunitionOnUseMacros.items;
			return onUseMacros;
		}
		get validProperties() {
			return new Set(["idi", "idr", "idv", "ida"]);
		}
		get macro() {
			return new Macro({ name: this.macroData.name || this.name, command: this.macroData.command, img: this.img, type: "script" });
		}
		set macro(macro) {
			//@ts-expect-error
			return this.update({ macroData: { name: macro.name, command: macro.command } });
		}
		get messageFlags() {
			const baseFlags = super.messageFlags;
			// cannot change targets once the workflow has started....
			if (baseFlags.dnd5e?.targets)
				delete baseFlags.dnd5e.targets;
			/*
			const targets = new Map();
			if (this.targets) {
			for (const token of this.targets) {
				const { name } = token;
				const { img, system, uuid } = token.actor ?? {};
				//@ts-expect-error no dnd5e types
				if (uuid) targets.set(uuid, { name, img, uuid, ac: system?.attributes?.ac?.value });
			}
			baseFlags.targets = Array.from(targets.values());
			
			} */
			return baseFlags;
		}
		async getTriggeredActivity() {
			let activity = this.item.system.activities.find(a => a.id === this.midiProperties?.triggeredActivityId);
			if (!activity)
				activity = this.item.system.activities.find(a => a.identifier === this.midiProperties?.triggeredActivityId);
			if (!activity)
				activity = await fromUuid(this.midiProperties?.triggeredActivityId);
			return activity;
		}
		get triggeredActivityX() {
			let activity = this.item.system.activities.find(a => a.id === this.midiProperties?.triggeredActivityId);
			if (!activity)
				return activity;
			activity = fromUuidSync(this.midiProperties?.triggeredActivityId);
			return activity;
		}
		static metadata = foundry.utils.mergeObject(super.metadata, {
			usage: {
				dialog: MidiActivityUsageDialog,
				actions: {
					rollDamage: MidiActivityMixin.#rollDamage,
					rollDamageNoCritical: MidiActivityMixin.#rollDamageNoCritical,
					rollDamageCritical: MidiActivityMixin.#rollDamageCritical,
					confirmDamageRollCancel: MidiActivityMixin.#confirmDamageRollCancel,
					confirmDamageRollComplete: MidiActivityMixin.#confirmDamageRollComplete,
					confirmDamageRollCompleteHit: MidiActivityMixin.#confirmDamageRollCompleteHit,
					confirmDamageRollCompleteMiss: MidiActivityMixin.#confirmDamageRollCompleteMiss,
					midiApplyEffects: MidiActivityMixin.#applyEffects,
				}
			},
		}, { inplace: false, insertValues: true, insertKeys: true });
		static async #applyEffects(event, target, message) {
			const workflow = Workflow.getWorkflow(message?.uuid);
			if (workflow)
				workflow.activity = this;
			if (!workflow) {
				const errMessage = "MidiQOL | MidiActivity | applyEffects | No workflow found";
				error(errMessage);
				TroubleShooter.recordError(new Error("No workflow found"), errMessage);
				return;
			}
			const authorId = message.author.id;
			if (game.user?.id !== authorId) {
				// applying effects on behalf of another user;
				if (!game.user?.isGM) {
					ui.notifications?.warn("Only the GM can apply effects for other players");
					return;
				}
				if (game.user?.targets.size === 0) {
					ui.notifications?.warn(i18n("midi-qol.noTokens"));
					return;
				}
				const result = (await socketlibSocket.executeAsUser("applyEffects", authorId, {
					workflowId: message.uuid,
					targets: Array.from(game.user?.targets).map(t => t.document.uuid)
				}));
			}
			else {
				if (workflow) {
					workflow.forceApplyEffects = true; // don't overwrite the application targets
					workflow.effectTargets = game.user?.targets;
					if (workflow.effectTargets.size > 0)
						workflow.performState(workflow.WorkflowState_ApplyDynamicEffects);
				}
				else {
					ui.notifications?.warn(i18nFormat("midi-qol.NoWorkflow", { itemName: this.item?.name }));
				}
			}
		}
		static async #confirmDamageRollCancel(event, target, message) {
			const workflowId = message?.uuid;
			const authorId = message.author?.id;
			if (!authorId || !workflowId)
				return;
			if (!game.user?.isGM && configSettings.confirmAttackDamage === "gmOnly") {
				return;
			}
			/* TODO Since workflows are available locally we can use them to cancel the workflow without a socket call
			const workflow = Workflow.getWorkflow(workflowId);
			if (workflow) cancelWorkflow(workflowId);
			*/
			const user = game.users?.get(authorId);
			if (user?.active) {
				await socketlibSocket.executeAsUser("cancelWorkflow", authorId, { workflowId, itemCardUuid: message.uuid }).then(result => {
					if (typeof result === "string")
						ui.notifications?.warn(result);
				});
			}
			else {
				await Workflow.removeItemCardButtons(message.uuid, { removeAllButtons: true });
			}
		}
		static async #confirmDamageRollComplete(event, target, message) {
			await this.doConfirmation("confirmDamageRollComplete", event, target, message);
		}
		static async #confirmDamageRollCompleteHit(event, target, message) {
			await this.doConfirmation("confirmDamageRollCompleteHit", event, target, message);
		}
		static async #confirmDamageRollCompleteMiss(event, target, message) {
			await this.doConfirmation("confirmDamageRollCompleteMiss", event, target, message);
		}
		async doConfirmation(actionToCall, event, target, message) {
			if (!game.user?.isGM && configSettings.confirmAttackDamage === "gmOnly") {
				return;
			}
			if (message.author.active) {
				const result = await socketlibSocket.executeAsUser(actionToCall, message.author.id, { workflowId: message.uuid, activityUuid: this.uuid, itemCardUuid: message.uuid });
				if (typeof result === "string")
					ui.notifications?.warn(result);
			}
			else {
				await Workflow.removeItemCardButtons(message.uuid, { removeConfirmButtons: true });
			}
		}
		static #rollDamage(event, target, message) {
			const workflow = Workflow.getWorkflow(message?.uuid);
			if (workflow)
				workflow.activity = this;
			return this.rollDamage({ event, workflow, critical: { allow: true } }, {}, message);
		}
		static #rollDamageNoCritical(event, target, message) {
			const workflow = Workflow.getWorkflow(message?.uuid);
			if (workflow)
				workflow.activity = this;
			return this.rollDamage({ event, workflow, critical: { allow: false }, midiOptions: { isCritical: false } }, {}, message);
		}
		static #rollDamageCritical(event, target, message) {
			const workflow = Workflow.getWorkflow(message?.uuid);
			if (workflow)
				workflow.activity = this;
			return this.rollDamage({ event, workflow, midiOptions: { isCritical: true } }, {}, message);
		}
		get identifier() {
			if (this.midiProperties.identifier)
				return this.midiProperties.identifier;
			return this.name.slugify();
		}
		prepareData() {
			//@ts-expect-error
			if (!this.midiProperties.identifier && !this.name)
				this.midiProperties.identifier = i18n(this.constructor.metadata.dnd5eTitle).slugify();
			super.prepareData();
		}
		async _onChatAction(event, target, message) {
			const scaling = message.getFlag("dnd5e", "scaling") ?? 0;
			const item = scaling ? this.item.clone({ "flags.dnd5e.scaling": scaling }, { keepId: true }) : this.item;
			let activity = this.otherActivity;
			const action = target.dataset.action;
			let handler = activity?.metadata?.usage?.actions?.[action];
			const workflow = Workflow.getWorkflow(message._uuid);
			if (!handler && workflow) {
				activity = workflow.activity;
				if (activity)
					handler = activity.metadata.usage?.actions?.[action];
			}
			if (handler)
				await handler.call(activity, event, target, message);
		}
		async useAs(actor, config = {}, dialog = {}, message = {}) {
			// itemData._id = this.item._id;
			actor = getActor(config.midiOptions.rollAs);
			delete config.midiOptions.rollAs;
			if (!actor || actor === this.actor || this.selfOnlyTriggerActivity)
				return this.use(config, dialog, message);
			const itemData = this.item.toObject();
			delete itemData._id;
			foundry.utils.setProperty(itemData, `flags.${MODULE_ID}.syntheticItem`, true);
			let item = new CONFIG.Item.documentClass(itemData, { parent: actor });
			item.prepareData();
			//@ts-expect-error no dnd5e-types
			item.prepareFinalAttributes(); // Since actor prepareData is not being called need to do this here
			// @ts-expect-error no dnd5e-types
			const activity = item.system.activities.get(this.id);
			return activity.use(config, dialog, message);
		}
		async use(usage = {}, dialog = {}, message = {}) {
			if (usage.midiOptions?.rollAs)
				return this.useAs(usage.midiOptions.rollAs, usage, dialog, message);
			if (!this.item.isEmbedded)
				return;
			if (!this.item.isOwner) {
				ui.notifications?.error("DND5E.DocumentUseWarn", { localize: true });
			}
			if (!this.canUse) {
				ui.notifications?.error("DND5E.ACTIVITY.Warning.UsageNotAllowed", { localize: true });
				return;
			}
			let preUseActivityHookId;
			let activityConsumptionHookId;
			try {
				usage.midiOptions ??= {};
				usage.midiOptions.workflowOptions ??= {};
				if (debugEnabled > 0)
					warn("MidiQOL | MidiActivity | use | Called", usage, dialog, message);
				let item = this.item.clone({}, { keepId: true });
				let activity = item.system.activities.get(this.id);
				if (!usage.workflow) {
					let workflowClass = usage?.midi?.workflowClass ?? globalThis.MidiQOL.workflowClass;
					if (game.user?.isGM && checkRule("activeDefence")) {
						workflowClass = ActiveDefenceWorkflow;
					}
					if (!(workflowClass.prototype instanceof Workflow))
						workflowClass = Workflow;
					const workflowOptions = { ...usage.midiOptions, ...(usage.midiOptions?.workflowOptions ?? {}), event: usage.event, storeWorkflow: false };
					delete workflowOptions.workflowOptions;
					usage.workflow = new workflowClass(activity.actor, activity, ChatMessage.getSpeaker({ actor: activity.item.actor }), undefined, { workflowOptions });
					usage.workflow.sequenceId = usage.sequenceId;
					message.workflow = usage.workflow; // TODO: remove hack to allow card config processing
				}
				usage.workflow.systemCard = message.systemCard;
				if ((activity.midiProperties.rollMode ?? "default") !== "default")
					message.rollMode = activity.midiProperties.rollMode;
				await removeFlanking(item.parent);
				// config.midiOptions.workflowOptions.targetConfirmation ??= this.forcedTargetConfirmation;
				// come back and see about re-rolling etc.
				if (!usage.workflow)
					return undefined;
				if (usage.midiOptions.isCritical)
					usage.workflow.isCritical = true;
				if (usage.midiOptions.isFumble)
					usage.workflow.isFumble = true;
				const autoCreateTemplate = activityHasAutoPlaceTemplate(activity);
				const emanationNoTemplate = activityHasEmanationNoTemplate(activity);
				if (autoCreateTemplate || emanationNoTemplate) {
					usage.create ??= {};
					usage.create.measuredTemplate = false;
				}
				foundry.utils.setProperty(message, "data.flags.midi-qol.messageType", "attack");
				if (usage.midiOptions?.configureDialog === false)
					dialog.configure = false;
				const rollToggle = areMidiKeysPressed(usage.event, "RollToggle");
				if (rollToggle && usage.workflow)
					foundry.utils.setProperty(usage.workflow, "rollOptions.rollToggle", true);
				activity.checkAutoConsume(usage, dialog, message, rollToggle);
				// if (rollToggle) dialog.configure = !dialog.configure; Should this always be looked at
				// Synthetic items don't have an _id so setup for them to be recovered from the chat card
				if (!activity.item._id || foundry.utils.getProperty(activity.item, `flags.${MODULE_ID}.syntheticItem`))
					foundry.utils.setProperty(message, "data.flags.dnd5e.item.data", activity.item.toObject());
				let useResults;
				let upcastActivity;
				preUseActivityHookId = Hooks.on("dnd5e.preUseActivity", (newActivity, usageConfig, messageConfig) => {
					if (usage.workflow.activity.uuid !== newActivity.uuid)
						return true;
					// Not sure if required but set the workflow in the super.usageConfig.
					usageConfig.workflow = usage.workflow;
					// newActivity will be the scaled activity to use that instead of what we have
					usage.workflow.activity = newActivity;
					// TODO fix this nasty hack
					messageConfig.workflow = usage.workflow;
					return true;
				});
				let usageData;
				activityConsumptionHookId = Hooks.on("dnd5e.activityConsumption", (newActivity, usageConfig, messageConfig) => {
					if (usage.workflow.activity.uuid !== newActivity.uuid)
						return;
					usageData = usageConfig;
					usageConfig.workflow.activity = newActivity;
				});
				if (!await activity.preTargetingChecks(usage, dialog, message))
					return false;
				if (!await activity.setupTargets(usage, dialog, message))
					return false;
				if (!await activity.preChatChecks(usage, dialog, message))
					return false;
				try {
					useResults = await super.use.bind(activity)(usage, dialog, message);
				}
				catch (err) {
					error("MidiQOL | MidiActivity | use | Error in super.use", err);
					TroubleShooter.recordError(err, "Error in super.use");
					Workflow.removeWorkflow(usage.workflow?.id);
					return false;
				}
				if (!useResults) { // activity use was aborted
					Workflow.removeWorkflow(usage.workflow?.id);
					return false;
				}
				// Remove the copy stored by activity uuid
				Workflow._workflows.delete(usage.workflow.activity.uuid);
				// Force a few values to be added to the updates cache now we have the chat card.
				usage.workflow.itemCardUuid = useResults.message.uuid;
				usage.workflow.id = useResults.message.uuid;
				usage.workflow.activity = usage.workflow.activity;
				usage.workflow.item = usage.workflow.activity.item;
				activity = usage.workflow.activity;
				Workflow.addWorkflow(usage.workflow);
				if (usage.workflow.templateUuid) { // A template was placed check to see if there is a concentration effect to add it to
					const concentrationEffect = usage.workflow.actor.effects.get(usage.workflow.chatCard.getFlag("dnd5e", "use.concentrationId"));
					if (concentrationEffect) {
						concentrationEffect.addDependent(fromUuidSync(usage.workflow.templateUuid));
					}
				}
				if (await activity.postChatCardChecks(usage, dialog, message) !== true)
					return;
				await activity.setupCanSeeSense(usage);
				// if (autoCreateTemplate || emanationNoTemplate) if (!await activity.setupTargets(usage, dialog, message)) return;
				if (activity.templates) { // TODO find a better place to store this
					useResults.templates = activity.templates;
					delete activity.templates;
				}
				if (configSettings.undoWorkflow)
					await saveUndoData(usage.workflow);
				usage.workflow.itemUseComplete = true;
				usage.workflow.id = useResults.message.uuid;
				if (usage.workflow.itemUsesReaction && !hasUsedReaction(this.actor))
					await setReactionUsed(this.actor);
				if (usage.workflow.itemUsesBonusAction && !hasUsedBonusAction(this.actor))
					await setBonusActionUsed(this.actor);
				if (activity.item?.type === "spell" || foundry.utils.getProperty(activity.item, "flags.dnd5e.spellLevel")) {
					usage.workflow.castData = {
						baseLevel: activity.item.system.level ?? foundry.utils.getProperty(activity.item, "flags.dnd5e.spellLevel.base"),
						castLevel: activity.item.system.level !== undefined ? activity.item.system.level + (usageData?.scaling ?? 0) : foundry.utils.getProperty(activity.item, "flags.dnd5e.spellLevel.value"),
						scaling: (usageData?.scaling ?? foundry.utils.getProperty(activity.item, "flags.dnd5e.scaling")) || 0,
						itemUuid: activity.item.uuid
					};
				}
				const scaling = useResults.message?.getFlag && (useResults.message?.getFlag("dnd5e", "scaling") ?? 0);
				if (scaling) {
					const item = activity.item.clone({ "flags.dnd5e.scaling": scaling }, { keepId: true });
					activity = item.system.activities.get(activity.id);
				}
				activity.midiOptions = usage.midiOptions;
				await usage.workflow.performState(usage.workflow.WorkflowState_Start, {});
				return useResults;
			}
			finally {
				if (preUseActivityHookId)
					Hooks.off("dnd5e.preUseActivity", preUseActivityHookId);
				if (activityConsumptionHookId)
					Hooks.off("dnd5e.activityConsumption", activityConsumptionHookId);
			}
		}
		checkAutoConsume(usage, dialog, message, rollToggle) {
			if (dialog.configure === false)
				return;
			if (this.isSpell) {
				if (["both", "spell"].includes(autoConsumeResource(usage.workflow))) {
					dialog.configure = false;
					// Check that there is a spell slot of the right level
					const spells = this.actor.system.spells;
					// Come back and check for spell level in activities
					if (spells[`spell${this.item.system.level}`]?.value === 0 &&
						(spells.pact.value === 0 || spells.pact.level < this.item.system.level)) {
						dialog.configure = true;
					}
					if (!dialog.configure && this.hasAreaTarget && this.actor?.sheet) {
						setTimeout(() => {
							this.actor?.sheet.minimize();
						}, 100);
					}
				}
			}
			else
				dialog.configure = !(["both", "item"].includes(autoConsumeResource(usage.workflow)));
			if (autoConsumeResource(usage.workflow) !== "none" && rollToggle)
				dialog.configure = true;
		}
		async rollDamage(config, dialog = {}, message = {}) {
			// TODO decide what this should mean
			if (!config.workflow) {
				return super.rollDamage(config, dialog, message);
			}
			if (!config.workflow && config.workflow?.currentAction === config.workflow?.WorkflowState_Aborted || config.workflow?.currentAction === config.workflow?.WorkflowState_Completed)
				return this.use(config, dialog, {});
			config.midiOptions ??= {};
			if (debugEnabled > 0) {
				warn("MidiActivity | rollDamage | Called", config, dialog, message);
			}
			let result;
			let otherResult;
			let preRollDamageHookId;
			let rollDamageHookId;
			config.midiOptions ??= config.workflow?.workflowOptions ?? {};
			try {
				if (await asyncHooksCall("midi-qol.preDamageRoll", config.workflow, this, config, dialog, message) === false
					|| await asyncHooksCall(`midi-qol.preDamageRoll.${this.item.uuid}`, config.workflow, this, config, dialog, message) === false
					|| await asyncHooksCall(`midi-qol.preDamageRoll.${this.uuid}`, config.workflow, this, config, dialog, message) === false) {
					console.warn("midi-qol | Damage roll blocked via pre-hook");
					return;
				}
				if (configSettings.allowUseMacro) {
					const results = await config.workflow?.callMacros(this.item, config.workflow?.onUseMacros?.getMacros("preDamageRollConfig"), "OnUse", "preDamageRollConfig", { config, dialog, message });
					const cancelWorkflow = results?.some(i => i === false) ?? false;
					if (cancelWorkflow) {
						console.warn("midi-qol | Damage roll blocked by preDamageRollConfig onUseMacro");
						return;
					}
				}
				//@ts-expect-error
				const areKeysPressed = game.system.utils.areKeysPressed;
				const keys = {
					normal: areKeysPressed(config.event, "skipDialogNormal")
						|| areKeysPressed(config.event, "skipDialogDisadvantage"),
					critical: areKeysPressed(config.event, "skipDialogAdvantage")
				};
				config.midiOptions.isCritical ??= config.workflow.workflowOptions?.isCritical || config.workflow.isCritical;
				config.midiOptions.fastForwardDamage ??= config.workflow.workflowOptions?.fastForwardDamage;
				if (this.hasDamage || this.hasHealing) {
					if (Object.values(keys).some(k => k))
						dialog.configure = !!(this.midiProperties.forceDialog);
					else
						dialog.configure ??= !config.midiOptions?.fastForwardDamage || !!this.midiProperties.forceDialog;
					if (config.workflow && areMidiKeysPressed(config.event, "RollToggle"))
						config.workflow.rollOptions.rollToggle = !config.workflow.rollOptions.rollToggle;
					if (this.item.system.properties?.has("ver") && areMidiKeysPressed(config.event ?? config.workflow.workflowOptions.event, "Versatile")) {
						config.workflow.attackMode = "twoHanded";
					}
					if (config.workflow?.rollOptions?.rollToggle)
						dialog.configure = !dialog.configure;
					// if (dialog.configure) config.midiOptions.isCritical = false;
					preRollDamageHookId = Hooks.once(`${game.system?.id}.preRollDamageV2`, (rollConfig, dialogConfig, messageConfig) => {
						if (keys.critical)
							rollConfig.isCritical = true;
						else if (keys.normal)
							rollConfig.isCritical = false;
						else if (!dialogConfig.configure)
							rollConfig.isCritical ||= rollConfig.midiOptions.isCritical;
						if (dialogConfig.configure) {
							if (rollConfig.isCritical || rollConfig.midiOptions.isCritical) {
								dialogConfig.options.defaultButton = "critical";
							}
							else
								dialogConfig.options.defaultButton = "normal";
						}
						return true;
					});
					rollDamageHookId = Hooks.once(`${game.system?.id}.rollDamageV2`, rolls => {
						if (rolls[0] && config.workflow && config.midiOptions.updateWorkflow !== false)
							config.workflow.isCritical = rolls[0].options.isCritical;
					});
					message.create ??= false;
					if (this.damage?.parts.some(part => part.types.size > 1))
						dialog.configure = true;
					if (this.healing?.types?.size > 1)
						dialog.configure = true;
					result = await super.rollDamage(config, dialog, message);
					if (!result) { // user backed out of roll 
						config.workflow.currentAction = config.workflow?.WorkflowState_WaitForDamageRoll;
						// config.workflow.suspend();
						return;
					}
					if (result)
						result = await this.postProcessDamageRoll(config, result);
					if (config.workflow && config.midiOptions.updateWorkflow !== false)
						await config.workflow.setDamageRolls(result);
				}
				if (this.otherActivity && config.workflow?.otherActivity !== this && config.midiOptions.updateWorkflow !== false) {
					let shouldRollOther = true;
					if (this.otherCondition && config.workflow) {
						shouldRollOther = false;
						for (let token of config.workflow.hitTargets) {
							shouldRollOther ||= await evalActivationCondition(config.workflow, this.otherCondition, token, { async: true });
							if (shouldRollOther)
								break;
						}
					}
					if (shouldRollOther && (this.otherActivity.hasDamage || this.otherActivity.hasHealing || this.otherActivity.roll?.formula)) {
						// Check conditions & flags
						const otherConfig = foundry.utils.deepClone(config);
						otherConfig.midiOptions.fastForward = config.midiOptions.fastForwardDamage;
						otherConfig.midiOptions.updateWorkflow = false; // rollFormula will try and restart the workflow
						// Undo the roll toggle since rollFormula will look at it as well
						if (config.workflow?.rollOptions?.rollToggle)
							dialog.configure = !dialog.configure;
						if (this.otherActivity?.hasDamage)
							otherResult = await this.otherActivity.rollDamage(otherConfig, dialog, { create: false });
						else if (this.otherActivity?.roll?.formula) {
							otherResult = await this.otherActivity.rollFormula(otherConfig, dialog, { create: false });
							if (otherResult) {
								if (!(otherResult instanceof Array))
									otherResult = [otherResult];
								otherResult = otherResult.map(roll => 
								//@ts-expect-error
								new game.system.dice.DamageRoll(roll.formula, {}, {}));
							}
						}
						if (otherResult && config.midiOptions.updateWorkflow !== false && config.workflow)
							await config.workflow.setOtherDamageRolls(otherResult);
					}
				}
				if (config.midiOptions.updateWorkflow !== false && config.workflow?.suspended)
					config.workflow.unSuspend.bind(config.workflow)({ damageRoll: result, otherDamageRoll: otherResult });
			}
			catch (err) {
				const message = "doDamageRoll error";
				TroubleShooter.recordError(err, message);
				error(message, err);
			}
			finally {
				if (preRollDamageHookId)
					Hooks.off(`${game.system?.id}.preRollDamageV2`, preRollDamageHookId);
				if (rollDamageHookId)
					Hooks.off(`${game.system?.id}.rollDamageV2`, rollDamageHookId);
			}
			return result ?? [];
		}
		configureDamageRoll(config) {
			//@ts-expect-error
			const DamageRoll = CONFIG.Dice.DamageRoll;
			try {
				let workflow = config.workflow;
				if (!workflow)
					return config;
				if (workflow.workflowType === "TrapWorkflow")
					workflow.rollOptions.fastForward = true;
				const midiFlags = workflow.actor.flags[MODULE_ID];
				if (workflow.currentAction !== workflow.WorkflowState_WaitForDamageRoll && workflow.noAutoAttack) {
					// TODO NW check this allow damage roll to go ahead if it's an ordinary roll
					workflow.currentAction = workflow.WorkflowState_WaitForDamageRoll;
				}
				if (workflow.currentAction !== workflow.WorkflowState_WaitForDamageRoll) {
					if (workflow.currentAction === workflow.WorkflowState_AwaitTemplate)
						return ui.notifications?.warn(i18n("midi-qol.noTemplateSeen"));
					else if (workflow.currentAction === workflow.WorkflowState_WaitForAttackRoll)
						return ui.notifications?.warn(i18n("midi-qol.noAttackRoll"));
				}
				// TODO revisit this to see if it is still possible to just re-roll the damage
				if (workflow && (workflow.damageRollCount ?? 0) > 0) { // we are re-rolling the damage. redisplay the item card but remove the damage if the roll was finished
					workflow.displayChatCardWithoutDamageDetail();
				}
				;
				// Allow overrides form the caller
				if (workflow && config.midiOptions.spellLevel)
					workflow.rollOptions.spellLevel = config.midiOptions.spellLevel;
				if (workflow && config.midiOptions.powerLevel)
					workflow.rollOptions.spellLevel = config.midiOptions.powerLevel;
				if (debugEnabled > 0)
					warn("rolling damage  ", this.name, this);
				if (workflow && config.midiOptions?.isCritical !== undefined)
					workflow.isCritical = config.midiOptions?.isCritical;
				config.midiOptions.fastForwardDamage ??= workflow.workflowOptions?.fastForwardDamage ?? workflow.rollOptions.fastForwardDamage;
				if (workflow)
					workflow.damageRollCount += 1;
			}
			catch (err) {
				const message = "Configure Damage Roll error";
				TroubleShooter.recordError(err, message);
				error(message, err);
			}
		}
		getDamageConfig(config = {}) {
			config.attackMode = config.workflow?.attackMode;
			const rollConfig = super.getDamageConfig(config);
			this.configureDamageRoll(rollConfig);
			this.createDamageRollModifiers(rollConfig);
			return rollConfig;
		}
		getDamageRollModifiers(roll /* DamageRoll */) {
			const modifiers = [];
			const actionType = this.actionType;
			const itemType = this.item.type;
			//@ts-expect-error
			const damageTypes = roll.options.types;
			const changes = [];
			if (this.actor)
				for (let effect of this.actor.allApplicableEffects()) {
					if (effect.disabled || effect.isSuppressed)
						continue;
					for (let change of effect.changes) {
						if (!change.key.startsWith("flags.midi-qol.rollModifiers.damage."))
							continue;
						changes.push(change);
					}
				}
			changes.sort((a, b) => a.priority - b.priority);
			for (let change of changes) {
				const [_, __, ___, ____, actionOrItem, damageType] = change.key.split(".");
				if (actionOrItem === "all" || actionOrItem === actionType || actionOrItem === itemType) {
					if (damageType === "all" || damageTypes.includes(damageType)) {
						modifiers.push(change.value);
					}
				}
			}
			return modifiers;
		}
		createDamageRollModifiers(rollConfig) {
			const rolls = rollConfig.rolls;
			rolls.forEach(roll => {
				const modifiers = this.getDamageRollModifiers(roll);
				roll.options["midi-qol"] ??= {};
				roll.options["midi-qol"].modifiers ??= [];
				roll.options["midi-qol"].modifiers = roll.options["midi-qol"].modifiers.concat(modifiers);
			});
		}
		async postProcessDamageRoll(config, result) {
			let result2 = [];
			//@ts-expect-error
			const DamageRoll = CONFIG.Dice.DamageRoll;
			const rollMode = safeGetGameSetting("core", "rollMode");
			const showChatDamageCards = game.user?.isGM && configSettings.gmAttackDamageCards && [CONST.DICE_ROLL_MODES.SELF, CONST.DICE_ROLL_MODES.BLIND, CONST.DICE_ROLL_MODES.PRIVATE].includes(rollMode);
			try {
				if (!config.workflow)
					return result;
				let magicalDamage = this.item?.system.properties?.has("mgc") || this.item?.flags?.midiProperties?.magicdam;
				magicalDamage ||= config.ammunition?.system.properties.has("mgc") || config.ammunition?.flags?.midiProperties?.magicdam;
				magicalDamage = magicalDamage || (configSettings.requireMagical === "off" && this.attackBonus > 0);
				magicalDamage ||= configSettings.requireMagical === "off" && config.ammunition?.attackBonus > 0;
				magicalDamage = magicalDamage || (configSettings.requireMagical === "off" && (this.attack?.type.classification ?? "none") !== "weapon");
				magicalDamage = magicalDamage || (configSettings.requireMagical === "nonspell" && this.isSpell);
				if (result?.length > 0) {
					result.forEach(roll => {
						const droll = roll;
						if (!droll.options.properties)
							droll.options.properties = [];
						if (this.isSpell)
							droll.options.properties.push("spell");
						if (magicalDamage && !droll.options.properties.includes("mgc"))
							droll.options.properties.push("mgc");
						droll.options.properties.push(this.actionType);
					});
				}
				const firstTarget = config.workflow.hitTargets.first() ?? config.workflow.targets?.first();
				const firstTargetActor = firstTarget?.actor;
				const targetMaxFlags = foundry.utils.getProperty(firstTargetActor, `flags.${MODULE_ID}.grants.max.damage`) ?? {};
				const maxFlags = foundry.utils.getProperty(config.workflow, `actor.flags.${MODULE_ID}.max`) ?? {};
				let needsMaxDamage = (maxFlags.damage?.all && await evalActivationCondition(config.workflow, maxFlags.damage.all, firstTarget, { async: true, errorReturn: false }))
					|| (maxFlags.damage && maxFlags.damage[this.actionType] && await evalActivationCondition(config.workflow, maxFlags.damage[this.actionType], firstTarget, { async: true, errorReturn: false }));
				needsMaxDamage = needsMaxDamage || ((targetMaxFlags.all && await evalActivationCondition(config.workflow, targetMaxFlags.all, firstTarget, { async: true, errorReturn: false }))
					|| (targetMaxFlags[this.actionType] && await evalActivationCondition(config.workflow, targetMaxFlags[this.actionType], firstTarget, { async: true, errorReturn: false })));
				const targetMinFlags = foundry.utils.getProperty(firstTargetActor, `flags.${MODULE_ID}.grants.min.damage`) ?? {};
				const minFlags = foundry.utils.getProperty(config.workflow, `actor.flags.${MODULE_ID}.min`) ?? {};
				let needsMinDamage = (minFlags.damage?.all && await evalActivationCondition(config.workflow, minFlags.damage.all, firstTarget, { async: true, errorReturn: false }))
					|| (minFlags?.damage && minFlags.damage[this.actionType] && await evalActivationCondition(config.workflow, minFlags.damage[this.actionType], firstTarget, { async: true, errorReturn: false }));
				needsMinDamage = needsMinDamage || ((targetMinFlags.damage && await evalActivationCondition(config.workflow, targetMinFlags.all, firstTarget, { async: true, errorReturn: false }))
					|| (targetMinFlags[this.actionType] && await evalActivationCondition(config.workflow, targetMinFlags[this.actionType], firstTarget, { async: true, errorReturn: false })));
				if (needsMaxDamage && needsMinDamage) {
					needsMaxDamage = false;
					needsMinDamage = false;
				}
				const bonusDamageFlags = foundry.utils.getProperty(firstTargetActor, `flags.${MODULE_ID}.grants.bonus.damage`) ?? {};
				if (bonusDamageFlags.all || bonusDamageFlags[this.actionType]) {
					const bonusDamage = await new DamageRoll(bonusDamageFlags[this.actionType] ?? bonusDamageFlags.all).roll();
					result[0] = addRollTo(result[0], bonusDamage);
				}
				let actionFlavor;
				switch (game.system?.id) {
					case "sw5e":
						actionFlavor = i18n(this.actionType === "heal" ? "SW5E.Healing" : "SW5E.DamageRoll");
						break;
					case "n5e":
						actionFlavor = i18n(this.actionType === "heal" ? "N5E.Healing" : "N5E.DamageRoll");
						break;
					case "dnd5e":
					default:
						actionFlavor = i18n(this.actionType === "heal" ? "DND5E.Healing" : "DND5E.DamageRoll");
				}
				const title = `${this.name}`;
				const speaker = getSpeaker(this.actor);
				let flavor = title;
				if (this.item.labels.damages?.length > 0) {
					flavor = `${title} (${this.item.labels.damages.map(d => d.damageType)})`;
				}
				let messageData = foundry.utils.mergeObject({
					title,
					flavor,
					speaker,
				}, { "flags.dnd5e.roll": { type: "damage", itemId: this.item.id, itemUuid: this.item.uuid } });
				if (game.system?.id === "sw5e")
					foundry.utils.setProperty(messageData, "flags.sw5e.roll", { type: "damage", itemId: this.item.id, itemUuid: this.item.uuid });
				if (needsMaxDamage) {
					for (let i = 0; i < result.length; i++) {
						result[i] = await result[i].reroll({ maximize: true });
					}
				}
				else if (needsMinDamage) {
					for (let i = 0; i < result.length; i++) {
						result[i] = await result[i].reroll({ minimize: true });
					}
				}
				else if (foundry.utils.getProperty(this, `actor.flags.${MODULE_ID}.damage.reroll-kh`) || foundry.utils.getProperty(this, `actor.flags.${MODULE_ID}.damage.reroll-kl`)) {
					let result2 = [];
					for (let i = 0; i < result.length; i++) {
						result2.push(await result[i].reroll());
					}
					if ((foundry.utils.getProperty(this, `actor.flags.${MODULE_ID}.damage.reroll-kh`) && (sumRolls(result2) > sumRolls(result)))
						|| (foundry.utils.getProperty(this, `actor.flags.${MODULE_ID}.damage.reroll-kl`) && (sumRolls(result2) < sumRolls(result)))) {
						[result, result2] = [result2, result];
					}
					// display roll not being used.
					if (config.workflow.workflowOptions?.damageRollDSN !== false && !showChatDamageCards) {
						let promises = result2.map(r => displayDSNForRoll(r, "damageRoll"));
						await Promise.all(promises);
					}
					if (!showChatDamageCards)
						await DamageRoll.toMessage(result2, messageData, { rollMode: game.settings?.get("core", "rollMode") });
					// await result2.toMessage(messageData, { rollMode: game.settings.get("core", "rollMode") });
				}
				else if (foundry.utils.getProperty(this, `actor.flags.${MODULE_ID}.damage.advantage`)) {
					// To do this properly requires rerolling each term in the damage roll
				}
				setDamageRollMinTerms(result);
				if (this.actionType === "heal" && !Object.keys(GameSystemConfig.healingTypes).includes(config.workflow.defaultDamageType ?? ""))
					config.workflow.defaultDamageType = "healing";
				if (config.workflow?.workflowOptions?.damageRollDSN !== false && !showChatDamageCards) {
					let promises = result.map(r => displayDSNForRoll(r, "damageRoll"));
					await Promise.all(promises);
				}
				result = await processDamageRollBonusFlags.bind(config.workflow)(result);
				if (showChatDamageCards) {
					DamageRoll.toMessage(result, {}, { create: true, rollMode: game.settings?.get("core", "rollMode") });
				}
				return result;
			}
			catch (err) {
				const message = `doDamageRoll error for item ${this?.name} ${this.uuid}`;
				TroubleShooter.recordError(err, message);
				throw err;
			}
		}
		async setupCanSeeSense(usage) {
			const workflow = usage.workflow;
			if (!workflow)
				return;
			let needPause = false;
			for (let tokenRef of workflow.targets) {
				const target = getToken(tokenRef);
				if (!target)
					continue;
				if (
				// sight not enabled but we are treating it as if it is
				(!target.document.sight.enabled && configSettings.optionalRules.invisVision)
					// @ts-expect-error no dnd5e-types
					|| (target.actor?.type === "npc")
					// sight enabled but not the owner of the token
					|| (!target.isOwner && target.document.sight.enabled)
					|| (!target.vision || !target.vision?.los)) {
					initializeVision(target);
					needPause = game.modules?.get("levels-3d-preview")?.active ?? false;
				}
			}
			if (needPause) {
				await busyWait(0.1);
				for (let tokenRef of workflow.targets) {
					const target = getToken(tokenRef);
					if (!target || !target.vision?.los)
						continue;
					const sourceId = target.sourceId;
					canvas?.effects?.visionSources.set(sourceId, target.vision);
				}
			}
			for (let tokenRef of workflow.targets) {
				const target = getToken(tokenRef);
				if (!target)
					continue;
				const tokenCanSense = workflow.token ? canSense(workflow.token, target, globalThis.MidiQOL.InvisibleDisadvantageVisionModes) : true;
				const targetCanSense = workflow.token ? canSense(target, workflow.token, globalThis.MidiQOL.InvisibleDisadvantageVisionModes) : true;
				if (targetCanSense)
					workflow.targetsCanSense.add(workflow.token);
				else
					workflow.targetsCanSense.delete(workflow.token);
				if (tokenCanSense)
					workflow.tokenCanSense.add(target);
				else
					workflow.tokenCanSense.delete(target);
				const tokenCanSee = workflow.token ? canSee(workflow.token, target) : true;
				const targetCanSee = workflow.token ? canSee(target, workflow.token) : true;
				if (targetCanSee)
					workflow.targetsCanSee.add(workflow.token);
				else
					workflow.targetsCanSee.delete(workflow.token);
				if (tokenCanSee)
					workflow.tokenCanSee.add(target);
				else
					workflow.tokenCanSee.delete(target);
			}
		}
		async setupTargets(config, dialog, message) {
			if (!config.workflow) {
				const errMessage = "MidiQOL | MidiActivity | setupTargets | No workflow found";
				error(errMessage);
				TroubleShooter.recordError(new Error("No workflow found"), errMessage);
				return false;
			}
			const workflow = config.workflow;
			if (config.midiOptions?.targetsToUse?.size > 0 && !activityHasAreaTarget(this) && this.target.affects.type !== "self") {
				workflow.setTargets(config.midiOptions.targetsToUse);
			}
			else {
				if (((this.target?.affects.type ?? "") !== "")
					|| configSettings.enforceSingleWeaponTarget
					|| (targetConfirmation.enabled && targetConfirmation.all)
					|| this.forcedTargetConfirmation) {
					if (!(await preTemplateTargets(this, config.midiOptions)))
						return false;
					// TODO clean this up
					// if ((dialog.targets?.size ?? 0) === 0 && game.user?.targets) dialog.targets = game.user?.targets;
				}
				// Setup targets.
				let selfTarget = this.target?.affects.type === "self";
				if (this.item.type === "tool" && !this.target?.affects.type)
					selfTarget = true;
				if (!selfTarget) {
					workflow.setTargets(validTargetTokens(game.user?.targets));
				}
				else { // There is no token for the actor on the scene - create a temp target for this
					foundry.utils.setProperty(config, "midiOptions.workflowOptions.targetConfirmation", "none");
					let potentialTarget = getOrCreateTokenForActor(this.actor);
					workflow.setTargets(new Set());
					if (potentialTarget)
						workflow.setTargets(workflow.targets.add(potentialTarget));
				}
			}
			// remove selection of un-targetable targets TODO
			if (canvas?.scene) {
				const tokensIdsToUse = Array.from(workflow.targets).filter(t => isValidTarget(t)).map(t => t.id);
				updateUserTargets(tokensIdsToUse);
			}
			return true;
		}
		async confirmTargets() {
		}
		async preTargetingChecks(usage, dialog, message, optionsConfig = {}) {
			const workflow = usage.workflow;
			const removeWorkflow = (workflow) => {
				return false;
			};
			if (!workflow)
				return false;
			let options = { none: false, checkAoO: true, checkReaction: true, checkBonusAction: true, checkAllowIncapacitated: false, callMacros: true, callHooks: true, checkTargets: true, checkComponents: true, checkUse: true };
			if (optionsConfig.none)
				options = optionsConfig;
			options = foundry.utils.mergeObject(options, optionsConfig, { insertKeys: true, overwrite: true });
			if (usage.midiOptions.proceedChecks?.none)
				options = usage.midiOptions.proceedChecks;
			options = foundry.utils.mergeObject(options, usage.midiOptions.proceedChecks ?? {}, { insertKeys: true, overwrite: true });
			let cancelWorkflow;
			if (options.checkAllowIncapacitated && !usage.midiOptions?.workflowOptions?.allowIncapacitated && checkMechanic("incapacitated") !== "nothing") {
				const condition = checkIncapacitated(this.actor, true, false);
				if (condition) {
					logIncapacitatedCheckResult(this.actor.name, condition, debugEnabled > 0, true);
					if (checkMechanic("incapacitated") === "enforce")
						return removeWorkflow(usage.workflow);
				}
			}
			if (options.callHooks) {
				cancelWorkflow = await asyncHooksCall("midi-qol.preTargeting", { activity: this, token: usage.workflow.token, config: usage, dialog, message }) === false
					|| await asyncHooksCall(`midi-qol.preTargeting.${this.item.uuid}`, { activity: this, token: usage.workflow.token, config: usage, dialog, message }) === false
					|| await asyncHooksCall(`midi-qol.preTargeting.${this.uuid}`, { activity: this, token: usage.workflow.token, config: usage, dialog, message }) === false;
				cancelWorkflow ||= await asyncHooksCall("midi-qol.preTargetingV2", { workflow: usage.workflow, usage, dialog, message }) === false
					|| await asyncHooksCall(`midi-qol.preTargetingV2.${this.item.uuid}`, { workflow: usage.workflow, usage, dialog, message }) === false
					|| await asyncHooksCall(`midi-qol.preTargetingV2.${this.uuid}`, { workflow: usage.workflow, usage, dialog, message }) === false;
			}
			if (options.callMacros && configSettings.allowUseMacro) {
				const results = await workflow?.callMacros(this.item, workflow?.onUseMacros?.getMacros("preTargeting"), "OnUse", "preTargeting", { usage, dialog, message });
				cancelWorkflow ||= results?.some(i => i === false) ?? false;
			}
			if (cancelWorkflow)
				return removeWorkflow(usage.workflow);
			return true;
		}
		async preChatChecks(usage, dialog, message, optionsConfig = {}) {
			const workflow = usage.workflow;
			const removeWorkflow = (workflow) => {
				return false;
			};
			if (!workflow)
				return false;
			let options = { none: false, checkAoO: true, checkReaction: true, checkBonusAction: true, checkAllowIncapacitated: false, callMacros: true, callHooks: true, checkTargets: true, checkComponents: true, checkUse: true };
			if (optionsConfig.none)
				options = optionsConfig;
			else
				options = foundry.utils.mergeObject(options, optionsConfig, { insertKeys: true, overwrite: true });
			if (usage.midiOptions.proceedChecks?.none)
				options = usage.midiOptions.proceedChecks;
			else
				options = foundry.utils.mergeObject(options, usage.midiOptions.proceedChecks ?? {}, { insertKeys: true, overwrite: true });
			if (options.callHooks) {
				let hookAbort = await asyncHooksCall("midi-qol.preItemRoll", { activity: this, token: usage.workflow.token, config: usage, dialog, message }) === false
					|| await asyncHooksCall(`midi-qol.preItemRoll.${this.uuid}`, { activity: this, token: usage.workflow.token, config: usage, dialog, message }) === false;
				hookAbort ||= await asyncHooksCall("midi-qol.preItemRollV2", { workflow: usage.workflow, usage, dialog, message }) === false
					|| await asyncHooksCall(`midi-qol.preItemRollV2.${this.item.uuid}`, { workflow: usage.workflow, usage, dialog, message }) === false;
				if (hookAbort || workflow.aborted) {
					console.warn("midi-qol | attack roll blocked by preItemRoll hook");
					workflow.aborted = true;
					await workflow.performState(workflow.WorkflowState_Abort);
					return removeWorkflow(usage.workflow);
				}
			}
			if (options.callMacros && configSettings.allowUseMacro) {
				const results = await workflow.callMacros(workflow.item, workflow.onUseMacros?.getMacros("preItemRoll"), "OnUse", "preItemRoll", { usage, dialog, message });
				if (workflow.aborted || results.some(i => i === false)) {
					console.warn("midi-qol | item roll blocked by preItemRoll macro");
					workflow.aborted = true;
					await workflow.performState(workflow.WorkflowState_Abort);
					return removeWorkflow(usage.workflow);
				}
			}
			if (options.checkUse && this.useCondition && this.activation.type !== "reaction") { // reactions condition evaluation is handled elsewhere
				if (!(await evalActivationCondition(usage.workflow, this.useCondition, workflow.targets.first(), { async: true }))) {
					const message = `${this.useConditionReason} ${this.actor.name} unable to use ${this.item.name}:${this.name}`;
					if (!(usage.midiOptions.noUseWarning || usage.midiOptions.workflowOptions.noUseWarning))
						ui.notifications?.warn(message);
					else
						console.warn(`midi-qol | preChatChecks | ${message} `);
					return removeWorkflow(usage.workflow);
				}
			}
			if (this.isSpell || this.item?.system.type.value === "scroll") {
				const midiFlags = this.actor.flags[MODULE_ID];
				const needsVerbal = this.item.system.properties.has("vocal");
				const needsSomatic = this.item.system.properties.has("somatic");
				const needsMaterial = this.item.system.properties.has("material");
				//TODO Consider how to disable this check for DamageOnly workflows and trap workflows
				const conditionData = createConditionData({ actor: this.actor, activity: this });
				const notSpell = await evalCondition(midiFlags?.fail?.spell?.all, conditionData, { errorReturn: false, async: true });
				if (notSpell) {
					ui.notifications?.warn("You are unable to cast the spell");
					return removeWorkflow(usage.workflow);
				}
				if (options.checkComponents) {
					let notVerbal = await evalCondition(midiFlags?.fail?.spell?.verbal, conditionData, { errorReturn: false, async: true });
					if (notVerbal && needsVerbal) {
						ui.notifications?.warn("You make no sound and the spell fails");
						return removeWorkflow(usage.workflow);
					}
					notVerbal = notVerbal || await evalCondition(midiFlags?.fail?.spell?.vocal, conditionData, { errorReturn: false, async: true });
					if (notVerbal && needsVerbal) {
						ui.notifications?.warn("You make no sound and the spell fails");
						return removeWorkflow(usage.workflow);
					}
					const notSomatic = await evalCondition(midiFlags?.fail?.spell?.somatic, conditionData, { errorReturn: false, async: true });
					if (notSomatic && needsSomatic) {
						ui.notifications?.warn("You can't make the gestures and the spell fails");
						return removeWorkflow(usage.workflow);
					}
					const notMaterial = await evalCondition(midiFlags?.fail?.spell?.material, conditionData, { errorReturn: false, async: true });
					if (notMaterial && needsMaterial) {
						ui.notifications?.warn("You can't use the material component and the spell fails");
						return removeWorkflow(usage.workflow);
					}
				}
			}
			workflow.AoO = false;
			workflow.itemUsesReaction = false;
			if (!usage.midiOptions?.workflowOptions?.notReaction && options.checkReaction && this.actor.inCombat) {
				const isTurn = game.combat?.combatant === game.combat?.getCombatantsByActor(this.actor)[0];
				if (["reaction", "reactiondamage", "reactionmanual", "reactionpreattack"].includes(this.activation?.type)
					&& (this.activation?.cost ?? 1) > 0 && needsReactionCheck(this.actor)) {
					workflow.itemUsesReaction = true;
				}
				if (needsAOOCheck(this.actor) && this.attack && !isTurn) {
					workflow.itemUsesReaction = true;
					workflow.AoO = true;
				}
				const queryReaction = workflow.itemUsesReaction && hasUsedReaction(this.actor) && this.actor.inCombat
					&& needsReactionCheck(this.actor);
				if (queryReaction) {
					let shouldRoll = false;
					let d = await Dialog.confirm({
						title: i18n("midi-qol.EnforceReactions.Title"),
						content: i18n("midi-qol.EnforceReactions.Content"),
						yes: () => { shouldRoll = true; },
					});
					if (!shouldRoll) {
						await workflow.performState(workflow.WorkflowState_Abort);
						return removeWorkflow(usage.workflow); // user aborted roll TODO should the workflow be deleted?
					}
				}
			}
			workflow.itemUsesBonusAction = false;
			if (!usage.midiOptions?.workflowOptions?.notBonusAction && options.checkBonusAction && this.actor.inCombat && needsBonusActionCheck(this.actor)) {
				workflow.itemUsesBonusAction = ["bonus"].includes(this.activation?.type);
				const queryBonus = workflow.itemUsesBonusAction && hasUsedBonusAction(this.actor) && needsBonusActionCheck(this.actor);
				if (queryBonus) {
					let shouldRoll = false;
					let d = await Dialog.confirm({
						title: i18n("midi-qol.EnforceBonusActions.Title"),
						content: i18n("midi-qol.EnforceBonusActions.Content"),
						yes: () => { shouldRoll = true; },
					});
					if (!shouldRoll) {
						await workflow.performState(workflow.WorkflowState_Abort); // user aborted roll TODO should the workflow be deleted?
						return removeWorkflow(usage.workflow);
					}
				}
			}
			return true;
		}
		async postChatCardChecks(usage, dialog, message, optionsConfig = {}) {
			const workflow = usage.workflow;
			const removeWorkflow = async (workflow) => {
				Workflow.removeWorkflow(workflow.id);
				const consumed = workflow.chatCard?.getFlag("dnd5e", "use.consumed");
				if (!foundry.utils.isEmpty(consumed)) {
					await workflow.activity.refund(consumed);
				}
				await workflow.chatCard?.delete();
				return false;
			};
			if (debugEnabled > 0)
				warn("MidiQOL | postChatCardChecks | Called", this);
			let options = { none: false, checkAoO: true, checkReaction: true, checkBonusAction: true, checkAllowIncapacitated: false, callMacros: true, callHooks: true, checkTargets: true, checkComponents: true, checkUse: true };
			if (optionsConfig.none)
				options = optionsConfig;
			else
				options = foundry.utils.mergeObject(options, optionsConfig, { insertKeys: true, overwrite: true });
			if (usage.midiOptions.proceedChecks?.none)
				options = usage.midiOptions.proceedChecks;
			else
				options = foundry.utils.mergeObject(options, usage.midiOptions.proceedChecks ?? {}, { insertKeys: true, overwrite: true });
			try {
				// Needs to be after chat card as requires targets
				let isEmanationTargeting = activityHasAutoPlaceTemplate(this) || activityHasEmanationNoTemplate(this);
				let isAoETargeting = !isEmanationTargeting && activityHasAreaTarget(this);
				let selfTarget = this.target?.affects.type === "self";
				const inCombat = isInCombat(this.actor);
				const requiresTargets = configSettings.requiresTargets === "always" || (configSettings.requiresTargets === "combat" && inCombat);
				let speaker = getSpeaker(this.actor);
				const token = tokenForActor(this.actor);
				let cancelWorkflow = false;
				let shouldAllowRoll = !options.checkTargets || !requiresTargets // we don't care about targets
					|| (workflow.targets.size > 0) // there are some target selected
					|| (this.target?.affects.type ?? "") === "" // no target required
					|| selfTarget
					|| isAoETargeting // area effect spell and we will auto target
					|| isEmanationTargeting // range target and will autoTarget
					|| (!this.attack && !this.hasDamage && !this.hasSave); // does not do anything - need to check dynamic effects
				if (!shouldAllowRoll) {
					return removeWorkflow(usage.workflow);
				}
				// only allow attacks against at most the specified number of targets
				let allowedTargets;
				if (this.target?.affects.type === "creature" && this.target?.affects.count === "") //dnd5e 3.2
					allowedTargets = 9999;
				else
					allowedTargets = (this.target?.affects.type === "creature" ? this.target?.affects.count : 9999) ?? 9999;
				if (requiresTargets && configSettings.enforceSingleWeaponTarget && allAttackTypes.includes(this.actionType) && allowedTargets === 9999) {
					allowedTargets = 1;
					if (requiresTargets && workflow.targets.size !== 1) {
						ui.notifications?.warn(i18nFormat("midi-qol.wrongNumberTargets", { allowedTargets }));
						if (debugEnabled > 0)
							warn(`${game.user?.name} ${i18nFormat(`midi-qol.${MODULE_ID}.wrongNumberTargets`, { allowedTargets })}`);
						return removeWorkflow(usage.workflow);
					}
				}
				if (options.checkTargets) {
					if (requiresTargets && !isEmanationTargeting && !isAoETargeting && this.target?.affects.type === "creature" && workflow.targets.size === 0) {
						ui.notifications?.warn(i18n("midi-qol.noTargets"));
						if (debugEnabled > 0)
							warn(`${game.user?.name} attempted to roll with no targets selected`);
						return removeWorkflow(usage.workflow);
					}
				}
				// do pre roll checks
				if (options.checkTargets) {
					if ((game.system?.id === "dnd5e" || game.system?.id === "n5e") && requiresTargets && workflow.targets.size > allowedTargets) {
						ui.notifications?.warn(i18nFormat("midi-qol.wrongNumberTargets", { allowedTargets }));
						if (debugEnabled > 0)
							warn(`${game.user?.name} ${i18nFormat(`midi-qol.${MODULE_ID}.wrongNumberTargets`, { allowedTargets })}`);
						return removeWorkflow(usage.workflow);
					}
				}
				let tokenToUse;
				if (speaker.token)
					tokenToUse = canvas?.tokens?.get(speaker.token);
				const rangeDetails = checkActivityRange(this, tokenToUse, workflow.targets, checkMechanic("checkRange") !== "none");
				if (checkMechanic("checkRange") !== "none" && !isAoETargeting && !isEmanationTargeting && !workflow.AoO && speaker.token) {
					if (tokenToUse && workflow.targets.size > 0) {
						if (rangeDetails.result === "fail")
							return removeWorkflow(usage.workflow);
						else {
							tokenToUse = rangeDetails.attackingToken;
						}
					}
				}
				if (!workflow)
					return false;
				workflow.token = tokenToUse;
				workflow.rangeDetails = rangeDetails;
				if (configSettings.undoWorkflow)
					await saveUndoData(workflow);
				// if showing a full card we don't want to auto roll attacks or damage.
				const consume = this.consume;
				if (consume?.type === "ammo") {
					workflow.ammo = this.actor.items.get(consume.target);
				}
				// Need concentration removal to complete before allowing workflow to continue so have workflow wait for item use to complete
			}
			catch (err) {
				const message = `postChatCardChecks error for ${this.actor?.name} ${this.name} ${this.uuid}`;
				TroubleShooter.recordError(err, message);
				throw err;
			}
			return true;
		}
		_usageChatButtons(message) {
			let buttons = super._usageChatButtons(message);
			const autoCreateTemplate = activityHasAutoPlaceTemplate(this);
			const emanationNoTemplate = activityHasEmanationNoTemplate(this);
			if (autoCreateTemplate || emanationNoTemplate) {
				buttons = buttons.filter(b => b.dataset?.action !== "placeTemplate");
			}
			return buttons;
		}
		async _placeEmanationTemplate(workflow) {
			const autoCreateTemplate = activityHasAutoPlaceTemplate(this);
			const actor = this.item.parent;
			const tokenToUse = workflow.token ?? getToken(actor);
			if (!tokenToUse)
				return;
			if (autoCreateTemplate) {
				const gs = canvas?.dimensions?.distance ?? 5;
				const templateOptions = {};
				// square templates don't respect the options distance field
				let item = this;
				let target = this.target ?? { value: 0 };
				const fudge = 0.1;
				const { width, height } = tokenToUse.document;
				templateOptions.distance = Math.ceil(target.template.size + Math.max((width ?? 1) / 2, (height ?? 1) / 2, 0) * (canvas?.dimensions?.distance ?? 0));
				templateOptions.x = tokenToUse.center?.x ?? 0;
				templateOptions.y = tokenToUse.center?.y ?? 0;
				foundry.utils.setProperty(templateOptions, `flags.${MODULE_ID}.actorUuid`, actor.uuid);
				foundry.utils.setProperty(templateOptions, `flags.${MODULE_ID}.tokenId`, tokenToUse.id);
				foundry.utils.setProperty(templateOptions, `flags.${MODULE_ID}.workflowId`, this.uuid); // TODO look at this when workflow ids are chat card ids
				foundry.utils.setProperty(templateOptions, `flags.${MODULE_ID}.itemUuid`, this.item.uuid);
				// @ts-expect-error .canvas
				let templates = game.system.canvas.AbilityTemplate.fromActivity(this, templateOptions);
				// fromActivity returns an array of templates - work out if we need more than one
				if (!templates)
					error("No templates returned from fromActivity");
				let template = templates[0];
				const templateData = template.document.toObject();
				if (this.item)
					foundry.utils.setProperty(templateData, `flags.${MODULE_ID}.itemUuid`, this.item.uuid);
				if (this.actor)
					foundry.utils.setProperty(templateData, `flags.${MODULE_ID}.actorUuid`, this.actor.uuid);
				if (!foundry.utils.getProperty(templateData, `flags.${game.system?.id}.origin`))
					foundry.utils.setProperty(templateData, `flags.${game.system?.id}.origin`, this.item?.uuid);
				//@ts-expect-error
				const templateDocuments = await canvas?.scene?.createEmbeddedDocuments("MeasuredTemplate", [templateData]);
				if (templateDocuments && templateDocuments.length > 0) {
					let td = templateDocuments[0];
					await td.object?.refresh();
					await busyWait(0.01);
					if (workflow) {
						workflow.templateUuid = td.uuid;
						if (installedModules.get("walledtemplates") && this.flags?.walledtemplates?.attachToken === "caster") {
							// @ts-expect-error .object
							await tokenToUse.attachTemplate(td.object, { "flags.dae.stackable": "noneName" }, true);
							if (workflow && !foundry.utils.getProperty(this, "item.flags.walledtemplates.noAutotarget"))
								selectTargets.bind(workflow)(td);
						}
						else if (getActivityAutoTargetAction(this) !== "none")
							selectTargets.bind(workflow)(td);
					}
					else
						selectTargets.bind(this)(td);
					return templates;
				}
			}
		}
		async _usageChatContext(message) {
			// TODO major revisit needed for this
			const workflow = message.workflow;
			let systemCard = message.systemCard ?? false;
			const minimalCard = false;
			// Insert targets as required for emanation templates and emanationNoTemplate
			// This is the wrong spot, but there is no other good spot for it.
			const tokenToUse = getToken(this.item.parent);
			const autoCreateTemplate = activityHasAutoPlaceTemplate(this);
			const emanationNoTemplate = activityHasEmanationNoTemplate(this);
			// If emanationTemplate or emanationNoTemplate setup game.user?.targets.
			if (tokenToUse && autoCreateTemplate) {
				this.templates = await this._placeEmanationTemplate(workflow);
			}
			if (tokenToUse && emanationNoTemplate) {
				setRangedTargets(tokenToUse, this.target);
				message.workflow.setTargets(validTargetTokens(game.user?.targets));
			}
			foundry.utils.setProperty(message.data, "flags.dnd5e.targets", globalThis.dnd5e.utils.getTargetDescriptors());
			const context = await super._usageChatContext(message);
			if (debugEnabled > 0)
				warn("show item card ", this, this.actor, this.actor.token, systemCard, workflow);
			let needAttackButton = !getRemoveAttackButtons(this.item) || configSettings.mergeCardMulti
				|| configSettings.confirmAttackDamage !== "none" ||
				!(workflow?.someAutoRollEventKeySet() && !getAutoRollAttack(workflow));
			const needDamageButton = (this.hasDamage || this.hasHealing) && ((["none", "saveOnly"].includes(getAutoRollDamage(workflow)) || workflow?.rollOptions.rollToggle)
				|| configSettings.confirmAttackDamage !== "none"
				|| !getRemoveDamageButtons(this.item)
				|| systemCard
				|| configSettings.mergeCardMulti);
			// not used const sceneId = token?.scene && token.scene.id || canvas?.scene?.id;
			const isPlayerOwned = this.item.actor?.hasPlayerOwner;
			const hideItemDetails = (["none", "cardOnly"].includes(configSettings.showItemDetails) || (configSettings.showItemDetails === "pc" && !isPlayerOwned))
				|| !configSettings.itemTypeList?.includes(this.item.type);
			const hasEffects = !["applyNoButton", "applyRemove"].includes(configSettings.autoItemEffects) && workflow?.workflowType === "BaseWorkflow" && this.effects.find(ae => !ae.transfer && !foundry.utils.getProperty(ae, "flags.dae.dontApply"));
			let dmgBtnText = (this.actionType === "heal") ? i18n(`${SystemString}.Healing`) : i18n(`${SystemString}.Damage`);
			if (workflow?.workflowOptions?.fastForwardDamage && configSettings.showFastForward)
				dmgBtnText += ` ${i18n("midi-qol.fastForward")}`;
			let midiContextData = {
				hasButtons: true,
				labels: this.labels,
				//@ ts-expect-error TODO needed for abilities translation
				// config: game.system.config,
				condensed: configSettings.mergeCardCondensed && !!this.attack,
				hasAttack: this.attack && !minimalCard && (systemCard || needAttackButton || configSettings.confirmAttackDamage !== "none"),
				isHealing: !minimalCard && this.item.isHealing && (systemCard || configSettings.autoRollDamage !== "always"),
				hasDamage: needDamageButton,
				hasAttackRoll: !minimalCard && this.attack,
				configSettings,
				hideItemDetails,
				dmgBtnText,
				showProperties: workflow?.workflowType === "BaseWorkflow",
				hasEffects,
				effects: this.item.effects,
				isMerge: true,
				mergeCardMulti: configSettings.mergeCardMulti && (this.attack || this.hasDamage),
				confirmAttackDamage: configSettings.confirmAttackDamage !== "none" && (this.attack || this.hasDamage),
				RequiredMaterials: i18n(`${SystemString}.RequiredMaterials`),
				Attack: i18n(`${SystemString}.Attack`),
				OtherFormula: i18n(`${SystemString}.OtherFormula`),
				canCancel: configSettings.undoWorkflow // TODO enable this when more testing done.
			};
			const rollSaveOptions = new Set();
			rollSaveOptions.add(configSettings.rollNPCLinkedSaves);
			rollSaveOptions.add(configSettings.rollNPCSaves);
			rollSaveOptions.add(configSettings.playerRollSaves);
			const showSaveButtons = rollSaveOptions.has("none") || rollSaveOptions.has("chat") || configSettings.autoCheckSaves === "none";
			if (this.otherActivity?.save && !this.save && showSaveButtons) {
				context.buttons = context.buttons.concat(this.otherActivity._usageChatButtons(message));
			}
			else if (this.otherActivity?.check && !this.check && showSaveButtons) {
				context.buttons = context.buttons.concat(this.otherActivity._usageChatButtons(message));
			}
			context.buttons = context.buttons?.filter(b => !["rollAttack", "rollDamage", "rollHealing"].includes(b.dataset?.action));
			if (!showSaveButtons)
				context.buttons = context.buttons?.filter(b => !["rollSave", "rollCheck"].includes(b.dataset?.action));
			if (configSettings.autoCheckSaves !== "none")
				context.buttons?.forEach(b => { b.isDisabled = ["rollSave", "rollCheck"].includes(b.dataset?.action) ? "disabled" : ""; });
			return foundry.utils.mergeObject(context, midiContextData);
		}
		get actionType() {
			return this.metadata.type;
		}
		get otherActivity() {
			if (this._otherActivity !== undefined)
				return this._otherActivity;
			if (this.otherActivityId === "none" || this.otherActivityId === undefined)
				return undefined;
			if (this.ammunitionItem) {
				//TODO consider making this a choice of activity
				this._otherActivity = this.ammunitionItem.system.activities?.find(a => a.midiProperties?.automationOnly && a.isOtherActivityCompatible);
				if (!this._otherActivity) {
					this._otherActivity = this.ammunitionItem.system.activities.find(a => a.isOtherActivityCompatible);
				}
				if (this._otherActivity) {
					this._otherActivity.prepareData();
					return this._otherActivity;
				}
			}
			if (this.otherActivityId !== "") {
				this._otherActivity = this.item.system.activities.get(this.otherActivityId);
				if (!this._otherActivity)
					this._otherActivity = this.item.system.activities.find(a => a.identifier === this.otherActivityId);
				this._otherActivity?.prepareData();
				return this._otherActivity;
			}
			if (!this._otherActivity) {
				// Is there exactly 1 automation only and otherActivityCompatible activity on the item
				const otherActivityOptions = this.item.system.activities.filter(a => a.midiProperties?.automationOnly && a.isOtherActivityCompatible && a.uuid !== this.uuid);
				if (otherActivityOptions.length === 1) {
					this._otherActivity = otherActivityOptions[0];
				}
			}
			if (!this._otherActivity) {
				// Is there exactly 1 other activity compatible activity on the item
				const otherActivityOptions = this.item.system.activities.filter(a => a.isOtherActivityCompatible && a.uuid !== this.uuid);
				if (otherActivityOptions.length === 1) {
					this._otherActivity = otherActivityOptions[0];
				}
			}
			// If none of the above match we can't tell which one to use.
			this._otherActivity?.prepareData();
			if (!this._otherActivity)
				this._otherActivity = null;
			return this._otherActivity;
		}
		get useCondition() {
			if (this.useConditionText && this.useConditionText !== "")
				return this.useConditionText;
			return foundry.utils.getProperty(this.item, "flags.midi-qol.itemCondition") ?? "";
		}
		get effectCondition() {
			if (this.effectConditionText && this.effectConditionText !== "")
				return this.effectConditionText;
			return foundry.utils.getProperty(this.item, "flags.midi-qol.effectCondition") ?? "";
		}
		get reactionCondition() {
			if (this.useCondition && this.useCondition !== "")
				return this.useCondition;
			return foundry.utils.getProperty(this.item, "flags.midi-qol.reactionCondition") ?? "";
		}
		get otherCondition() {
			if (this.otherActivity && this.otherActivity?.useCondition !== "")
				return this.otherActivity.useCondition;
			return foundry.utils.getProperty(this.item, "flags.midi-qol.otherCondition") ?? "";
		}
		get hasDamage() {
			return this.damage?.parts.length > 0;
		}
		get hasHealing() {
			return this.healing !== undefined;
		}
		get hasAttack() {
			return this.attack !== undefined;
		}
		get hasSave() {
			return (!!this.save || !!this.check);
		}
	};
};
export var MidiActivityMixinSheet = Base => {
	return class MidiActivitySheet extends Base {
		static DEFAULT_OPTIONS = {
			...super.DEFAULT_OPTIONS,
			actions: {
				...super.DEFAULT_OPTIONS.actions,
				addMacro: MidiActivitySheet.#addMacro,
			}
		};
		static #addMacro() {
			const Editor = globalThis.DAE?.DIMEditor;
			if (Editor)
				new Editor({ document: this.activity }).render({ force: true });
			else
				ui.notifications?.error("DIMEditor not available - install Dynamic Active Effects");
		}
		static PARTS = {
			...super.PARTS,
			"midi-qol": {
				template: "modules/midi-qol/templates/activity/parts/midi-activity-tab.hbs",
			}
		};
		_getTabs() {
			let tabs = super._getTabs();
			tabs = {
				...tabs,
				"midi-qol": {
					id: "midi-qol", group: "sheet", icon: "fa-solid fa-sun", label: "midi-qol"
				}
			};
			return super._markTabs(tabs);
		}
		async _prepareMidiQolContext(context) {
			// context = await super._prepareEffectContext(context);
			context.ConfirmTargetOptions = Object.entries(geti18nOptions("ConfirmTargetOptions")).map(([value, label]) => ({ value, label }));
			if (this.activity.forcedTargetConfirmation)
				context.ConfirmTargetOptions = context.ConfirmTargetOptions.filter(o => o.value === this.activity.forcedTargetConfirmation);
			const ROLL_MODES = CONST.DICE_ROLL_MODES;
			context.RollModeOptions = [
				{ value: "default", label: i18n("Default") },
				{ value: ROLL_MODES.PUBLIC, label: i18n("CHAT.RollPublic") },
				{ value: ROLL_MODES.PRIVATE, label: i18n("CHAT.RollPrivate") },
				{ value: ROLL_MODES.BLIND, label: i18n("CHAT.RollBlind") },
				{ value: ROLL_MODES.SELF, label: i18n("CHAT.RollSelf") }
			];
			context.placeholderIdentifier = context.activity.identifier;
			const midiProperties = context.activity.midiProperties;
			context.otherActivityOptions = this.item.system.activities
				.filter(a => a.id !== this.activity.id && a.isOtherActivityCompatible)
				.reduce((ret, a) => { ret.push({ label: `${a.name} (${a.identifier})`, value: a.identifier, id: a.id }); return ret; }, [{ label: "Auto", value: "" }, { label: "None", value: "none" }]);
			context.otherActivityOptions?.forEach(option => { option.selected = option.value === this.activity.otherActivityId || option.id === this.activity.otherActivityId; });
			context.triggeredActivityOptions = this.item.system.activities
				.filter(a => a.id !== this.activity.id && a.isTriggerableActivity)
				.reduce((ret, a) => { ret.push({ label: `${a.name} (${a.identifier})`, value: a.identifier, id: a.id }); return ret; }, [{ label: "None", value: "none" }]);
			context.triggeredActivityOptions?.filter(option => { option.selected = option.value === this.activity.midiProperties.triggeredActivityId || option.id === this.activity.midiProperties.triggeredActivityId; });
			let activity = context.activity;
			let triggeredActivity = await context.activity.getTriggeredActivity();
			const triggerList = [activity.name];
			while (triggeredActivity) {
				triggerList.push(triggeredActivity.name);
				if (triggeredActivity.id === activity.id) {
					ui.notifications?.error(`midi-qol | Circular activity call detected ${triggerList.join("->")}`);
					break;
				}
				triggeredActivity = await triggeredActivity.getTriggeredActivity();
			}
			context.triggeredActivityTargetOptions = Object.entries(geti18nOptions("TriggeredActivityTargetOptions")).map(([value, label]) => {
				return { value, label, selected: midiProperties.triggeredActivityTargets === value };
			});
			context.triggeredActivityRollAsOptions = Object.entries(geti18nOptions("TriggeredActivityRollAsOptions")).map(([value, label]) => {
				return { value, label, selected: midiProperties.triggeredActivityRollAs === value };
			});
			if ((await context.activity.getTriggeredActivity())?.isSelfTriggerableOnly) {
				context.triggeredActivityRollAsOptions = context.triggeredActivityRollAsOptions.filter(o => o.value === "self");
				// adjust target list
			}
			context.ignoreTraitsOptions = Object.entries(geti18nOptions("SHARED.FIELDS.midiProperties.ignoreTraits")).map(([value, entry]) => {
				//@ts-expect-error
				return { value, label: entry.label, selected: this.activity.midiProperties.ignoreTraits.has(value) };
			});
			context.AutoTargetTypeOptions = Object.entries(geti18nOptions("AoETargetTypeOptions")).map(([value, label]) => ({ value, label }));
			const defaultAction = { "default": i18n("midi-qol.MidiSettings") };
			context.AutoTargetActionOptions = Object.entries(foundry.utils.mergeObject(defaultAction, geti18nOptions("autoTargetOptions"))).map(([value, label]) => ({ value, label }));
			context.hasAreaTarget = this.activity.target?.template?.type;
			context.possibleOtherActivity = this.activity.possibleOtherActivity;
			context.turnChoiceOptions = [{ value: "start", label: i18n("midi-qol.OVERTIME.FIELDS.turnChoice.start") }, { value: "end", label: i18n("midi-qol.OVERTIME.FIELDS.turnChoice.end") }];
			return context;
		}
		async _preparePartContext(partId, context) {
			if (partId === "midi-qol") {
				context.tab = context.tabs["midi-qol"];
				return this._prepareMidiQolContext(context);
			}
			return super._preparePartContext(partId, context);
		}
		/** @override */ // This does not seem to work for activity sheets
		_getHeaderButtons() {
			let buttons = super._getHeaderButtons();
			const DIMtitle = i18n('dae.DIMEditor.Name');
			const Editor = globalThis.DAE?.DIMEditor;
			if (!Editor)
				return buttons;
			buttons.unshift({
				label: DIMtitle,
				class: "dae-dimeditor",
				icon: "fas fa-file-pen",
				onclick: () => { new Editor({ document: this.activity }).render({ force: true }); }
			});
			return buttons;
		}
	};
};
function renderActivitySheetHook(app, [elem]) {
	const Editor = globalThis.DAE?.DIMEditor;
	if (!Editor)
		return;
	let activity = app.activity;
	if (!activity.macro)
		return;
	let existingButton = elem.closest('.window-header').querySelector('button.dae-dimeditor');
	if (existingButton) {
		if (activity.macro?.command)
			existingButton.style.color = '#36ba36';
		return;
	}
	let closeButton = elem.closest('.window-header').querySelector('button[data-action="close"]');
	let daeButton = document.createElement('button');
	const DIMtitle = i18n('dae.DIMEditor.Name');
	daeButton.setAttribute('class', 'header-control fa-solid fa-file-pen dae-dimeditor');
	daeButton.onclick = function (ev) { new Editor({ document: this.activity }).render({ force: true }); }.bind(app);
	if (activity.macro?.command)
		daeButton.style.color = '#36ba36';
	daeButton.title = "Activity Macro Editor";
	closeButton.parentNode.insertBefore(daeButton, closeButton);
}
export var MidiActivityUsageDialog;
export function setupMidiActivityMixin() {
	//@ts-expect-error
	const ActivityUsageDialog = game.system.applications.activity.ActivityUsageDialog;
	MidiActivityUsageDialog = class MidiActivityUsageDialog extends ActivityUsageDialog {
		async _prepareCreationContext(context, options) {
			context = await super._prepareCreationContext(context, options);
			//@ts-expect-error
			if (activityHasAutoPlaceTemplate(this.activity) || activityHasEmanationNoTemplate(this.activity)) {
				context.hasCreation = false;
			}
			return context;
		}
	};
	Hooks.on("renderActivitySheet", renderActivitySheetHook);
}
