import { flatten, round, uniqBy } from 'lodash';
import { TypeScriptUtils } from '../../utils';
import { AbilityAccessors } from '../Ability';
import { ActionAccessors, ActionGenerators } from '../Action';
import { CharacterValidators } from '../Character';
import { AbilityStatEnum, AttackTypeRangeEnum, PreferenceEncumbranceTypeEnum, StealthCheckTypeEnum } from '../Core';
import { DataOriginTypeEnum } from '../DataOrigin';
import { DefinitionHacks } from '../Definition';
import { HelperUtils } from '../Helper';
import { InfusionAccessors, InfusionTypeEnum } from '../Infusion';
import { LimitedUseAccessors } from '../LimitedUse';
import { ModifierAccessors, ModifierDerivers, ModifierValidators } from '../Modifier';
import { isValidWeaponMasteryModifier } from '../Modifier/validators';
import { RuleDataAccessors, RuleDataUtils } from '../RuleData';
import { AdjustmentTypeEnum, ValueAccessors, ValueHacks, ValueUtils, ValueValidators, } from '../Value';
import { getArmorDefinitionAc, getArmorTypeId, getAttackType, getBaseItemId, getBaseTypeId, getBundleSize, getCategoryId, getDefinitionAvatarUrl, getDefinitionCanAttune, getDefinitionCanEquip, getDefinitionCapacityWeight, getDefinitionCost, getDefinitionDamageType, getDefinitionId, getDefinitionMagic, getDefinitionName, getDefinitionProperties, getDefinitionStealthCheck, getDescription, getGearTypeId, getItemRequiredStr, getItemWeaponBehaviors, getLimitedUse, getMappingEntityTypeId, getMappingId, getModifiers, getQuantity, getSources, getWeight, getWeightDefinition, isEquipped, isMagic, isMonkWeapon, isStackable, } from './accessors';
import { ArmorTypeEnum, FUTURE_ITEM_DEFINITION_TYPE, ITEM_CUSTOMIZATION_ADJUSTMENT_TYPES, ItemBaseTypeEnum, ItemBaseTypeIdEnum, ItemTypeEnum, WeaponPropertyEnum, } from './constants';
import { hack__deriveStealthMediumArmorMasterShouldReturnStealthDefinition } from './hacks';
import { hasWeaponProperty, isArmorContract, isBaseArmor, isBaseGear, isBaseWeapon, hasItemWeaponBehaviors, isWeaponContract, isAmmunition, isGearContract, } from './utils';
import { isCustomItem, validateIsWeaponLike } from './validators';
/**
 *
 * @param item
 */
export function deriveArmorClass(item) {
    if (!isArmorContract(item)) {
        return null;
    }
    const definitionArmorClass = getArmorDefinitionAc(item);
    return deriveArmorModifierAcTotal(item) + (definitionArmorClass === null ? 0 : definitionArmorClass);
}
/**
 *
 * @param weapon
 * @param valueLookup
 */
export function deriveIsOffhand(weapon, valueLookup) {
    return !!ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.IS_OFFHAND, ValueHacks.hack__toString(getMappingId(weapon)), ValueHacks.hack__toString(getMappingEntityTypeId(weapon)));
}
/**
 *
 * @param weapon
 * @param valueLookup
 */
export function deriveIsSilvered(weapon, valueLookup) {
    return !!ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.IS_SILVER, ValueHacks.hack__toString(getMappingId(weapon)), ValueHacks.hack__toString(getMappingEntityTypeId(weapon)));
}
/**
 *
 * @param weapon
 * @param valueLookup
 */
export function deriveIsAdamantine(weapon, valueLookup) {
    return !!ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.IS_ADAMANTINE, ValueHacks.hack__toString(getMappingId(weapon)), ValueHacks.hack__toString(getMappingEntityTypeId(weapon)));
}
/**
 *
 * @param item
 * @param infusion
 */
export function deriveCanAttune(item, infusion) {
    if (infusion && InfusionAccessors.getType(infusion) !== InfusionTypeEnum.REPLICATE) {
        return InfusionAccessors.requiresAttunement(infusion);
    }
    return getDefinitionCanAttune(item);
}
/**
 *
 * @param item
 * @param infusion
 */
export function deriveCanEquip(item, infusion) {
    if (infusion !== null) {
        return true;
    }
    return getDefinitionCanEquip(item);
}
/**
 *
 * @param item
 * @param ruleData
 */
export function deriveCategoryInfo(item, ruleData) {
    if (!isWeaponContract(item)) {
        return null;
    }
    return RuleDataUtils.getWeaponCategoryInfo(getCategoryId(item), ruleData);
}
/**
 *
 * @param item
 * @param valueLookup
 */
export function deriveIsDisplayAsAttack(item, valueLookup) {
    const override = ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.DISPLAY_AS_ATTACK, ValueHacks.hack__toString(getMappingId(item)), ValueHacks.hack__toString(getMappingEntityTypeId(item)));
    const defaultIsDisplayAsAttack = deriveDefaultIsDisplayAsAttack(item);
    return (defaultIsDisplayAsAttack && (override === null || override)) || !!override;
}
/**
 *
 * @param baseTypeId
 */
export function deriveBaseTypeNameFromId(baseTypeId) {
    switch (baseTypeId) {
        case ItemBaseTypeIdEnum.WEAPON:
            return 'Weapon';
        case ItemBaseTypeIdEnum.ARMOR:
            return 'Armor';
        case ItemBaseTypeIdEnum.GEAR:
            return 'Gear';
        default:
        // not implemented
    }
    return '';
}
/**
 *
 * @param item
 * @param valueLookup
 */
export function deriveCost(item, valueLookup) {
    const override = ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.COST_OVERRIDE, ValueHacks.hack__toString(getMappingId(item)), ValueHacks.hack__toString(getMappingEntityTypeId(item)));
    const quantity = getQuantity(item);
    const bundleSize = getBundleSize(item);
    let costPerItem = getDefinitionCost(item);
    if (costPerItem === null) {
        costPerItem = 0;
    }
    if (override !== null) {
        costPerItem = override;
    }
    if (isStackable(item)) {
        costPerItem /= bundleSize === 0 ? 1 : bundleSize;
    }
    return round(costPerItem * quantity, 2);
}
/**
 *
 * @param item
 * @param globalModifiers
 */
export function deriveStealthCheck(item, globalModifiers) {
    if (!isArmorContract(item)) {
        return StealthCheckTypeEnum.NONE;
    }
    const itemModifiers = getModifiers(item);
    const definitionCheck = getDefinitionStealthCheck(item);
    if (definitionCheck === StealthCheckTypeEnum.DISADVANTAGE) {
        const filteredGlobalModifiers = globalModifiers.filter((modifier) => ModifierValidators.isStealthDisadvantageRemoveModifier(modifier));
        if (itemModifiers.find((modifier) => ModifierValidators.isStealthDisadvantageRemoveModifier(modifier)) ||
            filteredGlobalModifiers.length) {
            if (hack__deriveStealthMediumArmorMasterShouldReturnStealthDefinition(item, filteredGlobalModifiers)) {
                return definitionCheck;
            }
            return StealthCheckTypeEnum.NONE;
        }
    }
    return definitionCheck;
}
/**
 *
 * @param item
 * @param ruleData
 */
export function deriveAvatarUrl(item, ruleData) {
    let previewImageUrl = getDefinitionAvatarUrl(item);
    switch (getBaseTypeId(item)) {
        case ItemBaseTypeIdEnum.WEAPON:
            if (!previewImageUrl) {
                previewImageUrl = ruleData.defaultWeaponImageUrl;
            }
            break;
        case ItemBaseTypeIdEnum.ARMOR:
            if (!previewImageUrl) {
                previewImageUrl = ruleData.defaultArmorImageUrl;
            }
            break;
        case ItemBaseTypeIdEnum.GEAR:
            if (!previewImageUrl) {
                previewImageUrl = ruleData.defaultGearImageUrl;
            }
            break;
    }
    if (!previewImageUrl && isCustomItem(item)) {
        previewImageUrl = ruleData.defaultGearImageUrl;
    }
    if (previewImageUrl) {
        return previewImageUrl;
    }
    return '';
}
/**
 *
 * @param item
 */
export function deriveBaseType(item) {
    if (isArmorContract(item)) {
        return ItemBaseTypeEnum.ARMOR;
    }
    else if (isWeaponContract(item)) {
        return ItemBaseTypeEnum.WEAPON;
    }
    return ItemBaseTypeEnum.GEAR;
}
/**
 *
 * @param item
 * @param valueLookup
 */
export function deriveWeight(item, valueLookup) {
    const quantity = getQuantity(item);
    const bundleSize = getBundleSize(item);
    const isItemStackable = isStackable(item);
    const weight = getWeightDefinition(item);
    const weightOverride = ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.WEIGHT_OVERRIDE, ValueHacks.hack__toString(getMappingId(item)), ValueHacks.hack__toString(getMappingEntityTypeId(item)));
    let baseWeight = weight;
    if (weightOverride !== null) {
        baseWeight = weightOverride;
    }
    if (!baseWeight) {
        return 0;
    }
    if (isItemStackable) {
        if (quantity) {
            return round(baseWeight * (quantity / (bundleSize === 0 ? 1 : bundleSize)), 2);
        }
        return 0;
    }
    return baseWeight;
}
/**
 *
 * @param item
 * @param valueLookup
 */
export function deriveCapacityWeight(item, valueLookup) {
    const capacityWeight = getDefinitionCapacityWeight(item);
    const capacityOverride = ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.CAPACITY_WEIGHT_OVERRIDE, ValueHacks.hack__toString(getMappingId(item)), ValueHacks.hack__toString(getMappingEntityTypeId(item)));
    return capacityOverride !== null && capacityOverride !== void 0 ? capacityOverride : capacityWeight;
}
/**
 *
 * @param items
 * @param initialValue
 */
export function deriveItemsWeightTotal(items, initialValue = 0) {
    return items.reduce((acc, item) => (acc += getWeight(item)), initialValue);
}
/**
 *
 * @param armor
 * @param modifiers
 * @param abilityLookup
 */
export function deriveArmorDexBonusMax(armor, modifiers, abilityLookup) {
    const armorTypeId = getArmorTypeId(armor);
    switch (armorTypeId) {
        case ArmorTypeEnum.MEDIUM_ARMOR:
            const dexAbility = abilityLookup[AbilityStatEnum.DEXTERITY];
            const dexAbilityScore = AbilityAccessors.getScore(dexAbility);
            if (dexAbility && dexAbilityScore !== null && dexAbilityScore >= 16) {
                const maxModifiers = modifiers.filter((modifier) => ModifierValidators.isSetAcMaxDexModifierModifier(modifier));
                const maxArmoredModifiers = modifiers.filter((modifier) => ModifierValidators.isSetAcMaxDexArmoredModifier(modifier));
                const maxModifierPossibilities = [];
                if (maxModifiers.length) {
                    maxModifierPossibilities.push(ModifierDerivers.deriveHighestModifierValue(maxModifiers));
                }
                if (maxArmoredModifiers.length) {
                    maxModifierPossibilities.push(ModifierDerivers.deriveHighestModifierValue(maxArmoredModifiers));
                }
                if (maxModifierPossibilities.length) {
                    return Math.max(...maxModifierPossibilities);
                }
            }
            return 2;
        case ArmorTypeEnum.HEAVY_ARMOR:
            return 0;
        default:
        // not implemented
    }
    return null;
}
/**
 *
 * @param modifiers
 */
export function deriveUnarmoredDexBonusMax(modifiers) {
    const maxDexModifiers = modifiers.filter((modifier) => ModifierValidators.isSetAcMaxDexModifierModifier(modifier));
    const maxDexUnarmoredModifiers = modifiers.filter((modifier) => ModifierValidators.isSetAcMaxDexUnarmoredModifier(modifier));
    const maxDexValuePossibilities = [];
    if (maxDexModifiers.length) {
        maxDexValuePossibilities.push(ModifierDerivers.deriveHighestModifierValue(maxDexModifiers));
    }
    if (maxDexUnarmoredModifiers.length) {
        maxDexValuePossibilities.push(ModifierDerivers.deriveHighestModifierValue(maxDexUnarmoredModifiers));
    }
    if (maxDexValuePossibilities.length) {
        return Math.max(...maxDexValuePossibilities);
    }
    return null;
}
/**
 *
 * @param localModifiers
 * @param globalModifiers
 * @param abilityLookup
 */
export function deriveUnarmoredDexBonus(localModifiers, globalModifiers, abilityLookup) {
    let bonus = 0;
    if (Object.keys(abilityLookup).length) {
        const dexMod = abilityLookup[AbilityStatEnum.DEXTERITY];
        bonus = AbilityAccessors.getModifier(dexMod) || 0;
    }
    const ignoreUnarmoredDexBonusModifiers = localModifiers.filter((modifier) => ModifierValidators.isIgnoreUnarmoredDexAcBonusModifier(modifier));
    if (ignoreUnarmoredDexBonusModifiers.length) {
        return 0;
    }
    const max = deriveUnarmoredDexBonusMax(globalModifiers);
    return Math.min(bonus, max === null ? Number.MAX_VALUE : max);
}
/**
 *
 * @param armor
 * @param dexModifier
 * @param modifiers
 * @param abilityLookup
 */
export function deriveArmorDexBonus(armor, dexModifier, modifiers, abilityLookup) {
    switch (getArmorTypeId(armor)) {
        case ArmorTypeEnum.HEAVY_ARMOR:
        case ArmorTypeEnum.SHIELD:
            return 0;
    }
    const armorDexBonusMax = deriveArmorDexBonusMax(armor, modifiers, abilityLookup);
    return armorDexBonusMax === null ? dexModifier : Math.min(armorDexBonusMax, dexModifier);
}
/**
 *
 * @param armor
 * @param dexModifier
 * @param modifiers
 * @param abilityLookup
 * @param ruleData
 */
export function deriveArmorAc(armor, dexModifier, modifiers, abilityLookup, ruleData) {
    let armorAc = ruleData.noArmorAcAmount;
    if (armor) {
        const definitionArmorClass = getArmorDefinitionAc(armor);
        if (definitionArmorClass !== null) {
            armorAc = definitionArmorClass;
        }
    }
    return armorAc + deriveArmorDexBonus(armor, dexModifier, modifiers, abilityLookup);
}
/**
 *
 * @param armorItems
 * @param dexModifier
 * @param modifiers
 * @param abilityLookup
 * @param ruleData
 */
export function deriveHighestArmorAcItem(armorItems, dexModifier, modifiers, abilityLookup, ruleData) {
    const armorAc = armorItems.map((item) => {
        const armorAc = deriveArmorAc(item, dexModifier, modifiers, abilityLookup, ruleData);
        const modifierAc = deriveArmorModifierAcTotal(item);
        const definitionArmorClass = getArmorDefinitionAc(item);
        return {
            item,
            armorClassWithDex: armorAc + modifierAc,
            armorClass: (definitionArmorClass === null ? 0 : definitionArmorClass) + modifierAc,
        };
    });
    return HelperUtils.getLast(armorAc, ['armorClassWithDex']);
}
/**
 *
 * @param equippedItems
 * @param strScore
 * @param preferences
 * @param modifiers
 */
export function deriveArmorSpeedAdjustmentAmount(equippedItems, strScore, preferences, modifiers) {
    if (preferences.encumbranceType === PreferenceEncumbranceTypeEnum.VARIANT ||
        modifiers.some(ModifierValidators.isIgnoreHeavyArmorSpeedReductionModifier)) {
        return 0;
    }
    const armorItems = equippedItems.filter(isArmorContract);
    const encumberingArmorItem = armorItems.find((item) => {
        const reqStr = getItemRequiredStr(item);
        return reqStr && reqStr > strScore;
    });
    if (encumberingArmorItem) {
        return -10;
    }
    return 0;
}
/**
 *
 * @param item
 * @param globalModifiers
 * @param isPactWeapon
 * @param valueLookupByType
 * @param ruleData
 */
export function deriveIsProficient(item, globalModifiers, isPactWeapon, valueLookupByType, ruleData) {
    if (isBaseWeapon(item)) {
        if (globalModifiers.filter((modifier) => ModifierValidators.isValidWeaponProficiencyModifier(modifier, item))
            .length) {
            return true;
        }
        if (valueLookupByType.hasOwnProperty(AdjustmentTypeEnum.WEAPON_PROFICIENCY_LEVEL)) {
            const charValues = valueLookupByType[AdjustmentTypeEnum.WEAPON_PROFICIENCY_LEVEL];
            if (charValues.find((charValue) => ValueAccessors.getValueIntId(charValue) === getBaseItemId(item))) {
                return true;
            }
        }
        return isPactWeapon;
    }
    if (isBaseArmor(item)) {
        if (globalModifiers.filter((modifier) => ModifierValidators.isValidArmorProficiencyModifier(modifier, item))
            .length) {
            return true;
        }
        if (valueLookupByType.hasOwnProperty(AdjustmentTypeEnum.ARMOR_PROFICIENCY_LEVEL)) {
            const charValues = valueLookupByType[AdjustmentTypeEnum.ARMOR_PROFICIENCY_LEVEL];
            if (charValues.find((charValue) => ValueAccessors.getValueIntId(charValue) === getBaseItemId(item))) {
                return true;
            }
        }
    }
    if (!isBaseGear(item)) {
        if (getModifiers(item).filter((modifier) => ModifierValidators.isProficiencySelfModifier(modifier)).length) {
            return true;
        }
    }
    return false;
}
/**
 *
 * @param item
 * @param modifiers
 */
export function deriveIsMonkWeapon(item, modifiers) {
    if (!isWeaponContract(item)) {
        return false;
    }
    const monkWeaponModifiers = modifiers.filter((modifier) => ModifierValidators.isMonkWeaponModifier(modifier));
    return (isMonkWeapon(item) ||
        !!monkWeaponModifiers.find((modifier) => ModifierValidators.isValidWeaponSubTypeModifier(modifier, item)));
}
/**
 *
 * @param item
 * @param valueLookup
 */
export function deriveIsCustomized(item, valueLookup) {
    return isCustomItem(item)
        ? false
        : ValueValidators.validateHasCustomization(ITEM_CUSTOMIZATION_ADJUSTMENT_TYPES, valueLookup, ValueHacks.hack__toString(getMappingId(item)), ValueHacks.hack__toString(getMappingEntityTypeId(item)));
}
/**
 *
 * @param item
 */
export function deriveWeaponAdditionalDamage(item) {
    let additionalDamages = [];
    if (!isWeaponContract(item)) {
        return additionalDamages;
    }
    const itemModifiers = getModifiers(item);
    const additionalDamageModifiers = itemModifiers.filter((modifier) => ModifierValidators.isWeaponAdditionalDamageModifier(modifier));
    if (additionalDamageModifiers.length) {
        additionalDamages = additionalDamageModifiers.map((modifier) => {
            const damageValue = ModifierAccessors.getValue(modifier);
            return {
                damageType: ModifierAccessors.getFriendlySubtypeName(modifier),
                damage: damageValue ? damageValue : ModifierAccessors.getDice(modifier),
                info: ModifierAccessors.getRestriction(modifier),
                id: ModifierAccessors.getId(modifier),
            };
        });
    }
    return additionalDamages;
}
/**
 *
 * @param item
 * @param modifiers
 * @param ruleData
 */
export function deriveWeaponReach(item, modifiers, ruleData) {
    let reach = null;
    if (!isBaseWeapon(item)) {
        return reach;
    }
    if (getAttackType(item) === AttackTypeRangeEnum.MELEE) {
        const baseReach = ruleData.baseWeaponReach;
        const bonusReachModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusMeleeReachModifier(modifier));
        const bonusReachModifierTotal = ModifierDerivers.sumModifiers(bonusReachModifiers);
        let reachPropertyTotal = 0;
        if (hasWeaponProperty(item, WeaponPropertyEnum.REACH)) {
            reachPropertyTotal = ruleData.weaponPropertyReachDistance;
        }
        reach = baseReach + reachPropertyTotal + bonusReachModifierTotal;
    }
    return reach;
}
/**
 *
 * @param item
 * @param modifiers
 * @param martialArtsLevel
 */
export function deriveAvailableWeaponAbilities(item, modifiers, martialArtsLevelScale, isDedicatedWeapon, isPactWeapon, isHexWeapon) {
    const availableAbilities = [];
    const attackType = getAttackType(item);
    if (attackType === AttackTypeRangeEnum.MELEE || hasWeaponProperty(item, WeaponPropertyEnum.FINESSE)) {
        availableAbilities.push(AbilityStatEnum.STRENGTH);
    }
    if (attackType === AttackTypeRangeEnum.RANGED ||
        (martialArtsLevelScale !== null && (deriveIsMonkWeapon(item, modifiers) || isDedicatedWeapon)) ||
        hasWeaponProperty(item, WeaponPropertyEnum.FINESSE)) {
        availableAbilities.push(AbilityStatEnum.DEXTERITY);
    }
    if (isMagic(item)) {
        const magicItemBonusModifiers = modifiers.filter((modifier) => ModifierValidators.isBonusMagicItemAttackWithStatModifier(modifier));
        magicItemBonusModifiers.forEach((modifier) => {
            const entityId = ModifierAccessors.getEntityId(modifier);
            if (entityId !== null && !availableAbilities.includes(entityId)) {
                availableAbilities.push(entityId);
            }
        });
    }
    //handle PactWeapon modifiers
    if (isPactWeapon) {
        //Get the enable Pact modifier
        const enablePactModifier = modifiers.find((modifier) => ModifierValidators.isEnablePactWeaponModifier(modifier));
        //Get the origin of the enable Pact Modifier
        const pactDataOrigin = enablePactModifier && ModifierAccessors.getDataOrigin(enablePactModifier);
        if (pactDataOrigin) {
            const replacementAbilities = deriveReplacementWeaponAbilities(modifiers, pactDataOrigin);
            replacementAbilities.forEach((ability) => {
                if (!availableAbilities.includes(ability)) {
                    availableAbilities.push(ability);
                }
            });
        }
    }
    //handle HexWeapon modifiers
    if (isHexWeapon) {
        //Get the enable Hex modifier
        const enableHexModifier = modifiers.find((modifier) => ModifierValidators.isEnableHexWeaponModifier(modifier));
        //Get the origin of the enable Hex Modifier
        const hexDataOrigin = enableHexModifier && ModifierAccessors.getDataOrigin(enableHexModifier);
        if (hexDataOrigin) {
            const replacementAbilities = deriveReplacementWeaponAbilities(modifiers, hexDataOrigin);
            replacementAbilities.forEach((ability) => {
                if (!availableAbilities.includes(ability)) {
                    availableAbilities.push(ability);
                }
            });
        }
    }
    return availableAbilities;
}
export function deriveReplacementWeaponAbilities(modifiers, enableModifierDataOrigin) {
    let replacementAbilities = [];
    const replaceAbilityModifiers = modifiers.filter((modifier) => ModifierValidators.isReplaceWeaponAbilityModifier(modifier));
    if (replaceAbilityModifiers.length > 0) {
        replaceAbilityModifiers.forEach((replaceAbilityModifier) => {
            var _a, _b;
            //get the id of the Ability Stat on the replace weapon ability modifier
            const statId = ModifierAccessors.getEntityId(replaceAbilityModifier);
            //get the data origin of the replace weapon ability modifier
            const dataOrigin = ModifierAccessors.getDataOrigin(replaceAbilityModifier);
            if (statId &&
                dataOrigin.type === enableModifierDataOrigin.type &&
                ((_a = dataOrigin.primary.definition) === null || _a === void 0 ? void 0 : _a.id) === ((_b = enableModifierDataOrigin.primary.definition) === null || _b === void 0 ? void 0 : _b.id) &&
                !replacementAbilities.includes(statId)) {
                replacementAbilities.push(statId);
            }
        });
    }
    return replacementAbilities;
}
/**
 *
 * @param item
 * @param modifiers
 */
export function deriveCanWeaponOffhand(item, modifiers) {
    if (!isBaseWeapon(item)) {
        return false;
    }
    const ignoreOffhandLightRestrictionsModifiers = modifiers.filter((modifier) => ModifierValidators.isIgnoreOffhandLightRestrictionsModifier(modifier));
    let canOffhand = false;
    if (hasWeaponProperty(item, WeaponPropertyEnum.LIGHT) || ignoreOffhandLightRestrictionsModifiers.length > 0) {
        canOffhand = true;
    }
    return canOffhand;
}
/**
 *
 * @param weapon
 */
export function deriveCanPactWeapon(weapon) {
    return getAttackType(weapon) === AttackTypeRangeEnum.MELEE;
}
/**
 *
 * @param item
 * @param proficiency
 */
export function deriveCanHexWeapon(item, proficiency) {
    if (!isBaseWeapon(item) && !validateIsWeaponLike(item)) {
        return false;
    }
    return proficiency && !hasWeaponProperty(item, WeaponPropertyEnum.TWO_HANDED);
}
/**
 *
 * @param item
 * @param proficiency
 */
export function deriveCanBeDedicatedWeapon(item, proficiency) {
    if (!isBaseWeapon(item) && !validateIsWeaponLike(item)) {
        return false;
    }
    return (proficiency &&
        !hasWeaponProperty(item, WeaponPropertyEnum.HEAVY) &&
        !hasWeaponProperty(item, WeaponPropertyEnum.SPECIAL));
}
/**
 *
 * @param item
 * @param modifiers
 */
export function deriveMasteryName(item, modifiers) {
    if (getBaseTypeId(item) !== ItemBaseTypeIdEnum.WEAPON) {
        return null;
    }
    //get modifiers that are weaponmastery
    const masteryModifiers = modifiers.filter((modifier) => isValidWeaponMasteryModifier(modifier));
    //get entity id from modifier and match to item baseWeaponid
    const masteryModifier = masteryModifiers.find((modifier) => ModifierAccessors.getEntityId(modifier) === getBaseItemId(item));
    return masteryModifier ? ModifierAccessors.getFriendlySubtypeName(masteryModifier) : null;
}
/**
 *
 * @param item
 * @param valueLookup
 */
export function deriveName(item, valueLookup) {
    const override = ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.NAME_OVERRIDE, ValueHacks.hack__toString(getMappingId(item)), ValueHacks.hack__toString(getMappingEntityTypeId(item)));
    if (override !== null && override !== '') {
        return override;
    }
    const definitionName = getDefinitionName(item);
    if (definitionName === null) {
        return '';
    }
    return definitionName;
}
/**
 *
 * @param item
 * @param valueLookup
 */
export function deriveNotes(item, valueLookup) {
    const notes = ValueUtils.getKeyValue(valueLookup, AdjustmentTypeEnum.NOTES, ValueHacks.hack__toString(getMappingId(item)), ValueHacks.hack__toString(getMappingEntityTypeId(item)));
    if (notes !== null && notes !== '') {
        return notes;
    }
    return null;
}
/**
 *
 * @param item
 * @param modifiers
 * @param ruleData
 */
export function deriveProperties(item, modifiers, ruleData) {
    let properties = [];
    if (isArmorContract(item)) {
        return properties;
    }
    if (isWeaponContract(item)) {
        properties = getDefinitionProperties(item);
    }
    else if (hasItemWeaponBehaviors(item)) {
        const behavior = getItemWeaponBehaviors(item);
        const firstBehavior = behavior[0];
        properties = firstBehavior.properties === null ? [] : firstBehavior.properties;
    }
    const weaponPropertyModifiers = modifiers.filter((modifier) => ModifierValidators.isWeaponPropertyModifier(modifier));
    const weaponPropertyModifierInfos = weaponPropertyModifiers
        .map((modifier) => {
        const entityId = ModifierAccessors.getEntityId(modifier);
        if (entityId === null) {
            return null;
        }
        const configWeaponProperty = RuleDataUtils.getWeaponPropertyInfo(entityId, ruleData);
        if (configWeaponProperty === null) {
            return null;
        }
        return {
            id: configWeaponProperty.id,
            description: configWeaponProperty.description,
            name: configWeaponProperty.name,
            notes: '',
        };
    })
        .filter(TypeScriptUtils.isNotNullOrUndefined);
    properties = uniqBy([...properties, ...weaponPropertyModifierInfos], (property) => property.name);
    const ignoreWeaponPropertyModifiers = modifiers.filter((modifier) => ModifierValidators.isIgnoreWeaponPropertyModifier(modifier));
    if (ignoreWeaponPropertyModifiers.length) {
        const ignoreWeaponPropertyModifiersNames = ignoreWeaponPropertyModifiers
            .map((modifier) => ModifierAccessors.getFriendlySubtypeName(modifier))
            .filter(TypeScriptUtils.isNotNullOrUndefined);
        properties = properties.filter((property) => property.name !== null && !ignoreWeaponPropertyModifiersNames.includes(property.name));
    }
    return properties;
}
/**
 *
 * @param armorItems
 */
export function deriveValidGlobalModifiersFromArmor(armorItems) {
    if (!armorItems.length) {
        return [];
    }
    // get all armor modifiers that aren't AC bonuses
    return flatten(armorItems.map((item) => getModifiers(item).filter((modifier) => !ModifierValidators.isBonusArmorModifier(modifier) &&
        !ModifierValidators.isProficiencySelfModifier(modifier) &&
        isEquipped(item) &&
        ModifierValidators.isValidAttunedItemModifier(modifier, item))));
}
/**
 *
 * @param weaponItems
 */
export function deriveValidGlobalModifiersFromWeapons(weaponItems) {
    if (!weaponItems.length) {
        return [];
    }
    // get all weapon modifiers that aren't bonus magic
    return flatten(weaponItems.map((item) => getModifiers(item).filter((modifier) => !ModifierValidators.isBonusMagicModifier(modifier) &&
        !ModifierValidators.isProficiencySelfModifier(modifier) &&
        !ModifierValidators.isReplaceDamageTypeModifier(modifier) &&
        !ModifierValidators.isWeaponPropertyModifier(modifier) &&
        !ModifierValidators.isWeaponAdditionalDamageModifier(modifier) &&
        isEquipped(item) &&
        ModifierValidators.isValidAttunedItemModifier(modifier, item))));
}
/**
 *
 * @param gearItems
 */
export function deriveValidGlobalModifiersFromGear(gearItems) {
    if (!gearItems.length) {
        return [];
    }
    return flatten(gearItems.map((item) => getModifiers(item).filter((modifier) => isEquipped(item) &&
        (!hasItemWeaponBehaviors(item) ||
            (hasItemWeaponBehaviors(item) && !ModifierValidators.isBonusMagicModifier(modifier))) &&
        !ModifierValidators.isProficiencySelfModifier(modifier) &&
        ModifierValidators.isValidAttunedItemModifier(modifier, item))));
}
/**
 *
 * @param item
 */
export function deriveValidArmorStatModifiers(item) {
    return getModifiers(item).filter((modifier) => ModifierValidators.isBonusArmorModifier(modifier) &&
        ModifierValidators.isValidAttunedItemModifier(modifier, item));
}
/**
 *
 * @param item
 */
export function deriveArmorModifierAcTotal(item) {
    const validModifiers = deriveValidArmorStatModifiers(item);
    return ModifierDerivers.sumModifiers(validModifiers);
}
/**
 *
 * @param item
 * @param modifiers
 */
export function deriveDamageType(item, modifiers) {
    let damageType = getDefinitionDamageType(item);
    const replaceDamageTypeModifiers = modifiers.filter((modifier) => ModifierValidators.isReplaceDamageTypeModifier(modifier));
    if (replaceDamageTypeModifiers.length) {
        damageType = ModifierAccessors.getFriendlySubtypeName(replaceDamageTypeModifiers[0]);
    }
    return damageType;
}
/**
 *
 * @param item
 */
export function deriveDefinitionKey(item) {
    return DefinitionHacks.hack__generateDefinitionKey(FUTURE_ITEM_DEFINITION_TYPE, getDefinitionId(item));
}
/**
 *
 * @param infusion
 * @param valueLookup
 * @param item
 */
export function deriveInfusionActions(infusion, valueLookup, item) {
    if (infusion === null) {
        return [];
    }
    const itemLimitedUse = getLimitedUse(item);
    const baseActions = ActionGenerators.generateDataOriginBaseActions(InfusionAccessors.getDefinitionActions(infusion), valueLookup, DataOriginTypeEnum.ITEM, item);
    return baseActions.map((action) => {
        const actionLimitedUse = ActionAccessors.getLimitedUse(action);
        // TODO I don't like this but im not sure how to fix it. and moving on because of time
        // possibly calculate in ActionDerivers.deriveLimitedUse
        if (actionLimitedUse !== null) {
            return Object.assign(Object.assign({}, action), { limitedUse: Object.assign(Object.assign({}, actionLimitedUse), { numberUsed: itemLimitedUse === null ? 0 : LimitedUseAccessors.getNumberUsed(itemLimitedUse) }) });
        }
        return action;
    });
}
/**
 *
 * @param infusionActions
 * @param itemLimitedUse
 */
export function deriveLimitedUse(infusionActions, itemLimitedUse) {
    // if we have no infusion actions, we will just keep what we have
    if (infusionActions.length === 0) {
        return itemLimitedUse;
    }
    // This is for when we updated charges used to null, but still have limited use object
    if (itemLimitedUse !== null && LimitedUseAccessors.getNumberUsed(itemLimitedUse) === null) {
        return null;
    }
    const limitedUseInfusionAction = infusionActions.find((action) => ActionAccessors.getLimitedUse(action) !== null);
    if (!limitedUseInfusionAction) {
        return itemLimitedUse;
    }
    // We made assumption that all of the limited use must be the same for all actions on items
    const actionLimitedUse = ActionAccessors.getLimitedUse(limitedUseInfusionAction);
    if (actionLimitedUse) {
        return {
            resetType: actionLimitedUse.resetType === null ? null : actionLimitedUse.resetType.toString(),
            resetTypeDescription: null,
            maxUses: actionLimitedUse.maxUses,
            numberUsed: itemLimitedUse === null ? 0 : itemLimitedUse.numberUsed,
        };
    }
    return itemLimitedUse;
}
/**
 *
 * @param item
 * @param infusion
 */
export function deriveIsMagic(item, infusion) {
    if (infusion) {
        return true;
    }
    return getDefinitionMagic(item);
}
/**
 *
 * @param item
 */
export function deriveDefaultIsDisplayAsAttack(item) {
    if (!isEquipped(item)) {
        return false;
    }
    return validateIsWeaponLike(item);
}
/**
 *
 * @param item
 * @param ruleData
 */
export function deriveType(item, ruleData) {
    if (isArmorContract(item)) {
        const armorTypeId = getArmorTypeId(item);
        if (armorTypeId !== null) {
            const armorType = HelperUtils.lookupDataOrFallback(RuleDataAccessors.getArmorTypeLookup(ruleData), armorTypeId);
            if (armorType !== null) {
                return armorType.name;
            }
        }
    }
    else if (isWeaponContract(item)) {
        if (isAmmunition(item)) {
            return 'Ammunition';
        }
        const weapon = RuleDataUtils.getWeaponByEntityId(getBaseItemId(item), getBaseTypeId(item), ruleData);
        if (weapon !== null) {
            return weapon.name;
        }
    }
    else if (isGearContract(item)) {
        if (!getDefinitionMagic(item)) {
            return 'Gear';
        }
        const gearTypeId = getGearTypeId(item);
        switch (gearTypeId) {
            case ItemTypeEnum.ARMOR:
                return 'Armor';
            case ItemTypeEnum.ARTIFACT:
                return 'Artifact';
            case ItemTypeEnum.POTION:
                return 'Potion';
            case ItemTypeEnum.RING:
                return 'Ring';
            case ItemTypeEnum.ROD:
                return 'Rod';
            case ItemTypeEnum.SCROLL:
                return 'Scroll';
            case ItemTypeEnum.STAFF:
                return 'Staff';
            case ItemTypeEnum.WAND:
                return 'Wand';
            case ItemTypeEnum.WEAPON:
                return 'Weapon';
            case ItemTypeEnum.WONDROUS_ITEM:
                return 'Wondrous item';
            default:
            // not implemented
        }
    }
    else if (isCustomItem(item)) {
        return 'Custom';
    }
    return null;
}
/**
 *
 * @param item
 * @param notes
 */
export function deriveOriginalCustomContract(item, notes) {
    if (isCustomItem(item)) {
        return {
            id: getDefinitionId(item),
            name: getDefinitionName(item),
            weight: getWeightDefinition(item),
            cost: getDefinitionCost(item),
            quantity: getQuantity(item),
            description: getDescription(item),
            notes,
        };
    }
    return null;
}
export function derivePrimarySourceCategoryId(item, ruleData) {
    var _a;
    const primarySources = getSources(item).filter(CharacterValidators.isPrimarySource);
    if (!primarySources.length) {
        return 0;
    }
    const source = HelperUtils.lookupDataOrFallback(RuleDataAccessors.getSourceDataLookup(ruleData), primarySources[0].sourceId);
    return ((_a = source === null || source === void 0 ? void 0 : source.sourceCategory) === null || _a === void 0 ? void 0 : _a.id) || 0;
}
export function deriveSecondarySourceCategoryIds(item, ruleData) {
    const secondarySources = getSources(item).filter(CharacterValidators.isSecondarySource);
    return secondarySources.map((source) => {
        var _a;
        const sourceData = HelperUtils.lookupDataOrFallback(RuleDataAccessors.getSourceDataLookup(ruleData), source.sourceId);
        return ((_a = sourceData === null || sourceData === void 0 ? void 0 : sourceData.sourceCategory) === null || _a === void 0 ? void 0 : _a.id) || 0;
    });
}
