/******/ (() => { // webpackBootstrap
/******/ 	"use strict";
var __webpack_exports__ = {};

;// CONCATENATED MODULE: ./src/constants.js
const debouncedReload = foundry.utils.debounce(() => window.location.reload(), 100);

const CONSTANTS = {
  MODULE_NAME: "pathmuncher",
  MODULE_FULL_NAME: "Pathmuncher",
  FLAG_NAME: "pathmuncher",
  SETTINGS: {
    // Enable options
    LOG_LEVEL: "log-level",
    RESTRICT_TO_TRUSTED: "restrict-to-trusted",
    ADD_VISION_FEATS: "add-vision-feats",
    USE_CUSTOM_COMPENDIUM_MAPPINGS: "use-custom-compendium-mappings",
    CUSTOM_COMPENDIUM_MAPPINGS: "custom-compendium-mappings",
    USE_IMMEDIATE_DEEP_DIVE: "use-immediate-deep-dive",
  },

  FEAT_PRIORITY: [
    "Heritage",
    "Heritage Feat",
    "Ancestry",
    "Ancestry Feat",
    "Background",
    "Background Feat",
    "Class Feat",
    "Skill Feat",
    "General Feat",
    "Awarded Feat",
  ],

  ACTOR_FLAGS: {
    pathbuilderId: undefined,
    addFeats: true,
    addEquipment: true,
    addBackground: true,
    addHeritage: true,
    addAncestry: true,
    addSpells: true,
    adjustBlendedSlots: true,
    addMoney: true,
    addTreasure: true,
    addLores: true,
    addWeapons: true,
    addArmor: true,
    addDeity: true,
    addName: true,
    addClass: true,
    addFamiliars: true,
    addFormulas: true,
  },

  CORE_COMPENDIUM_MAPPINGS: {
    feats: [
      "pf2e.feats-srd",
      "pf2e-playtest-data.war-of-immortals-playtest-class-feats",
      "pf2e-legacy-content.feats-legacy",
    ],
    ancestryFeatures: ["pf2e.ancestryfeatures", "pf2e-legacy-content.ancestry-features-legacy"],
    classFeatures: [
      "pf2e.classfeatures",
      "pf2e-playtest-data.war-of-immortals-playtest-class-features",
      "pf2e-legacy-content.class-features-legacy",
    ],
    actions: [
      "pf2e.actionspf2e",
      "pf2e-playtest-data.war-of-immortals-playtest-actions",
      "pf2e-legacy-content.actions-legacy",
    ],
    spells: [
      "pf2e-psychic-amps.psychic-psi-cantrips",
      "pf2e.spells-srd",
      "pf2e-playtest-data.war-of-immortals-playtest-spells",
      "pf2e-legacy-content.spells-legacy",
    ],
    classes: [
      "pf2e.classes",
      "pf2e-playtest-data.war-of-immortals-playtest-classes",
      "pf2e-legacy-content.classes-legacy",
    ],
    ancestries: ["pf2e.ancestries", "pf2e-legacy-content.ancestries-legacy"],
    heritages: ["pf2e.heritages", "pf2e-legacy-content.heritages-legacy"],
    equipment: ["pf2e.equipment-srd", "pf2e-legacy-content.equipment-legacy"],
    formulas: ["pf2e.equipment-srd", "pf2e-legacy-content.formulas-legacy"],
    deities: ["pf2e.deities", "pf2e-legacy-content.deities-legacy"],
    backgrounds: ["pf2e.backgrounds", "pf2e-legacy-content.backgrounds-legacy"],
  },

  GET_DEFAULT_SETTINGS() {
    return foundry.utils.deepClone(CONSTANTS.DEFAULT_SETTINGS);
  },
};

CONSTANTS.DEFAULT_SETTINGS = {
  // Enable options
  [CONSTANTS.SETTINGS.RESTRICT_TO_TRUSTED]: {
    name: `${CONSTANTS.FLAG_NAME}.Settings.RestrictToTrusted.Name`,
    hint: `${CONSTANTS.FLAG_NAME}.Settings.RestrictToTrusted.Hint`,
    scope: "world",
    config: true,
    type: Boolean,
    default: false,
    onChange: debouncedReload,
  },

  [CONSTANTS.SETTINGS.USE_CUSTOM_COMPENDIUM_MAPPINGS]: {
    name: `${CONSTANTS.FLAG_NAME}.Settings.UseCustomCompendiumMappings.Name`,
    scope: "world",
    config: false,
    type: Boolean,
    default: false,
  },

  [CONSTANTS.SETTINGS.USE_IMMEDIATE_DEEP_DIVE]: {
    name: `${CONSTANTS.FLAG_NAME}.Settings.UseImmediateDeepDive.Name`,
    scope: "world",
    config: false,
    type: Boolean,
    default: true,
  },

  [CONSTANTS.SETTINGS.CUSTOM_COMPENDIUM_MAPPINGS]: {
    scope: "world",
    config: false,
    type: Object,
    default: {
      feats: [
        "battlezoo-ancestries-dragons-pf2e.pf2e-battlezoo-dragon-feats",
        "battlezoo-ancestries-year-of-monsters-pf2e.yom-features",
        "battlezoo-ancestries-year-of-monsters-pf2e.yom-feats",
        "clerics.clerics-feats",
        "clerics.clerics-features",
        "pf2e.feats-srd",
        "pf2e-playtest-data.war-of-immortals-playtest-class-feats",
      ],
      ancestryFeatures: [
        "battlezoo-ancestries-year-of-monsters-pf2e.yom-features",
        "pf2e.ancestryfeatures",
      ],
      classFeatures: [
        "battlezoo-ancestries-year-of-monsters-pf2e.yom-features",
        "battlezoo-ancestries-dragons-pf2e.pf2e-battlezoo-dragon-feats",
        "battlezoo-ancestries-year-of-monsters-pf2e.yom-feats",
        "clerics.clerics-doctrines",
        "clerics.clerics-feats",
        "clerics.clerics-features",
        "pf2e.classfeatures",
        "pf2e-playtest-data.war-of-immortals-playtest-class-features",
      ],
      actions: ["pf2e.actionspf2e", "pf2e-playtest-data.war-of-immortals-playtest-actions"],
      spells: ["pf2e-psychic-amps.psychic-psi-cantrips", "pf2e.spells-srd", "pf2e-playtest-data.war-of-immortals-playtest-spells"],
      classes: ["clerics.clerics-features", "pf2e.classes", "pf2e-playtest-data.war-of-immortals-playtest-classes"],
      ancestries: [
        "battlezoo-ancestries-dragons-pf2e.pf2e-battlezoo-dragon-ancestry",
        "battlezoo-ancestries-year-of-monsters-pf2e.yom-ancestries",
        "pf2e.ancestries",
      ],
      heritages: [
        "battlezoo-ancestries-dragons-pf2e.pf2e-battlezoo-dragon-heritages",
        "battlezoo-ancestries-year-of-monsters-pf2e.yom-heritages",
        "pf2e.heritages",
      ],
      equipment: [
        "battlezoo-ancestries-dragons-pf2e.pf2e-battlezoo-dragon-equipment",
        "battlezoo-ancestries-year-of-monsters-pf2e.yom-equipment",
        "pf2e.equipment-srd"
      ],
      formulas: ["pf2e.equipment-srd"],
      deities: ["clerics.clerics-deities", "pf2e.deities"],
      backgrounds: ["pf2e.backgrounds"],
    },
  },

  [CONSTANTS.SETTINGS.ADD_VISION_FEATS]: {
    name: `${CONSTANTS.FLAG_NAME}.Settings.AddVisionFeats.Name`,
    hint: `${CONSTANTS.FLAG_NAME}.Settings.AddVisionFeats.Hint`,
    scope: "player",
    config: true,
    type: Boolean,
    default: false,
  },

  // debug
  [CONSTANTS.SETTINGS.LOG_LEVEL]: {
    name: `${CONSTANTS.FLAG_NAME}.Settings.LogLevel.Name`,
    hint: `${CONSTANTS.FLAG_NAME}.Settings.LogLevel.Hint`,
    scope: "world",
    config: true,
    type: String,
    choices: {
      DEBUG: `${CONSTANTS.FLAG_NAME}.Settings.LogLevel.debug`,
      INFO: `${CONSTANTS.FLAG_NAME}.Settings.LogLevel.info`,
      WARN: `${CONSTANTS.FLAG_NAME}.Settings.LogLevel.warn`,
      ERR: `${CONSTANTS.FLAG_NAME}.Settings.LogLevel.error`,
      OFF: `${CONSTANTS.FLAG_NAME}.Settings.LogLevel.off`,
    },
    default: "WARN",
  }

};

CONSTANTS.PATH = `modules/${CONSTANTS.MODULE_NAME}`;

/* harmony default export */ const constants = (CONSTANTS);

;// CONCATENATED MODULE: ./src/utils.js


const utils = {

  isObject: (obj) => {
    return typeof obj === 'object' && !Array.isArray(obj) && obj !== null;
  },

  isString: (str) => {
    return typeof str === 'string' || str instanceof String;
  },

  wait: async (ms) => {
    return new Promise((resolve) => {
      setTimeout(resolve, ms);
    });
  },

  capitalize: (s) => {
    if (typeof s !== "string") return "";
    return s.charAt(0).toUpperCase() + s.slice(1);
  },

  setting: (key) => {
    return game.settings.get(constants.MODULE_NAME, constants.SETTINGS[key]);
  },

  updateSetting: async (key, value) => {
    return game.settings.set(constants.MODULE_NAME, constants.SETTINGS[key], value);
  },

  getFlags: (actor) => {
    const flags = actor.flags[constants.FLAG_NAME]
      ? actor.flags[constants.FLAG_NAME]
      : constants.ACTOR_FLAGS;
    return flags;
  },

  setFlags: async (actor, flags) => {
    let updateData = {};
    setProperty(updateData, `flags.${constants.FLAG_NAME}`, flags);
    await actor.update(updateData);
    return actor;
  },

  resetFlags: async (actor) => {
    return utils.setFlags(actor, null);
  },


  getOrCreateFolder: async (root, entityType, folderName, folderColor = "") => {
    let folder = game.folders.contents.find((f) =>
      f.type === entityType && f.name === folderName
      // if a root folder we want to match the root id for the parent folder
      && (root ? root.id : null) === (f.folder?.id ?? null)
    );
    // console.warn(`Looking for ${root} ${entityType} ${folderName}`);
    // console.warn(folder);
    if (folder) return folder;
    folder = await Folder.create(
      {
        name: folderName,
        type: entityType,
        color: folderColor,
        parent: (root) ? root.id : null,
      },
      { displaySheet: false }
    );
    return folder;
  },

  // eslint-disable-next-line no-unused-vars
  getFolder: async (kind, subFolder = "", baseFolderName = "Pathmuncher", baseColor = "#6f0006", subColor = "#98020a", typeFolder = true) => {
    let entityTypes = new Map();
    entityTypes.set("pets", "Pets");

    const folderName = game.i18n.localize(`${constants.MODULE_NAME}.labels.${kind}`);
    const entityType = entityTypes.get(kind);
    const baseFolder = await utils.getOrCreateFolder(null, entityType, baseFolderName, baseColor);
    const entityFolder = typeFolder ? await utils.getOrCreateFolder(baseFolder, entityType, folderName, subColor) : baseFolder;
    if (subFolder !== "") {
      const subFolderName = subFolder.charAt(0).toUpperCase() + subFolder.slice(1);
      const typeFolder = await utils.getOrCreateFolder(entityFolder, entityType, subFolderName, subColor);
      return typeFolder;
    } else {
      return entityFolder;
    }
  },

  allowDualClasses: () => {
    return (isNewerVersion("5.9.0", game.version) && game.settings.get("pf2e", "dualClassVariant"));
    // || (!isNewerVersion("5.9.0", game.version) && when remaster supports dualclass then add here
  },

  allowAncestryParagon: () => {
    return (isNewerVersion("5.9.0", game.version) && game.settings.get("pf2e", "ancestryParagonVariant"));
  }

};


/* harmony default export */ const src_utils = (utils);

;// CONCATENATED MODULE: ./src/logger.js



const logger = {
  _showMessage: (logLevel, data) => {
    if (!logLevel || !data || typeof logLevel !== "string") {
      return false;
    }

    const setting = src_utils.setting("LOG_LEVEL");
    const logLevels = ["DEBUG", "INFO", "WARN", "ERR", "OFF"];
    const logLevelIndex = logLevels.indexOf(logLevel.toUpperCase());
    if (setting == "OFF" || logLevelIndex === -1 || logLevelIndex < logLevels.indexOf(setting)) {
      return false;
    }
    return true;
  },
  log: (logLevel, ...data) => {
    if (!logger._showMessage(logLevel, data)) {
      return;
    }

    logLevel = logLevel.toUpperCase();

    let msg = "No logging message provided.  Please see the payload for more information.";
    let payload = data.slice();
    if (data[0] && typeof (data[0] == "string")) {
      msg = data[0];
      if (data.length > 1) {
        payload = data.slice(1);
      } else {
        payload = null;
      }
    }
    msg = `${constants.MODULE_NAME} | ${logLevel} > ${msg}`;
    switch (logLevel) {
      case "DEBUG":
        if (payload) {
          console.debug(msg, ...payload); // eslint-disable-line no-console
        } else {
          console.debug(msg); // eslint-disable-line no-console
        }
        break;
      case "INFO":
        if (payload) {
          console.info(msg, ...payload); // eslint-disable-line no-console
        } else {
          console.info(msg); // eslint-disable-line no-console
        }
        break;
      case "WARN":
        if (payload) {
          console.warn(msg, ...payload); // eslint-disable-line no-console
        } else {
          console.warn(msg); // eslint-disable-line no-console
        }
        break;
      case "ERR":
        if (payload) {
          console.error(msg, ...payload); // eslint-disable-line no-console
        } else {
          console.error(msg); // eslint-disable-line no-console
        }
        break;
      default:
        break;
    }
  },

  debug: (...data) => {
    logger.log("DEBUG", ...data);
  },

  info: (...data) => {
    logger.log("INFO", ...data);
  },

  warn: (...data) => {
    logger.log("WARN", ...data);
  },

  error: (...data) => {
    logger.log("ERR", ...data);
  },
};
/* harmony default export */ const src_logger = (logger);

;// CONCATENATED MODULE: ./src/data/equipment.js
const SWAPS = [
  /^(Greater) (.*)/,
  /^(Lesser) (.*)/,
  /^(Major) (.*)/,
  /^(Moderate) (.*)/,
  /^(Standard) (.*)/,
];

const POSTFIX_PB_REMOVALS = [
  /(.*) (- Melee)$/,
  /(.*) (- Ranged)$/,
  /(Charm of Resistance .*) - (.*)/,
];

const PARENTHESIS = [
  /^(.*) \((.*)\)$/,
];

const SPLITS = [
  // /^(.*) - (.*)/,
];

const SPLITS_INVERT = [
  /^(.*): (.*)/,
];

const REPLACES = [
  { pbName: "Ring of Energy Resistance", foundryName: "Charm of Resistance" },
  { pbName: "Feather Token", foundryName: "Marvelous Miniatures" },
  { pbName: "Goggles of Night", foundryName: "Obsidian Goggles" },
];

// this equipment is named differently in foundry vs pathbuilder
const EQUIPMENT_RENAME_STATIC_MAP = [
  { pbName: "Inventor Power Suit", foundryName: "Power Suit" },
  { pbName: "Inventor Power Suit (Heavy)", foundryName: "Power Suit" },
  { pbName: "Inventor Subterfuge Suit", foundryName: "Subterfuge Suit" },
  { pbName: "Chain", foundryName: "Chain (10 feet)" },
  { pbName: "Oil", foundryName: "Oil (1 pint)" },
  { pbName: "Bracelets of Dashing", foundryName: "Bracelet of Dashing" },
  { pbName: "Fingerprinting Kit", foundryName: "Fingerprint Kit" },
  { pbName: "Ladder", foundryName: "Ladder (10-foot)" },
  { pbName: "Mezmerizing Opal", foundryName: "Mesmerizing Opal" },
  // { pbName: "Explorer's Clothing", foundryName: "Clothing (Explorer's)" },
  { pbName: "Flaming Star (Greater)", foundryName: "Greater Flaming Star" },
  { pbName: "Potion of Lesser Darkvision", foundryName: "Darkvision Elixir (Lesser)" },
  { pbName: "Potion of Greater Darkvision", foundryName: "Darkvision Elixir (Greater)" },
  { pbName: "Potion of Moderate Darkvision", foundryName: "Darkvision Elixir (Moderate)" },
  { pbName: "Bottled Sunlight", foundryName: "Formulated Sunlight" },
  { pbName: "Magazine (Repeating Hand Crossbow)", foundryName: "Magazine with 5 Bolts" },
  { pbName: "Astrolabe (Standard)", foundryName: "Standard Astrolabe" },
  { pbName: "Skinitch Salve", foundryName: "Skinstitch Salve" },
  { pbName: "Flawless Scale", foundryName: "Abadar's Flawless Scale" },
  { pbName: "Construct Key", foundryName: "Cordelia's Construct Key" },
  { pbName: "Construct Key (Greater)", foundryName: "Cordelia's Greater Construct Key" },
  { pbName: "Lesser Swapping Stone", foundryName: "Lesser Bonmuan Swapping Stone" },
  { pbName: "Major Swapping Stone", foundryName: "Major Bonmuan Swapping Stone" },
  { pbName: "Moderate Swapping Stone", foundryName: "Moderate Bonmuan Swapping Stone" },
  { pbName: "Greater Swapping Stone", foundryName: "Greater Bonmuan Swapping Stone" },
  { pbName: "Heartstone", foundryName: "Skarja's Heartstone" },
  { pbName: "Bullets (10 rounds)", foundryName: "Sling Bullets" },
  { pbName: "Hide", foundryName: "Hide Armor" },
  { pbName: "Soverign Glue", foundryName: "Sovereign Glue" },
  { pbName: "Axe Musket - Melee", foundryName: "Axe Musket" },
  { pbName: "Axe Musket - Ranged", foundryName: "Axe Musket" },
  { pbName: "Extendible Pincer", foundryName: "Extendable Pincer" },
  { pbName: "Clothing (Explorer's)", foundryName: "Explorer's Clothing" },
  { pbName: "Street Preacher [Placeholder]", foundryName: "Street Preacher" },
  { pbName: "Repair Kit", foundryName: "Repair Toolkit" },
  { pbName: "Repair Kit (Superb)", foundryName: "Repair Toolkit (Superb)" },
  { pbName: "Alchemist's Tools", foundryName: "Alchemist's Toolkit" },
  { pbName: "Healer's Tools", foundryName: "Healer's Toolkit" },
  { pbName: "Healer's Tools (Expanded)", foundryName: "Healer's Toolkit (Expanded)" },
  { pbName: "Thieves' Tools", foundryName: "Thieves' Toolkit" },
  { pbName: "Thieves' Tools (Infiltrator)", foundryName: "Thieves' Toolkit (Infiltrator)" },
  { pbName: "Thieves' Tools (Infiltrator Picks)", foundryName: "Thieves' Toolkit (Infiltrator Picks)" },
  { pbName: "Artisan's Tools", foundryName: "Artisan's Toolkit" },
  { pbName: "Artisan's Tools (Sterling)", foundryName: "Artisan's Toolkit (Sterling)" },

  { pbName: "Aeon Stone (Dull Grey)", foundryName: "Aeon Stone (Consumed)" },
  { pbName: "Aeon Stone (Clear Spindle)", foundryName: "Aeon Stone (Nourishing)" },
  { pbName: "Aeon Stone (Tourmaline Sphere)", foundryName: "Aeon Stone (Delaying)" },
  { pbName: "Aeon Stone (Orange Prism)", foundryName: "Aeon Stone (Amplifying)" },
  { pbName: "Bag of Holding", foundryName: "Spacious Pouch" },
  { pbName: "Barkskin Potion", foundryName: "Oak Potion" },
  { pbName: "Boots of Speed", foundryName: "Propulsive Boots" },
  { pbName: "Bracers of Armor", foundryName: "Bands of Force" },
  { pbName: "Broom of Flying", foundryName: "Flying Broomstick" },
  { pbName: "Dagger of Venom", foundryName: "Serpent Dagger" },
  // these are actually matched to energy type witch Pathbuilder does not support
  { pbName: "Dragon's Breath Potion (Young)", foundryName: "Energy Breath Potion (Lesser)" },
  { pbName: "Dragon's Breath Potion (Adult)", foundryName: "Energy Breath Potion (Moderate)" },
  { pbName: "Dragon's Breath Potion (Wyrm)", foundryName: "Energy Breath Potion (Greater)" },
  { pbName: "Druid's Vestments", foundryName: "Living Mantle" },
  { pbName: "Everburning Torch", foundryName: "Everlight Crystal" },
  { pbName: "Eyes of the Eagle", foundryName: "Eyes of the Cat" },
  { pbName: "Feather Token (Chest)", foundryName: "Marvelous Miniatures (Chest)" },
  { pbName: "Feather Token (Ladder)", foundryName: "Marvelous Miniatures (Ladder)" },
  { pbName: "Feather Token (Swan Boat)", foundryName: "Marvelous Miniatures (Boat)" },
  { pbName: "Flame Tongue", foundryName: "Searing Blade" },
  { pbName: "Gloves of Storing", foundryName: "Retrieval Belt" },
  { pbName: "Goggles of Night", foundryName: "Obsidian Goggles" },
  { pbName: "Goggles of Night (Greater)", foundryName: "Obsidian Goggles (Greater)" },
  { pbName: "Goggles of Night (Major)", foundryName: "Obsidian Goggles (Major)" },
  { pbName: "Hat of Disguise", foundryName: "Masquerade Scarf" },
  { pbName: "Hat of Disguise (Greater)", foundryName: "Masquerade Scarf (Greater)" },
  { pbName: "Horn of Fog", foundryName: "Cloud Pouch" },
  { pbName: "Horseshoes of Speed", foundryName: "Alacritous Horseshoes" },
  { pbName: "Javelin of Lightning", foundryName: "Trident of Lightning" },
  { pbName: "Potion of Expeditious Retreat", foundryName: "Potion of Emergency Escape" },
  { pbName: "Ring of Energy Resistance (Greater)", foundryName: "Charm of Resistance (Greater)" },
  { pbName: "Ring of Energy Resistance (Major)", foundryName: "Charm of Resistance (Major)" },
  { pbName: "Silversheen", foundryName: "Silver Salve" },
  { pbName: "Smokestick (Lesser)", foundryName: "Smoke Ball (Lesser)" },
  { pbName: "Smokestick (Greater)", foundryName: "Smoke Ball (Greater)" },
  { pbName: "Sunrod", foundryName: "Glow Rod" },
  { pbName: "Tanglefoot Bag (Lesser)", foundryName: "Glue Bomb (Lesser)" },
  { pbName: "Tanglefoot Bag (Moderate)", foundryName: "Glue Bomb (Moderate)" },
  { pbName: "Tanglefoot Bag (Major)", foundryName: "Glue Bomb (Major)" },
  { pbName: "Tanglefoot Bag (Greater)", foundryName: "Glue Bomb (Greater)" },
  { pbName: "Tindertwig", foundryName: "Matchstick" },
  { pbName: "Owlbear Claw", foundryName: "Predator's Claw" },
  { pbName: "Wand of Manifold Missiles", foundryName: "Wand of Shardstorm" },
  { pbName: "Wand of Manifold Missiles (1st-Level Spell)", foundryName: "Wand of Shardstorm (1st-Rank Spell)" },
  { pbName: "Wand of Manifold Missiles (3rd-Level Spell)", foundryName: "Wand of Shardstorm (3rd-Rank Spell)" },
  { pbName: "Wand of Manifold Missiles (5th-Level Spell)", foundryName: "Wand of Shardstorm (5th-Rank Spell)" },
  { pbName: "Wand of Manifold Missiles (7th-Level Spell)", foundryName: "Wand of Shardstorm (7th-Rank Spell)" },

];

function dynamicNamesSteps(pbName) {
  const result = [];
  for (const reg of POSTFIX_PB_REMOVALS) {
    const match = pbName.match(reg);
    if (match) {
      result.push({ pbName, foundryName: match[1], details: match[2] });
    }
  }
  for (const reg of SWAPS) {
    const match = pbName.match(reg);
    if (match) {
      result.push({ pbName, foundryName: `${match[2]} (${match[1]})`, details: match[2] });
    }
  }
  for (const reg of SPLITS) {
    const match = pbName.match(reg);
    if (match) {
      result.push({ pbName, foundryName: match[2], details: match[1] });
    }
  }
  for (const reg of SPLITS_INVERT) {
    const match = pbName.match(reg);
    if (match) {
      result.push({ pbName, foundryName: match[1], details: match[2] });
    }
  }
  for (const reg of PARENTHESIS) {
    const match = pbName.match(reg);
    if (match) {
      result.push({ pbName, foundryName: match[1], details: match[2] });
    }
  }
  return result;
}

function generateDynamicNames(pbName) {
  const result = [];
  // if we have a hardcoded map, don't return here
  const basicResults = EQUIPMENT_RENAME_STATIC_MAP.filter((e) => e.pbName === pbName);
  if (basicResults.length > 0) {
    result.push(...basicResults);
  }

  for (const replace of REPLACES) {
    if (pbName.includes(replace.pbName)) {
      const replaced = pbName.replace(replace.pbName, replace.foundryName);
      result.push(...dynamicNamesSteps(replaced));
      result.push({ pbName, foundryName: replaced });
    }
  }

  if (result.length > 0) {
    return result;
  }

  result.push(...dynamicNamesSteps(pbName));
  return result;
}


function EQUIPMENT_RENAME_MAP(pbName = null) {
  const postfixNames = pbName ? generateDynamicNames(pbName) : [];
  return postfixNames.concat(EQUIPMENT_RENAME_STATIC_MAP);
}


// this is equipment is special and shouldn't have the transformations applied to it
const RESTRICTED_EQUIPMENT = [
  "Bracers of Armor",
];

const IGNORED_EQUIPMENT = [
  "Unarmored",
  "Inventor Power Suit",
  "Inventor Power Suit (Heavy)",
  "Inventor Subterfuge Suit",
];

const IGNORED_DISPLAY_POSTFIX = [
  /(.*) - Melee$/,
  /(.*) - Ranged$/,
];

function IGNORED_EQUIPMENT_DISPLAY(pbName) {
  for (const reg of IGNORED_DISPLAY_POSTFIX) {
    const match = reg.test(pbName);
    if (match === true) return true;
  }
  return false;
}

const GRANTED_ITEMS_LIST = [
  "Inventor Power Suit",
  "Inventor Power Suit (Heavy)",
  "Inventor Subterfuge Suit",
];

;// CONCATENATED MODULE: ./src/data/features.js
// these are features which are named differently in pathbuilder to foundry



const SKILL_LOOKUP = {
  "acrobatics": "acr",
  "arcana": "arc",
  "athletics": "ath",
  "crafting": "cra",
  "deception": "dec",
  "diplomacy": "dip",
  "intimidation": "itm",
  "medicine": "med",
  "nature": "nat",
  "occultism": "occ",
  "performance": "prf",
  "religion": "rel",
  "society": "soc",
  "stealth": "ste",
  "survival": "sur",
  "thievery": "thi",
};

const features_POSTFIX_PB_REMOVALS = [
  /(.*) (Racket)$/,
  /(.*) (Style)$/,
  /(.*) (Initiate Benefit)$/,
  /(.*) Mystery$/,
  // Cleric +
  /(.*) (Doctrine)$/,
  /(.*) (Element)$/,
  /(.*) (Impulse Junction)$/,
  /(.*) (Gate Junction:).*$/,
  /(.*) (Patron)$/,
];

const PREFIX_PB_REMOVALS = [
  /^(Arcane Thesis): (.*)/,
  /^(Arcane School): (.*)/,
  /^(The) (.*)/,
  // Cleric +
  /^(Blessing): (.*)/,
  /^(Empiricism) Selected Skill: (.*)/,
];

const POSTFIX_PB_SPLIT_AND_KEEP = [
  /(.*) (Impulse Junction)$/,
  /(.*) Gate Junction: (.*)$/,
];

const features_PARENTHESIS = [
  /^(.*) \((.*)\)$/,
];

const features_SPLITS = [
  /^(.*): (.*)/,
];

const features_SWAPS = [
  /^(Greater) (.*)/,
  /^(Lesser) (.*)/,
  /^(Major) (.*)/,
  /^(Moderate) (.*)/,
  /^(Standard) (.*)/,
];

const FEAT_RENAME_STATIC_MAP = [
  { pbName: "Academic", foundryName: "Ustalavic Academic" },
  { pbName: "Academic (Arcana)", foundryName: "Magaambya Academic" },
  { pbName: "Academic (Nature)", foundryName: "Magaambya Academic" },
  { pbName: "Aerialist", foundryName: "Shory Aerialist" },
  { pbName: "Aeromancer", foundryName: "Shory Aeromancer" },
  { pbName: "Ancient-Blooded", foundryName: "Ancient-Blooded Dwarf" },
  { pbName: "Antipaladin [Chaotic Evil]", foundryName: "Antipaladin" },
  { pbName: "Ape", foundryName: "Ape Animal Instinct" },
  { pbName: "Aquatic Eyes (Darkvision)", foundryName: "Aquatic Eyes" },
  { pbName: "Astrology", foundryName: "Saoc Astrology" },
  { pbName: "Battle Ready", foundryName: "Battle-Ready Orc" },
  { pbName: "Bite (Gnoll)", foundryName: "Bite" },
  { pbName: "Bloodline: Genie (Efreeti)", foundryName: "Bloodline: Genie" },
  { pbName: "Bloody Debilitations", foundryName: "Bloody Debilitation" },
  { pbName: "Canoneer", foundryName: "Cannoneer" },
  { pbName: "Cave Climber Kobold", foundryName: "Caveclimber Kobold" },
  { pbName: "Child of Squalor", foundryName: "Child of the Puddles" },
  { pbName: "Chosen One", foundryName: "Chosen of Lamashtu" },
  { pbName: "Cognative Mutagen (Greater)", foundryName: "Cognitive Mutagen (Greater)" },
  { pbName: "Cognative Mutagen (Lesser)", foundryName: "Cognitive Mutagen (Lesser)" },
  { pbName: "Cognative Mutagen (Major)", foundryName: "Cognitive Mutagen (Major)" },
  { pbName: "Cognative Mutagen (Moderate)", foundryName: "Cognitive Mutagen (Moderate)" },
  { pbName: "Cognitive Crossover", foundryName: "Kreighton's Cognitive Crossover" },
  { pbName: "Collegiate Attendant Dedication", foundryName: "Magaambyan Attendant Dedication" },
  { pbName: "Construct Carver", foundryName: "Tupilaq Carver" },
  { pbName: "Cunning Stance", foundryName: "Devrin's Cunning Stance" },
  { pbName: "Constructed (Android)", foundryName: "Constructed" },
  { pbName: "Dazzling Diversion", foundryName: "Devrin's Dazzling Diversion" },
  { pbName: "Deadly Hair", foundryName: "Syu Tak-nwa's Deadly Hair" },
  { pbName: "Deepvision", foundryName: "Deep Vision" },
  { pbName: "Deflect Arrows", foundryName: "Deflect Arrow" },
  { pbName: "Desecrator [Neutral Evil]", foundryName: "Desecrator" },
  { pbName: "Detective Dedication", foundryName: "Edgewatch Detective Dedication" },
  { pbName: "Duelist Dedication (LO)", foundryName: "Aldori Duelist Dedication" },
  { pbName: "Dwarven Hold Education", foundryName: "Dongun Education" },
  { pbName: "Ember's Eyes (Darkvision)", foundryName: "Ember's Eyes" },
  { pbName: "Enhanced Familiar Feat", foundryName: "Enhanced Familiar" },
  { pbName: "Enhanced Fire", foundryName: "Artokus's Fire" },
  { pbName: "Enigma", foundryName: "Enigma Muse" },
  { pbName: "Escape", foundryName: "Fane's Escape" },
  { pbName: "Eye of the Arcane Lords", foundryName: "Eye of the Arclords" },
  { pbName: "Flip", foundryName: "Farabellus Flip" },
  { pbName: "Fourberie", foundryName: "Fane's Fourberie" },
  { pbName: "Ganzi Gaze (Low-Light Vision)", foundryName: "Ganzi Gaze" },
  { pbName: "Guild Agent Dedication", foundryName: "Pathfinder Agent Dedication" },
  { pbName: "Harmful Font", foundryName: "Divine Font" },
  { pbName: "Green Watcher", foundryName: "Greenwatcher" },
  { pbName: "Green Watch Initiate", foundryName: "Greenwatch Initiate" },
  { pbName: "Green Watch Veteran", foundryName: "Greenwatch Veteran" },
  { pbName: "Healing Font", foundryName: "Divine Font" },
  { pbName: "Heatwave", foundryName: "Heat Wave" },
  { pbName: "Heavenseeker Dedication", foundryName: "Jalmeri Heavenseeker Dedication" },
  { pbName: "Heir of the Astrologers", foundryName: "Heir of the Saoc" },
  { pbName: "High Killer Training", foundryName: "Vernai Training" },
  { pbName: "Ice-Witch", foundryName: "Irriseni Ice-Witch" },
  { pbName: "Impeccable Crafter", foundryName: "Impeccable Crafting" },
  { pbName: "Incredible Beastmaster's Companion", foundryName: "Incredible Beastmaster Companion" },
  { pbName: "Interrogation", foundryName: "Bolera's Interrogation" },
  { pbName: "Katana", foundryName: "Katana Weapon Familiarity" },
  { pbName: "Liberator [Chaotic Good]", foundryName: "Liberator" },
  { pbName: "Lumberjack Dedication", foundryName: "Turpin Rowe Lumberjack Dedication" },
  { pbName: "Lumberjack", foundryName: "Lumber Consortium Laborer" },
  { pbName: "Maestro", foundryName: "Maestro Muse" },
  { pbName: "Major Lesson I", foundryName: "Major Lesson" },
  { pbName: "Major Lesson II", foundryName: "Major Lesson" },
  { pbName: "Major Lesson III", foundryName: "Major Lesson" },
  { pbName: "Mantis God's Grip", foundryName: "Achaekek's Grip" },
  { pbName: "Marked for Death", foundryName: "Mark for Death" },
  { pbName: "Miraculous Spells", foundryName: "Miraculous Spell" },
  { pbName: "Multifarious", foundryName: "Multifarious Muse" },
  { pbName: "Mystic", foundryName: "Nexian Mystic" },
  { pbName: "Paladin [Lawful Good]", foundryName: "Paladin" },
  { pbName: "Parry", foundryName: "Aldori Parry" },
  { pbName: "Polymath", foundryName: "Polymath Muse" },
  { pbName: "Precise Debilitation", foundryName: "Precise Debilitations" },
  { pbName: "Prodigy", foundryName: "Merabite Prodigy" },
  { pbName: "Quick Climber", foundryName: "Quick Climb" },
  { pbName: "Raider", foundryName: "Ulfen Raider" },
  { pbName: "Recognise Threat", foundryName: "Recognize Threat" },
  { pbName: "Redeemer [Neutral Good]", foundryName: "Redeemer" },
  { pbName: "Revivification Protocall", foundryName: "Revivification Protocol" },
  { pbName: "Riposte", foundryName: "Aldori Riposte" },
  { pbName: "Rkoan Arts", foundryName: "Rokoan Arts" },
  { pbName: "Saberteeth", foundryName: "Saber Teeth" },
  { pbName: "Scholarly Recollection", foundryName: "Uzunjati Recollection" },
  { pbName: "Scholarly Storytelling", foundryName: "Uzunjati Storytelling" },
  { pbName: "Shamanic Adherent", foundryName: "Rivethun Adherent" },
  { pbName: "Shamanic Disciple", foundryName: "Rivethun Disciple" },
  { pbName: "Shamanic Spiritual Attunement", foundryName: "Rivethun Spiritual Attunement" },
  { pbName: "Skysage Dedication", foundryName: "Oatia Skysage Dedication" },
  { pbName: "Secret Lesson", foundryName: "Janatimo's Lessons" },
  { pbName: "Sentry Dedication", foundryName: "Lastwall Sentry Dedication" },
  { pbName: "Stab and Snag", foundryName: "Stella's Stab and Snag" },
  { pbName: "Tenets of Evil", foundryName: "The Tenets of Evil" },
  { pbName: "Tenets of Good", foundryName: "The Tenets of Good" },
  { pbName: "Tongue of the Sun and Moon", foundryName: "Tongue of Sun and Moon" },
  { pbName: "Tribal Bond", foundryName: "Quah Bond" },
  { pbName: "Tyrant [Lawful Evil]", foundryName: "Tyrant" },
  { pbName: "Vestigal Wings", foundryName: "Vestigial Wings" },
  { pbName: "Virtue-Forged Tattooed", foundryName: "Virtue-Forged Tattoos" },
  { pbName: "Wakizashi", foundryName: "Wakizashi Weapon Familiarity" },
  { pbName: "Warden", foundryName: "Lastwall Warden" },
  { pbName: "Warrior", foundryName: "Warrior Muse" },
  { pbName: "Wary Eye", foundryName: "Eye of Ozem" },
  { pbName: "Wayfinder Resonance Infiltrator", foundryName: "Westyr's Wayfinder Repository" },
  { pbName: "Wind God's Fan", foundryName: "Wind God’s Fan" },
  { pbName: "Wind God’s Fan", foundryName: "Wind God's Fan" },
  // dragons
  { pbName: "Black", foundryName: "Black Dragon" },
  { pbName: "Brine", foundryName: "Brine Dragon" },
  { pbName: "Copper", foundryName: "Copper Dragon" },
  { pbName: "Blue", foundryName: "Blue Dragon" },
  { pbName: "Bronze", foundryName: "Bronze Dragon" },
  { pbName: "Cloud", foundryName: "Cloud Dragon" },
  { pbName: "Sky", foundryName: "Sky Dragon" },
  { pbName: "Brass", foundryName: "Brass Dragon" },
  { pbName: "Underworld", foundryName: "Underworld Dragon" },
  { pbName: "Crystal", foundryName: "Crystal Dragon" },
  { pbName: "Forest", foundryName: "Forest Dragon" },
  { pbName: "Green", foundryName: "Green Dragon" },
  { pbName: "Sea", foundryName: "Sea Dragon" },
  { pbName: "Silver", foundryName: "Silver Dragon" },
  { pbName: "White", foundryName: "White Dragon" },
  { pbName: "Sovereign", foundryName: "Sovereign Dragon" },
  { pbName: "Umbral", foundryName: "Umbral Dragon" },
  { pbName: "Red", foundryName: "Red Dragon" },
  { pbName: "Gold", foundryName: "Gold Dragon" },
  { pbName: "Magma", foundryName: "Magma Dragon" },
  // sizes for fleshwarp
  { pbName: "Medium", foundryName: "med" },
  { pbName: "Small", foundryName: "sm" },
  // Cleric +
  { pbName: "Decree of the Warsworn Ecstacy", foundryName: "Decree of Warsworn Ecstacy" },
  { pbName: "Decree of Warsworn Ecstacy", foundryName: "Decree of the Warsworn Ecstacy" },

  // remaster
  { pbName: "Lightning Reflexes", foundryName: "Reflex Expertise" },
  { pbName: "Great Fortitude", foundryName: "Fortitude Expertise" },
  { pbName: "Iron Will", foundryName: "Will Expertise" },
  { pbName: "Alertness", foundryName: "Perception Expertise" },
  { pbName: "Incredible Senses", foundryName: "Perception Legend" },
  { pbName: "Vigilant Senses", foundryName: "Perception Mastery" },
];

function features_generateDynamicNames(pbName) {
  const result = [];
  // if we have a hardcoded map, don't return here
  if (FEAT_RENAME_STATIC_MAP.some((e) => e.pbName === pbName)) return result;
  for (const reg of features_POSTFIX_PB_REMOVALS) {
    const match = pbName.match(reg);
    if (match) {
      result.push({ pbName, foundryName: match[1], details: match[2] });
    }
  }
  for (const reg of PREFIX_PB_REMOVALS) {
    const match = pbName.match(reg);
    if (match) {
      const parsed = { pbName, foundryName: match[2], details: match[1] };
      parsed.foundryValue = SKILL_LOOKUP[parsed.foundryName.toLowerCase()];
      result.push(parsed);
    }
  }
  for (const reg of features_SPLITS) {
    const match = pbName.match(reg);
    if (match) {
      result.push({ pbName, foundryName: match[2], details: match[1] });
    }
  }
  for (const reg of features_PARENTHESIS) {
    const match = pbName.match(reg);
    if (match) {
      result.push({ pbName, foundryName: match[1], details: match[2] });
    }
  }
  for (const reg of features_SWAPS) {
    const match = pbName.match(reg);
    if (match) {
      result.push({ pbName, foundryName: `${match[2]} (${match[1]})`, details: match[2] });
    }
  }
  return result;
}

function FEAT_RENAME_MAP(pbName = null) {
  const postfixNames = pbName ? features_generateDynamicNames(pbName) : [];
  return postfixNames.concat(FEAT_RENAME_STATIC_MAP);
}

const SHARED_IGNORE_LIST = [
  "Draconic Rage", // just handled by effects on Draconic Instinct
  "Mirror Initiate Benefit",
  "Spellstrike Specifics",
  "Unarmored",
  "Simple Weapon Expertise",
  "Spellbook",
  "Titan Mauler", // not needed
  "Energy Emanation", // pathbuilder does not pass through a type for this
  "Imprecise Sense", // this gets picked up and added by granted features
  "Imprecise Scent", // this gets picked up and added by granted features
  "Sanctification", // choose on import
];

const IGNORED_FEATS_LIST = [
  // ignore skills listed as feats
  "Acrobatics",
  "Athletics",
  "Deception",
  "Intimidation",
  "Nature",
  "Performance",
  "Society",
  "Survival",
  "Arcana",
  "Crafting",
  "Diplomacy",
  "Medicine",
  "Occultism",
  "Religion",
  "Stealth",
  "Thievery",

  // sizes
  // "Medium",
  // "Small",
];

const IGNORED_SPECIALS_LIST = [

];

function IGNORED_FEATS() {
  // const visionFeats = utils.setting("ADD_VISION_FEATS") ? [] : ["Low-Light Vision", "Darkvision"];
  return IGNORED_FEATS_LIST.concat(SHARED_IGNORE_LIST);
}

function IGNORED_SPECIALS() {
  const visionFeats = src_utils.setting("ADD_VISION_FEATS") ? [] : ["Low-Light Vision", "Darkvision"];
  return IGNORED_SPECIALS_LIST.concat(SHARED_IGNORE_LIST, visionFeats);
}

function specialOnlyNameLookup(name) {
  for (const [key, value] of Object.entries(SKILL_LOOKUP)) {
    if (key === name.toLowerCase()) {
      return { pbName: name, foundryName: name, foundryValue: value };
    }
  }
  return undefined;
}


function SPECIAL_NAME_ADDITIONS(specials) {
  const newSpecials = [];

  for (const special of specials) {
    for (const reg of POSTFIX_PB_SPLIT_AND_KEEP) {
      const match = special.match(reg);
      if (match) {
        newSpecials.push(match[2]);
      }
    }
  }
  return newSpecials;
}

const NO_AUTO_CHOICE_LIST = [
  // "Elemental Evolution",
];

function NO_AUTO_CHOICE() {
  return NO_AUTO_CHOICE_LIST;
}

;// CONCATENATED MODULE: ./src/data/spells.js
const FEAT_SPELLCASTING = [
  { name: "Kitsune Spell Familiarity", showSlotless: false, knownSpells: ["Daze", "Forbidding Ward", "Ghost Sound"], preparePBSpells: true, },
  { name: "Kitsune Spell Expertise", showSlotless: false, knownSpells: ["Confusion", "Death Ward", "Illusory Scene"], preparePBSpells: true, },
  { name: "Kitsune Spell Mysteries", showSlotless: false, knownSpells: ["Bane", "Illusory Object", "Sanctuary"], preparePBSpells: true, },
  { name: "Nagaji Spell Familiarity", showSlotless: false, knownSpells: ["Daze", "Detect Magic", "Mage Hand"], preparePBSpells: true, },
  { name: "Nagaji Spell Expertise", showSlotless: false, knownSpells: ["Blink", "Control Water", "Subconscious Suggestion"], preparePBSpells: true, },
  { name: "Nagaji Spell Mysteries", showSlotless: false, knownSpells: ["Charm", "Fleet Step", "Heal"], preparePBSpells: true, },
  { name: "Rat Magic", showSlotless: false, knownSpells: [], preparePBSpells: true, },
];

const REMASTER_NAMES = [
  { pbName: "Animate Dead", foundryName: "Summon Undead" },
  { pbName: "Augment Summoning", foundryName: "Fortify Summoning" },
  { pbName: "Baleful Polymorph", foundryName: "Cursed Metamorphosis" },
  { pbName: "Barkskin", foundryName: "Oaken Resilience" },
  { pbName: "Bind Soul", foundryName: "Seize Soul" },
  { pbName: "Blind Ambition", foundryName: "Ignite Ambition" },
  { pbName: "Blink", foundryName: "Flicker" },
  { pbName: "Burning Hands", foundryName: "Breathe Fire" },
  { pbName: "Calm Emotions", foundryName: "Calm" },
  { pbName: "Charming Words", foundryName: "Charming Push" },
  { pbName: "Chill Touch", foundryName: "Void Warp" },
  { pbName: "Cloudkill", foundryName: "Toxic Cloud" },
  { pbName: "Color Spray", foundryName: "Dizzying Colors" },
  { pbName: "Commune with Nature", foundryName: "Commune" },
  { pbName: "Comprehend Language", foundryName: "Translate" },
  { pbName: "Continual Flame", foundryName: "Everlight" },
  { pbName: "Crushing Despair", foundryName: "Wave of Despair" },
  { pbName: "Dancing Lights", foundryName: "Light" },
  { pbName: "Dimension Door", foundryName: "Translocate" },
  { pbName: "Dimensional Anchor", foundryName: "Planar Seal" },
  { pbName: "Dimensional Lock", foundryName: "Planar Tether" },
  { pbName: "Discern Location", foundryName: "Pinpoint" },
  { pbName: "Disrupt Undead", foundryName: "Vitality Lash" },
  { pbName: "Disrupting Weapons", foundryName: "Infuse Vitality" },
  { pbName: "Endure Elements", foundryName: "Environmental Endurance" },
  { pbName: "Entangle", foundryName: "Entangling Flora" },
  { pbName: "False Life", foundryName: "False Vitality" },
  { pbName: "Feather Fall", foundryName: "Gentle Landing" },
  { pbName: "Feeblemind", foundryName: "Never Mind" },
  { pbName: "Finger of Death", foundryName: "Execute" },
  { pbName: "Flaming Sphere", foundryName: "Floating Flame" },
  { pbName: "Flesh To Stone", foundryName: "Petrify" },
  { pbName: "Freedom of Movement", foundryName: "Unfettered Movement" },
  { pbName: "Gaseous Form", foundryName: "Vapor Form" },
  { pbName: "Gentle Repose", foundryName: "Peaceful Rest" },
  { pbName: "Glibness", foundryName: "Honeyed Words" },
  { pbName: "Glitterdust", foundryName: "Revealing Light" },
  { pbName: "Globe of Invulnerability", foundryName: "Dispelling Globe" },
  { pbName: "Hallucinatory Terrain", foundryName: "Mirage" },
  { pbName: "Hideous Laughter", foundryName: "Laughing Fit" },
  { pbName: "Horrid Wilting", foundryName: "Desiccate" },
  { pbName: "Hypnotic Pattern", foundryName: "Hypnotize" },
  { pbName: "Inspire Competence", foundryName: "Uplifting Overture" },
  { pbName: "Inspire Courage", foundryName: "Courageous Anthem" },
  { pbName: "Inspire Defense", foundryName: "Rallying Anthem" },
  { pbName: "Inspire Heroics", foundryName: "Fortissimo Composition" },
  { pbName: "Know Direction", foundryName: "Know the Way" },
  { pbName: "Legend Lore", foundryName: "Collective Memories" },
  { pbName: "Longstrider", foundryName: "Tailwind" },
  { pbName: "Mage Armor", foundryName: "Mystic Armor" },
  { pbName: "Mage Hand", foundryName: "Telekinetic Hand" },
  { pbName: "Magic Aura", foundryName: "Disguise Magic" },
  { pbName: "Magic Fang", foundryName: "Runic Body" },
  { pbName: "Magic Missile", foundryName: "Force Barrage" },
  { pbName: "Magic Mouth", foundryName: "Embed Message" },
  { pbName: "Magic Weapon", foundryName: "Runic Weapon" },
  { pbName: "Magnificent Mansion", foundryName: "Planar Palace" },
  { pbName: "Maze", foundryName: "Quandary" },
  { pbName: "Meld into Stone", foundryName: "One with Stone" },
  { pbName: "Meteor Swarm", foundryName: "Falling Stars" },
  { pbName: "Mind Blank", foundryName: "Hidden Mind" },
  { pbName: "Misdirection", foundryName: "Disguise Magic" },
  { pbName: "Modify Memory", foundryName: "Rewrite Memory" },
  { pbName: "Neutralize Poison", foundryName: "Cleanse Affliction" },
  { pbName: "Nondetection", foundryName: "Veil of Privacy" },
  { pbName: "Obscuring Mist", foundryName: "Mist" },
  { pbName: "Pass Without Trace", foundryName: "Vanishing Tracks" },
  { pbName: "Passwall", foundryName: "Magic Passage" },
  { pbName: "Phantom Mount", foundryName: "Marvelous Mount" },
  { pbName: "Planar Binding", foundryName: "Planar Servitor" },
  { pbName: "Plane Shift", foundryName: "Interplanar Teleport" },
  { pbName: "Positive Luminance", foundryName: "Vital Luminance" },
  { pbName: "Private Sanctum", foundryName: "Peaceful Bubble" },
  { pbName: "Prying Eye", foundryName: "Scouting Eye" },
  { pbName: "Pulse of The City", foundryName: "Pulse of Civilization" },
  { pbName: "Purify Food And Drink", foundryName: "Cleanse Cuisine" },
  { pbName: "Ray of Enfeeblement", foundryName: "Enfeeble" },
  { pbName: "Remove Curse", foundryName: "Cleanse Affliction" },
  { pbName: "Remove Disease", foundryName: "Cleanse Affliction" },
  { pbName: "Remove Fear", foundryName: "Clear Mind" },
  { pbName: "Remove Paralysis", foundryName: "Sure Footing" },
  { pbName: "Restore Senses", foundryName: "Sound Body" },
  { pbName: "Scorching Ray", foundryName: "Blazing Bolt" },
  { pbName: "Searing Light", foundryName: "Holy Light" },
  { pbName: "See Invisibility", foundryName: "See the Unseen" },
  { pbName: "Shadow Walk", foundryName: "Umbral Journey" },
  { pbName: "Shapechange", foundryName: "Metamorphosis" },
  { pbName: "Shield Other", foundryName: "Share Life" },
  { pbName: "Sound Burst", foundryName: "Noise Blast" },
  { pbName: "Spectral Hand", foundryName: "Ghostly Carrier" },
  { pbName: "Spider Climb", foundryName: "Gecko Grip" },
  { pbName: "Splash of Art", foundryName: "Creative Splash" },
  { pbName: "Stone Tell", foundryName: "Speak with Stones" },
  { pbName: "Stoneskin", foundryName: "Mountain Resilience" },
  { pbName: "Tanglefoot", foundryName: "Tangle Vine" },
  { pbName: "Time Stop", foundryName: "Freeze Time" },
  { pbName: "Tongues", foundryName: "Truespeech" },
  { pbName: "Touch of Idiocy", foundryName: "Stupefy" },
  { pbName: "Tree Shape", foundryName: "One with Plants" },
  { pbName: "Tree Stride", foundryName: "Nature's Pathway" },
  { pbName: "Trueseeing", foundryName: "Truesight" },
  { pbName: "True Strike", foundryName: "Sure Strike" },
  { pbName: "Unseen Servant", foundryName: "Phantasmal Minion" },
  { pbName: "Vampiric Touch", foundryName: "Vampiric Feast" },
  { pbName: "Veil", foundryName: "Illusory Disguise" },
  { pbName: "Vigilant Eye", foundryName: "Rune of Observation" },
  { pbName: "Wail of the Banshee", foundryName: "Wails of the Damned" },
  { pbName: "Wind Walk", foundryName: "Migration" },
  { pbName: "Zone of Truth", foundryName: "Ring of Truth" },
];

function spellRename(spellName) {
  if (isNewerVersion(game.version, "5.9.0") && game.modules.get("pf2e-legacy-content")?.active) return spellName;
  const remasterName = REMASTER_NAMES.find((remaster) => remaster.pbName === spellName);
  if (remasterName) {
    return remasterName.foundryName;
  }
  return spellName;
}

;// CONCATENATED MODULE: ./src/app/Seasoning.js




/**
 * This class acts as a wrapper around the renaming data,
 * and the changing of names for foundry
 *
 * When Munching we refer to this as Seasoning the data to taste.
 *
 * It's split out just to make it more manageable
 */
class Seasoning {

  // sluggify
  static slug(name) {
    if (!name) return undefined;
    return game.pf2e.system.sluggify(name);
  }

  // sluggify with dromedary casing
  static slugD(name) {
    if (!name) return undefined;
    return game.pf2e.system.sluggify(name, { camel: "dromedary" });
  }

  static FEAT_RENAME_MAP(name) {
    return FEAT_RENAME_MAP(name);
  }

  static EQUIPMENT_RENAME_MAP(name) {
    return EQUIPMENT_RENAME_MAP(name);
  }

  static getSpellCastingFeatureAdjustment(name) {
    return FEAT_SPELLCASTING.find((f) => f.name === name);
  }

  static getFoundryEquipmentName(pbName) {
    return Seasoning.EQUIPMENT_RENAME_MAP(pbName).find((map) => map.pbName == pbName)?.foundryName ?? pbName;
  }

  // static getFoundryFeatureName(pbName) {
  //   const match = Seasoning.FEAT_RENAME_MAP(pbName).find((map) => map.pbName == pbName);
  //   return match ?? { pbName, foundryName: pbName, details: undefined };
  // }

  static RESTRICTED_EQUIPMENT() {
    return RESTRICTED_EQUIPMENT;
  }

  // specials that are handled by Foundry and shouldn't be added
  static IGNORED_FEATS() {
    return IGNORED_FEATS();
  };

  static IGNORED_SPECIALS() {
    return IGNORED_SPECIALS();
  }

  static IGNORED_EQUIPMENT() {
    return IGNORED_EQUIPMENT;
  };

  static IGNORED_EQUIPMENT_DISPLAY(pbName) {
    return IGNORED_EQUIPMENT_DISPLAY(pbName);
  }

  static GRANTED_ITEMS_LIST() {
    return GRANTED_ITEMS_LIST;
  }

  static getSizeValue(size) {
    switch (size) {
      case 0:
        return "tiny";
      case 1:
        return "sm";
      case 3:
        return "lg";
      default:
        return "med";
    }
  }

  static PHYSICAL_ITEM_TYPES = new Set([
    "armor",
    "backpack",
    "book",
    "consumable",
    "equipment",
    "treasure",
    "weapon"
  ]);

  static isPhysicalItemType(type) {
    return Seasoning.PHYSICAL_ITEM_TYPES.has(type);
  }

  static getMaterialGrade(material) {
    if (material.toLowerCase().includes("high-grade")) {
      return "high";
    } else if (material.toLowerCase().includes("standard-grade")) {
      return "standard";
    }
    return "low";
  }

  static getFoundryFeatLocation(pathbuilderFeatType, pathbuilderFeatLevel) {
    if (pathbuilderFeatType === "Ancestry Feat") {
      return `ancestry-${pathbuilderFeatLevel}`;
    } else if (pathbuilderFeatType === "Class Feat") {
      return `class-${pathbuilderFeatLevel}`;
    } else if (pathbuilderFeatType === "Skill Feat") {
      return `skill-${pathbuilderFeatLevel}`;
    } else if (pathbuilderFeatType === "General Feat") {
      return `general-${pathbuilderFeatLevel}`;
    } else if (pathbuilderFeatType === "Background Feat") {
      return `skill-${pathbuilderFeatLevel}`;
    } else if (pathbuilderFeatType === "Archetype Feat") {
      return `archetype-${pathbuilderFeatLevel}`;
    } else if (pathbuilderFeatType === "Kineticist Feat") { // return as null for now
      return null;
    } else {
      return null;
    }
  }

  static getClassAdjustedSpecialNameLowerCase(name, className) {
    return `${name} (${className})`.toLowerCase();
  }

  static getDualClassAdjustedSpecialNameLowerCase(name, dualClassName) {
    return `${name} (${dualClassName})`.toLowerCase();
  }

  static getAncestryAdjustedSpecialNameLowerCase(name, ancestryName) {
    return `${name} (${ancestryName})`.toLowerCase();
  }

  static getHeritageAdjustedSpecialNameLowerCase(name, heritageName) {
    return `${name} (${heritageName})`.toLowerCase();
  }

  static getChampionType(alignment) {
    if (alignment == "LG") return "Paladin";
    else if (alignment == "CG") return "Liberator";
    else if (alignment == "NG") return "Redeemer";
    else if (alignment == "LE") return "Tyrant";
    else if (alignment == "CE") return "Antipaladin";
    else if (alignment == "NE") return "Desecrator";
    return "Unknown";
  }


}

;// CONCATENATED MODULE: ./src/app/CompendiumMatcher.js
/* eslint-disable no-await-in-loop */





class CompendiumMatcher {

  constructor({ type, mappings = null, indexFields = ["name", "type", "system.slug"] } = {}) {
    this.type = type;
    this.indexFields = indexFields;
    this.packs = {};

    const packMappings = mappings !== null
      ? mappings
      : src_utils.setting("USE_CUSTOM_COMPENDIUM_MAPPINGS")
        ? src_utils.setting("CUSTOM_COMPENDIUM_MAPPINGS")
        : constants.CORE_COMPENDIUM_MAPPINGS;
    packMappings[type].forEach((name) => {
      const compendium = game.packs.get(name);
      if (compendium) {
        this.packs[name] = compendium;
      }
    });

    this.indexes = {

    };

  }

  async loadCompendiums() {
    for (const [name, compendium] of Object.entries(this.packs)) {
      this.indexes[name] = await compendium.getIndex({ fields: this.indexFields });
    }
  }


  getFoundryFeatureName(pbName) {
    const match = this.FEAT_RENAME_MAP(pbName).find((map) => map.pbName == pbName);
    return match ?? { pbName, foundryName: pbName, details: undefined };
  }

  getNameMatch(pbName, foundryName) {
    for (const [packName, index] of Object.entries(this.indexes)) {
      const indexMatch = index.find((i) => i.name === foundryName)
        ?? index.find((i) => i.name === pbName);

      if (indexMatch) {
        src_logger.debug(`Found name only compendium document for ${pbName} (${foundryName}) in ${packName} with id ${indexMatch._id}`);
        return { i: indexMatch, pack: this.packs[packName] };
      }
    }
    return undefined;
  }

  getSlugMatch(pbName, foundryName) {
    for (const [packName, index] of Object.entries(this.indexes)) {
      src_logger.debug(`Checking for compendium documents for ${pbName} (${foundryName}) in ${packName}`, {
        pbName,
        foundryName,
        packName,
        // index,
        // foundrySlug: Seasoning.slug(foundryName),
        // pbSlug: Seasoning.slug(pbName),
        // foundryMatch: index.find((i) => (i.system.slug ?? Seasoning.slug(i.name)) === Seasoning.slug(foundryName)),
        // pbMatch: index.find((i) => (i.system.slug ?? Seasoning.slug(i.name)) === Seasoning.slug(pbName)),
        // pbSlugMatch: (null ?? Seasoning.slug("Phase Bolt (Psychic)")) === Seasoning.slug("Phase Bolt (Psychic)"),
      });
      const indexMatch = index.find((i) => (i.system.slug ?? Seasoning.slug(i.name)) === Seasoning.slug(foundryName))
        ?? index.find((i) => (i.system.slug ?? Seasoning.slug(i.name)) === Seasoning.slug(pbName));

      if (indexMatch) {
        src_logger.debug(`Found slug based compendium document for ${pbName} (${foundryName}) in ${packName} with id ${indexMatch._id}`);
        return { i: indexMatch, pack: this.packs[packName] };
      }
    }
    return undefined;
  }

  getMatch(pbName, foundryName, forceName = false) {

    if (forceName) {
      const nameOnlyMatch = this.getNameMatch(pbName, foundryName);
      if (nameOnlyMatch) return nameOnlyMatch;
    }

    const slugMatch = this.getSlugMatch(pbName, foundryName);
    if (slugMatch) return slugMatch;

    return undefined;
  }

  static checkForFilters(i, filters) {
    for (const [key, value] of Object.entries(filters)) {
      if (getProperty(i, key) !== value) {
        return false;
      }
    }
    return true;
  }

  getNameMatchWithFilter(pbName, foundryName, filters = {}) {
    for (const [packName, index] of Object.entries(this.indexes)) {
      src_logger.debug(`Checking for compendium documents for ${pbName} (${foundryName}) in ${packName}`, {
        pbName,
        foundryName,
        filters,
        packName,
        // index,
      });
      const indexMatch = index.find((i) =>
        ((i.system.slug ?? Seasoning.slug(i.name)) === Seasoning.slug(foundryName))
          && CompendiumMatcher.checkForFilters(i, filters))
        ?? index.find((i) =>
          ((i.system.slug ?? Seasoning.slug(i.name)) === Seasoning.slug(pbName)
          && CompendiumMatcher.checkForFilters(i, filters))
        );

      if (indexMatch) {
        src_logger.debug(`Found compendium document for ${pbName} (${foundryName}) in ${packName} with id ${indexMatch._id}`);
        return { i: indexMatch, pack: this.packs[packName] };
      }
    }

    return undefined;
  }


}

;// CONCATENATED MODULE: ./src/app/CompendiumSelector.js



class CompendiumSelector extends FormApplication {
  constructor() {
    super();
    this.lookups = src_utils.setting("CUSTOM_COMPENDIUM_MAPPINGS");
    this.packs = game.packs
      .filter((p) => p.metadata.type === "Item")
      .map((p) => {
        return { id: p.metadata.id, label: `${p.metadata.label} (${p.metadata.packageName})` };
      });
    this.currentType = null;
    this.useCustomCompendiums = src_utils.setting("USE_CUSTOM_COMPENDIUM_MAPPINGS");
  }

  static get defaultOptions() {
    return mergeObject(super.defaultOptions, {
      id: "pathmuncher-compendium-selector",
      template: `${constants.PATH}/templates/compendium-selector.hbs`,
      width: 722,
      height: 275,
      title: game.i18n.localize(`${constants.FLAG_NAME}.Dialogs.CompendiumSelector.Title`),
      resizable: true,
      classes: ['pathmuncher-compendium-selector'],
    });
  }

  getData() {
    const lookups = [];
    for (const key in this.lookups) {
      lookups.push({
        key,
        label: game.i18n.localize(`${constants.FLAG_NAME}.CompendiumGroups.${key}`),
      });
    }
    return {
      lookups,
      title: this.options.title,
      sourceItems: [],
      compendiumItems: [],
      useCustomCompendiums: this.useCustomCompendiums,
    };
  }

  async reset() {
    const defaults = constants.GET_DEFAULT_SETTINGS();
    this.lookups = defaults[constants.SETTINGS.CUSTOM_COMPENDIUM_MAPPINGS].default;
    await src_utils.updateSetting("CUSTOM_COMPENDIUM_MAPPINGS", this.lookups);
    this.currentType = null;
    this.render(true);
  }

  async enableCustomCompendiums() {
    this.useCustomCompendiums = !this.useCustomCompendiums;
    await src_utils.updateSetting("USE_CUSTOM_COMPENDIUM_MAPPINGS", this.useCustomCompendiums);
  }

  filterList(event) {
    const compendiumType = event.srcElement.value;
    const sourceList = document.getElementById("sourceList");
    const compendiumList = document.getElementById("compendiumList");

    const sourceOptions = this.packs.filter((p) => !this.lookups[compendiumType].includes(p.id));
    const compendiumOptions = this.packs.filter((p) => this.lookups[compendiumType].includes(p.id));

    sourceList.innerHTML = "";
    compendiumList.innerHTML = "";

    sourceOptions.forEach((option) => {
      const sourceListItem = document.createElement("option");
      sourceListItem.value = option.id;
      sourceListItem.appendChild(document.createTextNode(option.label));
      sourceList.appendChild(sourceListItem);
    });

    compendiumOptions.forEach((option) => {
      const compendiumListItem = document.createElement("option");
      compendiumListItem.value = option.id;
      compendiumListItem.appendChild(document.createTextNode(option.label));
      compendiumList.appendChild(compendiumListItem);
    });

    this.currentType = compendiumType;
  }

  async updateCompendiums() {
    const compendiumList = document.getElementById("compendiumList");
    const compendiumOptions = Array.from(compendiumList.options);
    const compendiumIds = compendiumOptions.map((option) => {
      return option.value;
    });

    this.lookups[this.currentType] = compendiumIds;

    src_utils.updateSetting("CUSTOM_COMPENDIUM_MAPPINGS", this.lookups);
  }

  async addCompendium() {
    const sourceList = document.getElementById("sourceList");
    const compendiumList = document.getElementById("compendiumList");
    const selectedOptions = Array.from(sourceList.selectedOptions);

    selectedOptions.forEach((option) => {
      compendiumList.appendChild(option);
    });

    await this.updateCompendiums();
  }

  async removeCompendium() {
    const sourceList = document.getElementById("sourceList");
    const compendiumList = document.getElementById("compendiumList");
    const selectedOptions = Array.from(compendiumList.selectedOptions);

    selectedOptions.forEach((option) => {
      sourceList.appendChild(option);
    });
    await this.updateCompendiums();
  }

  async moveUp() {
    const compendiumList = document.getElementById("compendiumList");
    const selectedOption = compendiumList.selectedOptions[0];

    if (selectedOption && selectedOption.previousElementSibling) {
      compendiumList.insertBefore(selectedOption, selectedOption.previousElementSibling);
    }
    await this.updateCompendiums();
  }

  async moveDown() {
    const compendiumList = document.getElementById("compendiumList");
    const selectedOption = compendiumList.selectedOptions[0];

    if (selectedOption && selectedOption.nextElementSibling) {
      compendiumList.insertBefore(selectedOption.nextElementSibling, selectedOption);
    }
    await this.updateCompendiums();
  }

  activateListeners(html) {
    super.activateListeners(html);

    document.getElementById("addButton").addEventListener("click", this.addCompendium.bind(this));
    document.getElementById("removeButton").addEventListener("click", this.removeCompendium.bind(this));
    document.getElementById("upButton").addEventListener("click", this.moveUp.bind(this));
    document.getElementById("downButton").addEventListener("click", this.moveDown.bind(this));
    document.getElementById("compSelector").addEventListener("change", this.filterList.bind(this));
    document.getElementById("resetButton").addEventListener("click", this.reset.bind(this));
    document.getElementById("enableCustomCompendiums").addEventListener("change", this.enableCustomCompendiums.bind(this));
  }
}

;// CONCATENATED MODULE: ./src/app/Pathmuncher.js
/* eslint-disable no-await-in-loop */
/* eslint-disable no-continue */








class Pathmuncher {
  FEAT_RENAME_MAP(name) {
    const dynamicItems = [
      { pbName: "Shining Oath", foundryName: `Shining Oath (${Seasoning.getChampionType(this.source.alignment)})` },
      { pbName: "Counterspell", foundryName: `Counterspell (${src_utils.capitalize(this.getClassSpellCastingType() ?? "")})` },
      { pbName: "Counterspell", foundryName: `Counterspell (${src_utils.capitalize(this.getClassSpellCastingType(true) ?? "")})` },
      { pbName: "Cantrip Expansion", foundryName: `Cantrip Expansion (${this.source.class})` },
      { pbName: "Cantrip Expansion", foundryName: `Cantrip Expansion (${this.source.dualClass})` },
      { pbName: "Cantrip Expansion", foundryName: `Cantrip Expansion (${src_utils.capitalize(this.getClassSpellCastingType() ?? "")} Caster)` },
      { pbName: "Cantrip Expansion", foundryName: `Cantrip Expansion (${src_utils.capitalize(this.getClassSpellCastingType(true) ?? "")} Caster)` },
    ];
    return Seasoning.FEAT_RENAME_MAP(name).concat(dynamicItems);
  }

  getFoundryFeatureName(pbName, isSpecial = false) {
    if (isSpecial) {
      const specialMatch = specialOnlyNameLookup(pbName);
      if (specialMatch) return specialMatch;
    }
    const match = this.FEAT_RENAME_MAP(pbName).find((map) => map.pbName == pbName);
    return match ?? { pbName, foundryName: pbName, details: undefined };
  }

  constructor(actor, { addFeats = true, addEquipment = true, addSpells = true, adjustBlendedSlots = true,
    addMoney = true, addLores = true, addWeapons = true, addArmor = true, addTreasure = true, addDeity = true,
    addName = true, addClass = true, addBackground = true, addHeritage = true, addAncestry = true,
    statusCallback = null } = {}
  ) {
    this.devMode = game.modules.get("pathmuncher").version === "999.0.0";
    this.actor = actor;
    // note not all these options do anything yet!
    this.options = {
      addTreasure,
      addMoney,
      addFeats,
      addSpells,
      adjustBlendedSlots,
      addEquipment,
      addLores,
      addWeapons,
      addArmor,
      addDeity,
      addName,
      addClass,
      addBackground,
      addHeritage,
      addAncestry,
    };
    this.source = null;
    this.parsed = {
      specials: [],
      feats: [],
      equipment: [],
      armor: [],
      weapons: [],
    };
    this.usedLocations = new Set();
    this.usedLocationsAlternateRules = new Set();
    this.autoAddedFeatureIds = new Set();
    this.autoAddedFeatureItems = {};
    this.promptRules = {};
    this.allFeatureRules = {};
    this.autoAddedFeatureRules = {};
    this.grantItemLookUp = {};
    this.autoFeats = [];
    this.keyAbility = null;
    this.boosts = {
      custom: false,
      class: {},
      background: {},
      ancestry: {},
    };
    this.size = "med";
    this.result = {
      character: {
        _id: this.actor.id,
        prototypeToken: {},
      },
      class: [],
      deity: [],
      heritage: [],
      ancestry: [],
      background: [],
      casters: [],
      spells: [],
      feats: [],
      weapons: [],
      armor: [],
      equipment: [],
      lores: [],
      money: [],
      treasure: [],
      adventurersPack: {
        item: null,
        contents: [
          { slug: "bedroll", qty: 1 },
          { slug: "chalk", qty: 10 },
          { slug: "flint-and-steel", qty: 1 },
          { slug: "rope", qty: 1 },
          { slug: "rations", qty: 14 },
          { slug: "torch", qty: 5 },
          { slug: "waterskin", qty: 1 },
        ],
      },
      focusPool: 0,
    };
    this.check = {};
    this.bad = [];
    this.statusCallback = statusCallback;
    this.compendiumMatchers = {};
    const compendiumMappings = src_utils.setting("USE_CUSTOM_COMPENDIUM_MAPPINGS")
      ? src_utils.setting("CUSTOM_COMPENDIUM_MAPPINGS")
      : constants.CORE_COMPENDIUM_MAPPINGS;
    for (const type of Object.keys(compendiumMappings)) {
      this.compendiumMatchers[type] = new CompendiumMatcher({ type });
    }
  }

  async #loadCompendiumMatchers() {
    for (const matcher of Object.values(this.compendiumMatchers)) {
      await matcher.loadCompendiums();
    }
  }

  #statusUpdate(total, count, type, prefixLabel) {
    if (this.statusCallback) this.statusCallback(total, count, type, prefixLabel);
  }

  async fetchPathbuilder(pathbuilderId) {
    if (!pathbuilderId) {
      const flags = src_utils.getFlags(this.actor);
      pathbuilderId = flags?.pathbuilderId;
    }
    if (pathbuilderId) {
      const jsonData = await foundry.utils.fetchJsonWithTimeout(
        `https://www.pathbuilder2e.com/json.php?id=${pathbuilderId}`
      );
      if (jsonData.success) {
        this.source = jsonData.build;
      } else {
        ui.notifications.warn(
          game.i18n.format(`${constants.FLAG_NAME}.Dialogs.Pathmuncher.FetchFailed`, { pathbuilderId })
        );
      }
    } else {
      ui.notifications.error(game.i18n.localize(`${constants.FLAG_NAME}.Dialogs.Pathmuncher.NoId`));
    }
  }

  #generateFoundryFeatLocation(document, feature) {
    if (feature.type && feature.level) {
      // const freeArchetypeVariant = game.settings.get("pf2e", "freeArchetypeVariant");
      const location = Seasoning.getFoundryFeatLocation(feature.type, feature.level);
      if (location && !this.usedLocations.has(location)) {
        document.system.location = location;
        this.usedLocations.add(location);
      } else if (location && this.usedLocations.has(location)) {
        src_logger.debug("Variant feat location", { ancestryParagonVariant: src_utils.allowAncestryParagon(), location, feature });
        // eslint-disable-next-line max-depth
        if (src_utils.allowAncestryParagon() && feature.type === "Ancestry Feat") {
          document.system.location = "ancestry-bonus";
          this.usedLocationsAlternateRules.add(location);
        } else if (src_utils.allowDualClasses() && feature.type === "Class Feat") {
          document.system.location = `dualclass-${feature.level}`;
          this.usedLocationsAlternateRules.add(location);
        }
      }
    }
  }

  #processSpecialData(name) {
    if (name.includes("Domain: ")) {
      const domainName = name.split(" ")[1];
      this.parsed.feats.push({ name: "Deity's Domain", extra: domainName });
      return true;
    } else {
      return false;
    }
  }

  #getContainerData(key) {
    return {
      id: key,
      containerName: this.source.equipmentContainers[key].containerName,
      bagOfHolding: this.source.equipmentContainers[key].bagOfHolding,
      backpack: this.source.equipmentContainers[key].backpack,
    };
  }

  #nameMapSourceEquipment(e) {
    const name = Seasoning.getFoundryEquipmentName(e[0]);
    const containerKey = Object.keys(this.source.equipmentContainers)
      .find((key) => this.source.equipmentContainers[key].containerName === name);

    const container = containerKey ? this.#getContainerData(containerKey) : null;
    const foundryId = foundry.utils.randomID();

    if (container) {
      this.source.equipmentContainers[containerKey].foundryId = foundryId;
    }

    const item = {
      foundryName: name,
      pbName: e[0],
      originalName: e[0],
      qty: e[1],
      added: false,
      addedId: null,
      addedAutoId: null,
      inContainer: e[2] !== "Invested" ? e[2] : null,
      container,
      foundryId,
      invested: e[2] === "Invested",
      sourceType: "equipment",
    };
    this.parsed.equipment.push(item);
  }

  #nameMapSourceEquipmentAddHandwraps(e) {
    const name = Seasoning.getFoundryEquipmentName(e[0]);
    const potencyMatch = e[0].match(/\(\+(\d)[\s)]/i);
    const potency = potencyMatch ? parseInt(potencyMatch[1]) : 0;
    const strikingMatch = e[0].match(/\d( \w*)? (Striking)/i);
    const striking = strikingMatch
      ? Seasoning.slugD(`${(strikingMatch[1] ?? "").trim()}${(strikingMatch[2] ?? "").trim()}`) // `${(strikingMatch[2] ?? "").toLowerCase().trim()}${(strikingMatch[1] ?? "").trim()}`.trim()
      : "";
    const mockE = {
      name: e[0],
      qty: 1,
      prof: "unarmed",
      pot: Number.isInteger(potency) ? potency : 0,
      str: striking,
      mat: null,
      display: e[0],
      runes: [],
      damageType: "B",
      increasedDice: false
    };
    const weapon = mergeObject({
      foundryName: name,
      pbName: mockE.name,
      originalName: mockE.name,
      added: false,
      addedId: null,
      addedAutoId: null,
      sourceType: "weapons",
    }, mockE);
    this.parsed.weapons.push(weapon);
  }

  #nameMap() {
    let iRank = 0;
    let featRank = 0;
    src_logger.debug("Starting Equipment Rename");
    this.source.equipment
      .filter((e) => e[0] && e[0] !== "undefined")
      .forEach((e) => {
        if (e[0].startsWith("Handwraps of Mighty Blows")) {
          this.#nameMapSourceEquipmentAddHandwraps(e);
        } else {
          this.#nameMapSourceEquipment(e);
        }
      });
    this.source.armor
      .filter((e) => e && e !== "undefined")
      .forEach((e) => {
        const name = Seasoning.getFoundryEquipmentName(e.name);
        const item = mergeObject({
          foundryName: name,
          pbName: e.name,
          originalName: e.name,
          added: false,
          addedId: null,
          addedAutoId: null,
          sourceType: "armor",
        }, e);
        this.parsed.armor.push(item);
        // work around for now
        if (e.name.startsWith("Inventor ")) {
          this.parsed.feats.push({
            name,
            extra: "",
            added: false,
            addedId: null,
            addedAutoId: null,
            type: "Awarded Feat",
            level: 1,
            originalName: e.name,
            rank: 0,
            sourceType: "armor",
            featChoiceRef: null,
            hasChildren: null,
            isChild: null,
            isStandard: null,
            parentFeatChoiceRef: null,
          });
          featRank++;
        }
      });
    this.source.weapons
      .filter((e) => e && e !== "undefined")
      .forEach((e) => {
        const name = Seasoning.getFoundryEquipmentName(e.name);
        const item = mergeObject({
          foundryName: name,
          pbName: e.name,
          originalName: e.name,
          added: false,
          addedId: null,
          addedAutoId: null,
          sourceType: "weapons",
        }, e);
        this.parsed.weapons.push(item);
      });
    src_logger.debug("Finished Equipment Rename");

    src_logger.debug("Starting Special Rename");
    [].concat(this.source.specials, SPECIAL_NAME_ADDITIONS(this.source.specials))
      .filter((special) =>
        special
        && special !== "undefined"
        && special !== "Not Selected"
        && special !== this.source.heritage
      )
      .forEach((special) => {
        const match = this.getFoundryFeatureName(special); // , true);
        if (!this.#processSpecialData(match.foundryName) && !Seasoning.IGNORED_SPECIALS().includes(match.foundryName)) {
          this.parsed.specials.push({ name: match.foundryName, foundryName: match.foundryName, foundryValue: match.foundryValue, originalName: special, added: false, addedId: null, addedAutoId: null, rank: iRank, sourceType: "specials" });
          iRank++;
        }
      });
    src_logger.debug("Finished Special Rename");


    src_logger.debug("Starting Feat Rename");
    this.source.feats
      .filter((feat) =>
        feat[0]
        && feat[0] !== "undefined"
        && feat[0] !== "Not Selected"
        // && feat[0] !== this.source.heritage
      )
      .forEach((feat) => {
        const name = this.getFoundryFeatureName(feat[0]).foundryName;
        const data = {
          name,
          extra: feat[1],
          added: feat[0] === this.source.heritage,
          addedId: null,
          addedAutoId: null,
          type: feat[2],
          level: feat[3],
          originalName: feat[0],
          rank: featRank,
          sourceType: "feats",
        };
        if (feat.length >= 7) {
          data.featChoiceRef = feat[4];
          data.hasChildren = feat[5] === "parentChoice";
          data.isChild = feat[5] === "childChoice";
          data.isStandard = feat[5] === "standardChoice";
          data.parentFeatChoiceRef = feat[6];
          const parentFeatMatch = this.source.feats.find((f) =>
            feat[5] === "childChoice"
            && (data.featChoiceRef.toLowerCase().startsWith(f[0].toLowerCase())
            || (data.parentFeatChoiceRef
              && data.featChoiceRef.replace(data.parentFeatChoiceRef, "").trim().toLowerCase().startsWith(f[0].toLowerCase()))
            )
          );
          data.nameHint = parentFeatMatch?.[0];
        } else {
          // probably an awarded feat
          data.featChoiceRef = null;
          data.hasChildren = null;
          data.isChild = null;
          data.isStandard = null;
          data.parentFeatChoiceRef = null;
        }
        this.parsed.feats.push(data);
        featRank++;
      });
    src_logger.debug("Finished Feat Rename");
    src_logger.debug("Name remapping results", {
      parsed: this.parsed,
    });
  }

  #fixUps() {
    if (this.source.ancestry === "Dwarf" && !this.parsed.feats.some((f) => f.name === "Clan Pistol")) {
      const clanDagger = {
        name: "Clan Dagger",
        originalName: "Clan Dagger",
        added: false,
        addedId: null,
        addedAutoId: null,
        isChoice: true,
        rank: 0,
        sourceType: "specials",
      };
      this.parsed.specials.push(clanDagger);
    }

    const match = this.source.background.match(/(Magical Experiment) \((.*)\)$/);
    if (match) {
      this.parsed.specials.push({
        name: match[2],
        originalName: `${this.source.background}`,
        added: false,
        addedId: null,
        addedAutoId: null,
        isChoice: true,
        rank: 0,
        sourceType: "specials",
      });
      this.source.background = match[1];
    }
  }

  async #prepare() {
    await this.#loadCompendiumMatchers();
    this.#nameMap();
    this.#fixUps();
  }

  async #processSenses() {
    const senses = [];
    this.source.specials.forEach((special) => {
      if (special === "Low-Light Vision") {
        senses.push({ type: "lowLightVision" });
      } else if (special === "Darkvision") {
        senses.push({ type: "darkvision" });
      } else if (special === "Scent") {
        senses.push({ type: "scent" });
      }
    });
    setProperty(this.result.character, "system.traits.senses", senses);
  }

  // eslint-disable-next-line no-unused-vars
  async #addDualClass(klass) {
    if (!src_utils.allowDualClasses()) {
      if (this.source.dualClass && this.source.dualClass !== "") {
        src_logger.warn(`Imported character is dual class. Pathmuncher does not support dual class characters, please check the system macros`, {
          class: this.source.class,
          dualClass: this.source.dualClass,
        });
        ui.notifications.warn(`Imported character is dual class. Pathmuncher does not support dual class characters, please check the system macros`);
      }
      return;
    }
    if (!this.source.dualClass || this.source.dualClass === "") {
      src_logger.warn(`Imported character not dual class but system is configured for dual class`, {
        class: this.source.class,
      });
      ui.notifications.warn(`Imported character not dual class but system is configured for dual class`);
      return;
    }
    src_logger.info("Not processing dual class");

    // // find the dual class
    // const foundryName = this.getFoundryFeatureName(this.source.dualClass).foundryName;
    // const indexMatch = this.compendiumMatchers["classes"].getMatch(this.source.dualClass, foundryName);

    // if (!indexMatch) return;
    // const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
    // const dualClass = doc.toObject();

    // logger.debug(`Dual Class ${dualClass.name} found, squashing things together.`);

    // klass.name = `${klass.name} - ${dualClass.name}`;
    // const ruleEntry = {
    //   domain: "all",
    //   key: "RollOption",
    //   option: `class:${dualClass.system.slug}`,
    // };

    // // Attacks
    // ["advanced", "martial", "simple", "unarmed"].forEach((key) => {
    //   if (dualClass.system.attacks[key] > klass.system.attacks[key]) {
    //     klass.system.attacks[key] = dualClass.system.attacks[key];
    //   }
    // });
    // if (klass.system.attacks.martial <= dualClass.system.attacks.other.rank) {
    //   if (dualClass.system.attacks.other.rank === klass.system.attacks.other.rank) {
    //     let mashed = `${klass.system.attacks.other.name}, ${dualClass.system.attacks.other.name}`;
    //     mashed = mashed.replace("and ", "");
    //     klass.system.attacks.other.name = [...new Set(mashed.split(","))].join(",");
    //   }
    //   if (dualClass.system.attacks.other.rank > klass.system.attacks.other.rank) {
    //     klass.system.attacks.other.name = dualClass.system.attacks.other.name;
    //     klass.system.attacks.other.rank = dualClass.system.attacks.other.rank;
    //   }
    // }
    // if (
    //   klass.system.attacks.martial >= dualClass.system.attacks.other.rank
    //   && klass.system.attacks.martial >= klass.system.attacks.other.rank
    // ) {
    //   klass.system.attacks.other.rank = 0;
    //   klass.system.attacks.other.name = "";
    // }

    // // Class DC
    // if (dualClass.system.classDC > klass.system.classDC) {
    //   klass.system.classDC = dualClass.system.classDC;
    // }

    // // Defenses
    // ["heavy", "light", "medium", "unarmored"].forEach((key) => {
    //   if (dualClass.system.defenses[key] > klass.system.defenses[key]) {
    //     klass.system.defenses[key] = dualClass.system.defenses[key];
    //   }
    // });

    // // Description
    // klass.system.description.value = `${klass.system.description.value} ${dualClass.system.description.value}`;

    // // HP
    // if (dualClass.system.hp > klass.system.hp) {
    //   klass.system.hp = dualClass.system.hp;
    // }

    // // Items
    // Object.entries(dualClass.system.items).forEach((i) => {
    //   if (Object.values(klass.system.items).some((x) => x.uuid === i[1].uuid && x.level > i[1].level)) {
    //     Object.values(klass.system.items).find((x) => x.uuid === i[1].uuid).level = i[1].level;
    //   } else if (!Object.values(klass.system.items).some((x) => x.uuid === i[1].uuid && x.level <= i[1].level)) {
    //     klass.system.items[i[0]] = i[1];
    //   }
    // });

    // // Key Ability
    // dualClass.system.keyAbility.value.forEach((v) => {
    //   if (!klass.system.keyAbility.value.includes(v)) {
    //     klass.system.keyAbility.value.push(v);
    //   }
    // });

    // // Perception
    // if (dualClass.system.perception > klass.system.perception) klass.system.perception = dualClass.system.perception;

    // // Rules
    // klass.system.rules.push(ruleEntry);
    // dualClass.system.rules.forEach((r) => {
    //   if (!klass.system.rules.includes(r)) {
    //     klass.system.rules.push(r);
    //   }
    // });
    // klass.system.rules.forEach((r, i) => {
    //   if (r.path !== undefined) {
    //     const check = r.path.split(".");
    //     if (
    //       check.includes("data")
    //       && check.includes("martial")
    //       && check.includes("rank")
    //       && klass.system.attacks.martial >= r.value
    //     ) {
    //       klass.system.rules.splice(i, 1);
    //     }
    //   }
    // });

    // // Saving Throws
    // ["fortitude", "reflex", "will"].forEach((key) => {
    //   if (dualClass.system.savingThrows[key] > klass.system.savingThrows[key]) {
    //     klass.system.savingThrows[key] = dualClass.system.savingThrows[key];
    //   }
    // });

    // // Skill Feat Levels
    // dualClass.system.skillFeatLevels.value.forEach((v) => {
    //   klass.system.skillFeatLevels.value.push(v);
    // });
    // klass.system.skillFeatLevels.value = [...new Set(klass.system.skillFeatLevels.value)].sort((a, b) => {
    //   return a - b;
    // });

    // // Skill Increase Levels
    // dualClass.system.skillIncreaseLevels.value.forEach((v) => {
    //   klass.system.skillIncreaseLevels.value.push(v);
    // });
    // klass.system.skillIncreaseLevels.value = [...new Set(klass.system.skillIncreaseLevels.value)].sort((a, b) => {
    //   return a - b;
    // });

    // // Trained Skills
    // if (dualClass.system.trainedSkills.additional > klass.system.trainedSkills.additional) {
    //   klass.system.trainedSkills.additional = dualClass.system.trainedSkills.additional;
    // }
    // dualClass.system.trainedSkills.value.forEach((v) => {
    //   if (!klass.system.trainedSkills.value.includes(v)) {
    //     klass.system.trainedSkills.value.push(v);
    //   }
    // });

    // this.result.dualClass = dualClass;
  }

  // eslint-disable-next-line class-methods-use-this
  async #processGenericCompendiumLookup(type, name, target) {
    src_logger.debug(`Checking for compendium documents for ${name} (${target}) in compendiums for ${type}`);
    const foundryName = this.getFoundryFeatureName(name).foundryName;
    const indexMatch = this.compendiumMatchers[type].getMatch(name, foundryName);

    if (indexMatch) {
      const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
      const itemData = doc.toObject();
      if (name.includes("(")) {
        const extra = name.split(")")[0].split("(").pop();
        this.parsed.specials.push({ name: doc.name, originalName: name, added: true, extra, rank: 99 });
      }
      if (target === "class") {
        itemData.system.keyAbility.selected = this.keyAbility;
        await this.#addDualClass(itemData);
      }
      itemData._id = foundry.utils.randomID();
      // this.#generateGrantItemData(itemData);
      this.result[target].push(itemData);
      await this.#addGrantedItems(itemData, { applyFeatLocation: target !== "class" });
      return true;
    } else {
      this.bad.push({ pbName: name, type: target, details: { name } });
      return false;
    }
  }

  // for grants, e.g. ont he champion "Deity and Cause" where there are choices.
  // how do we determine and match these? should we?
  // "pf2e": {
  //   "itemGrants": {
  //     "adanye": {
  //       "id": "4GHcp3iaREfj2ZgN",
  //       "onDelete": "detach"
  //     },
  //     "paladin": {
  //       "id": "HGWkTEatliHgDaEu",
  //       "onDelete": "detach"
  //     }
  //   }
  // }

  // "Paladin" (granted by deity and casue)
  // "pf2e": {
  //   "grantedBy": {
  //     "id": "xnrkrJa2YE1UOAVy",
  //     "onDelete": "cascade"
  //   },
  //   "itemGrants": {
  //     "retributiveStrike": {
  //       "id": "WVHbj9LljCTovdsv",
  //       "onDelete": "detach"
  //     }
  //   }
  // }

  // retributive strike
  //   "pf2e": {
  //     "grantedBy": {
  //       "id": "HGWkTEatliHgDaEu",
  //       "onDelete": "cascade"
  //     }

  #slugNameMatch(f, slug) {
    return slug === Seasoning.slug(f.name)
    || slug === Seasoning.slug(f.foundryValue)
    || slug === Seasoning.slug(Seasoning.getClassAdjustedSpecialNameLowerCase(f.name, this.source.class))
    || slug === Seasoning.slug(Seasoning.getAncestryAdjustedSpecialNameLowerCase(f.name, this.source.ancestry))
    || slug === Seasoning.slug(Seasoning.getHeritageAdjustedSpecialNameLowerCase(f.name, this.source.heritage))
    || slug === Seasoning.slug(f.originalName)
    || slug === Seasoning.slug(Seasoning.getClassAdjustedSpecialNameLowerCase(f.originalName, this.source.class))
    || slug
      === Seasoning.slug(Seasoning.getAncestryAdjustedSpecialNameLowerCase(f.originalName, this.source.ancestry))
    || slug
      === Seasoning.slug(Seasoning.getHeritageAdjustedSpecialNameLowerCase(f.originalName, this.source.heritage))
    || (src_utils.allowDualClasses()
      && (slug
        === Seasoning.slug(Seasoning.getDualClassAdjustedSpecialNameLowerCase(f.name, this.source.dualClass))
        || slug
          === Seasoning.slug(
            Seasoning.getDualClassAdjustedSpecialNameLowerCase(f.originalName, this.source.dualClass)
          )));
  }

  #parsedFeatureMatch(type, document, slug, { ignoreAdded, isChoiceMatch = false, featType = null } = {}) {
    if (type === "feats" && document) {
      const hintMatch = this.parsed[type].find((f) =>
        (!ignoreAdded || (ignoreAdded && !f.added))
        && f.isChild
        && f.nameHint
        && Seasoning.slug(document.name) === Seasoning.slug(f.nameHint)
        && this.#slugNameMatch(f, slug)
      );
      if (hintMatch) {
        hintMatch.rank = -10;
        return hintMatch;
      }
    }
    // console.warn(`Trying to find ${slug} in ${type}, ignoreAdded? ${ignoreAdded}`, this.parsed[type]);
    const parsedMatch = this.parsed[type].find((f) =>
      (!ignoreAdded || (ignoreAdded && !f.added))
        && (
          featType === null
          || f.type === featType
        )
        && !f.isChoice
        && this.#slugNameMatch(f, slug)
    );
    if (parsedMatch || !document) return parsedMatch;

    const extraMatch = this.parsed[type].find((f) =>
      // (!ignoreAdded || (ignoreAdded && !f.added))
      f.extra
      && f.added
      && !f.isChoice
      && Seasoning.slug(f.name) === (document.system.slug ?? Seasoning.slug(document.name))
      && Seasoning.slug(f.extra) === slug
    );
    if (extraMatch) return extraMatch;

    if (isChoiceMatch) {
      // console.warn("Specials check", {
      //   document,
      //   type,
      //   slug,
      // });
      const choiceMatch = this.parsed[type].find((f) => f.isChoice && !f.added && Seasoning.slug(f.name) === slug);
      return choiceMatch;
    }
    return undefined;
  }

  #generatedResultMatch(type, slug) {
    const featMatch = this.result[type].find((f) => slug === f.system.slug);
    return featMatch;
  }

  #findAllFeatureMatch(document, slug, { ignoreAdded, isChoiceMatch = false, featType = null } = {}) {
    // console.warn("Finding all feature matches", { document, slug, ignoreAdded, isChoiceMatch, featType });
    const featMatch = this.#parsedFeatureMatch("feats", document, slug, { ignoreAdded, featType });
    if (featMatch) return featMatch;
    const specialMatch = this.#parsedFeatureMatch("specials", document, slug, { ignoreAdded, isChoiceMatch });
    if (specialMatch) return specialMatch;
    const deityMatch = this.#generatedResultMatch("deity", slug);
    return deityMatch;
    // const classMatch = this.#generatedResultMatch("class", slug);
    // return classMatch;
    // const equipmentMatch = this.#generatedResultMatch("equipment", slug);
    // return equipmentMatch;
  }

  #createGrantedItem(document, parent, { itemGrantName = null, originType = null, applyFeatLocation = false } = {}) {
    src_logger.debug(`Adding granted item flags to ${document.name} (parent ${parent.name}) with originType "${originType}", and will applyFeatLocation? ${applyFeatLocation}`);
    if (itemGrantName) {
      const camelCase = Seasoning.slugD(itemGrantName ?? document.system.slug ?? document.name);
      setProperty(parent, `flags.pf2e.itemGrants.${camelCase}`, { id: document._id, onDelete: "detach" });
      setProperty(document, "flags.pf2e.grantedBy", { id: parent._id, onDelete: "cascade" });
    }
    this.autoFeats.push(document);
    this.result.feats.push(document);
    const matchOptions = { ignoreAdded: true, featType: originType };
    const featureMatch
      = this.#findAllFeatureMatch(document, document.system.slug ?? Seasoning.slug(document.name), matchOptions)
      ?? (document.name.includes("(")
        ? this.#findAllFeatureMatch(document, Seasoning.slug(document.name.split("(")[0].trim()), matchOptions)
        : undefined);

    if (featureMatch) {
      src_logger.debug(`Found feature match for ${document.name}`, { featureMatch });
      const existingMatch = false;
      // featureMatch.sourceType
      //   ? this.parsed[featureMatch.sourceType].some((f) => f.addedId === document._id)
      //   : false;
      if (this.devMode && existingMatch) {
        src_logger.warn(`create Granted Item Existing match for ${document.name}`, { featureMatch, existingMatch, document });
      }
      // console.warn(`Match for ${document.name} createGrantedItem`, { featureMatch, existingMatch, document });
      if (hasProperty(featureMatch, "added") && !existingMatch) {
        featureMatch.added = true;
        featureMatch.addedId = document._id;
        if (applyFeatLocation) this.#generateFoundryFeatLocation(document, featureMatch);
      }

      return;
    }
    if (document.type !== "action")
      src_logger.warn(
        `Unable to find parsed feature match for granted feature ${document.name}. This might not be an issue, but might indicate feature duplication.`,
        { document, parent }
      );
  }


  static #getLowestChoiceRank(choices) {
    return choices.reduce((p, c) => {
      return p.rank > c.rank ? c : p;
    });
  }

  async #featureChoiceMatch(document, choices, ignoreAdded, adjustName, choiceHint = null) {
    const matches = [];
    for (const choice of choices) {
      const doc = adjustName ? game.i18n.localize(choice.label) : await fromUuid(choice.value);
      if (!doc) continue;
      const slug = adjustName
        ? Seasoning.slug(doc)
        : doc.system.slug === null
          ? Seasoning.slug(doc.name)
          : doc.system.slug;
      const featMatch = this.#findAllFeatureMatch(document, slug, { ignoreAdded, isChoiceMatch: false });
      if (featMatch) {
        matches.push({
          slug,
          rank: featMatch.rank,
          choice,
          featMatch,
        });
      }
    }
    if (matches.length > 0) {
      if (choiceHint) {
        const hintMatch = matches.find((m) => m.slug === Seasoning.slug(choiceHint));
        if (hintMatch) return hintMatch;
      }
      if (this.devMode) src_logger.warn(`MATCHES`, matches);
      const match = Pathmuncher.#getLowestChoiceRank(matches);
      const featMatch = this.#findAllFeatureMatch(document, match.slug, { ignoreAdded });
      const existingMatch = false;
      // featMatch.sourceType
      //   ? this.parsed[featMatch.sourceType].some((f) => f.addedId === document._id)
      //   : false;
      if (this.devMode && existingMatch) {
        src_logger.warn(`Feature Choice Existing match for ${document.name}`, { featMatch, existingMatch, document });
      }
      // console.warn(`Match for ${document.name} featureChoiceMatch`, { match, featMatch, existingMatch, document });
      if (adjustName && hasProperty(featMatch, "added") && !existingMatch) {
        featMatch.added = true;
        featMatch.addedId = document._id;
      }
      src_logger.debug("Choices evaluated", { choices, document, featMatch, match, matches });
      return match.choice;
    } else {
      return undefined;
    }
  }

  async #featureChoiceMatchNoUUID(document, choices, cleansedChoiceSet) {
    const matches = [];
    for (const choice of choices) {
      const featMatch = this.#findAllFeatureMatch(document, choice.value, { ignoreAdded: true, isChoiceMatch: true });
      if (featMatch) {
        matches.push({
          rank: featMatch.rank,
          choice,
        });
      }
    }
    if (matches.length > 0) {
      const match = Pathmuncher.#getLowestChoiceRank(matches);
      const featMatch = this.#findAllFeatureMatch(document, match.choice.value, { ignoreAdded: true, isChoiceMatch: true });

      const existingMatch = false;
      // featMatch.sourceType
      //   ? this.parsed[featMatch.sourceType].some((f) => f.addedId === document._id)
      //   : false;

      if (this.devMode && existingMatch) {
        src_logger.warn(`NoUUID Existing match for ${document.name}`, { featMatch, existingMatch, document });
      }
      // console.warn(`Match for ${document.name} featureChoiceMatchNoUUID`, { match, featMatch, existingMatch, document });
      if (featMatch && !existingMatch) {
        featMatch.added = true;
        featMatch.addedId = document._id;
        match.choice.nouuid = true;
      }
      src_logger.debug("No UUID Choices evaluated", { choices, cleansedChoiceSet, document, featMatch, match, matches });
      return match.choice;
    } else {
      return undefined;
    }
  }

  static getFlag(document, ruleSet) {
    return typeof ruleSet.flag === "string" && ruleSet.flag.length > 0
      ? ruleSet.flag.replace(/[^-a-z0-9]/gi, "")
      : Seasoning.slugD(document.system.slug ?? document.system.name ?? document.name);
  }

  async #evaluateChoices(document, choiceSet, choiceHint) {
    src_logger.debug(`Evaluating choices for ${document.name}`, { document, choiceSet, choiceHint });
    const tempActor = await this.#generateTempActor([document], false, false, true);
    const cleansedChoiceSet = deepClone(choiceSet);
    try {
      const item = tempActor.getEmbeddedDocument("Item", document._id);
      const choiceSetRules = new game.pf2e.RuleElements.all.ChoiceSet(cleansedChoiceSet, { parent: item });
      const rollOptions = [tempActor.getRollOptions(), item.getRollOptions("item")].flat();
      const choices = await choiceSetRules.inflateChoices(rollOptions, []);

      src_logger.debug("Starting choice evaluation", {
        document,
        choiceSet,
        item,
        choiceSetRules,
        rollOptions,
        choices,
      });

      if (cleansedChoiceSet.choices?.query) {
        const nonFilteredChoices = await choiceSetRules.inflateChoices(rollOptions, [item]);
        const queryResults = await choiceSetRules.queryCompendium(cleansedChoiceSet.choices, rollOptions, [item]);
        src_logger.debug("Query Result", { queryResults, nonFilteredChoices });
      }

      src_logger.debug("Evaluating choiceset", cleansedChoiceSet);
      const choiceMatch = await this.#featureChoiceMatch(document, choices, true, cleansedChoiceSet.adjustName, choiceHint);
      src_logger.debug("choiceMatch result", choiceMatch);
      if (choiceMatch) {
        choiceMatch.choiceQueryResults = deepClone(choices);
        return choiceMatch;
      }

      if (typeof cleansedChoiceSet.choices === "string" || Array.isArray(choices)) {
        const featureMatch = await this.#featureChoiceMatchNoUUID(document, choices, cleansedChoiceSet);
        if (featureMatch) {
          return featureMatch;
        }
      }

      let tempSet = deepClone(choiceSet);
      src_logger.debug(`Starting dynamic selection for ${document.name}`, { document, choiceSet, tempSet, Pathmuncher: this });
      await choiceSetRules.preCreate({ itemSource: item, ruleSource: tempSet, pendingItems: [item], tempItems: [] });
      // console.warn("chociesetdata", {
      //   choiceSetRules,
      //   selection: choiceSetRules.selection,
      //   choiceSet: deepClone(choiceSet),
      //   tempSet: deepClone(tempSet),
      // });
      if (tempSet.selection) {
        const lookedUpChoice = choices.find((c) => c.value === tempSet.selection);
        src_logger.debug("lookedUpChoice", lookedUpChoice);
        if (lookedUpChoice) lookedUpChoice.choiceQueryResults = deepClone(choices);
        // set some common lookups here, e.g. deities are often not set!
        if (lookedUpChoice && cleansedChoiceSet.flag === "deity") {
          if (lookedUpChoice.label && lookedUpChoice.label !== "") {
            setProperty(this.result.character, "system.details.deity.value", lookedUpChoice.label);
            await this.#processGenericCompendiumLookup("deities", lookedUpChoice.label, "deity");
            const camelCase = Seasoning.slugD(this.result.deity[0].system.slug);
            setProperty(document, `flags.pf2e.itemGrants.${camelCase}`, {
              id: this.result.deity[0]._id,
              onDelete: "detach",
            });
            setProperty(this.result.deity[0], "flags.pf2e.grantedBy", { id: document._id, onDelete: "cascade" });
            this.autoAddedFeatureIds.add(`${lookedUpChoice.value.split(".").pop()}deity`);
          }
        }
        return lookedUpChoice;
      }
    } catch (err) {
      src_logger.error("Whoa! Something went major bad wrong during choice evaluation", {
        err,
        tempActor: tempActor.toObject(),
        document: duplicate(document),
        choiceSet: duplicate(cleansedChoiceSet),
      });
      throw err;
    } finally {
      await Actor.deleteDocuments([tempActor._id]);
    }

    src_logger.debug("Evaluate Choices failed", { choiceSet: cleansedChoiceSet, tempActor, document });
    return undefined;
  }

  async #resolveInjectedUuid(document, ruleEntry) {
    const tempActor = await this.#generateTempActor([document], false, false);
    const cleansedRuleEntry = deepClone(ruleEntry);
    try {
      const item = tempActor.getEmbeddedDocument("Item", document._id);
      // console.warn("creating grant item");
      const grantItemRule = new game.pf2e.RuleElements.all.GrantItem(cleansedRuleEntry, { parent: item });
      // console.warn("Begining uuid resovle");
      const uuid = grantItemRule.resolveInjectedProperties(grantItemRule.uuid, { warn: false });

      src_logger.debug("uuid selection", {
        document,
        choiceSet: ruleEntry,
        item,
        grantItemRule,
        uuid,
      });
      if (uuid) return uuid;
    } catch (err) {
      src_logger.error("Whoa! Something went major bad wrong during uuid evaluation", {
        err,
        tempActor: tempActor.toObject(),
        document: duplicate(document),
        ruleEntry: duplicate(cleansedRuleEntry),
      });
      throw err;
    } finally {
      await Actor.deleteDocuments([tempActor._id]);
    }

    src_logger.debug("Evaluate UUID failed", { choiceSet: cleansedRuleEntry, tempActor, document });
    return undefined;
  }

  async #checkRule(document, rule) {
    const tempActor = await this.#generateTempActor([document], true);
    const cleansedRule = deepClone(rule);
    try {
      const item = tempActor.getEmbeddedDocument("Item", document._id);
      const ruleElement = cleansedRule.key === "ChoiceSet"
        ? new game.pf2e.RuleElements.all.ChoiceSet(cleansedRule, { parent: item })
        : new game.pf2e.RuleElements.all.GrantItem(cleansedRule, { parent: item });
      const rollOptions = [tempActor.getRollOptions(), item.getRollOptions("item")].flat();

      if (rule.predicate) {
        const predicate = ruleElement.resolveInjectedProperties(ruleElement.predicate);
        if (!predicate.test(rollOptions)) return false;
      }

      const choices = cleansedRule.key === "ChoiceSet"
        ? await ruleElement.inflateChoices(rollOptions, [item])
        : [ruleElement.resolveValue()];

      const isGood = cleansedRule.key === "ChoiceSet"
        ? (await this.#featureChoiceMatch(document, choices, false)) !== undefined
        : ruleElement.test(rollOptions);

      src_logger.debug("Checking rule", {
        tempActor,
        cleansedRule,
        item,
        ruleElement,
        rollOptions,
        choices,
        isGood,
      });
      return isGood;
    } catch (err) {
      src_logger.error("Something has gone most wrong during rule checking", {
        document,
        rule: cleansedRule,
        tempActor,
      });
      throw err;
    } finally {
      await Actor.deleteDocuments([tempActor._id]);
    }
  }

  async #checkRulePredicate(document, rule) {
    const tempActor = await this.#generateTempActor([document], true);
    const cleansedRule = deepClone(rule);
    try {
      const item = tempActor.getEmbeddedDocument("Item", document._id);
      const ruleElement = cleansedRule.key === "ChoiceSet"
        ? new game.pf2e.RuleElements.all.ChoiceSet(cleansedRule, { parent: item })
        : new game.pf2e.RuleElements.all.GrantItem(cleansedRule, { parent: item });
      const rollOptions = [tempActor.getRollOptions(), item.getRollOptions("item")].flat();

      if (rule.predicate) {
        const predicate = ruleElement.resolveInjectedProperties(ruleElement.predicate);
        return predicate.test(rollOptions);
      } else {
        return true;
      }
    } catch (err) {
      src_logger.error("Something has gone most wrong during rule predicate checking", {
        document,
        rule: cleansedRule,
        tempActor,
      });
      throw err;
    } finally {
      await Actor.deleteDocuments([tempActor._id]);
    }
  }

  static adjustDocumentName(featureName, label) {
    const localLabel = game.i18n.localize(label);
    if (featureName.trim().toLowerCase() === localLabel.trim().toLowerCase()) return featureName;
    const name = `${featureName} (${localLabel})`;
    const pattern = (() => {
      const escaped = RegExp.escape(localLabel);
      return new RegExp(`\\(${escaped}\\) \\(${escaped}\\)$`);
    })();
    return name.replace(pattern, `(${localLabel})`);
  }

  // eslint-disable-next-line complexity, no-unused-vars
  async #addGrantedRules(document, originType = null, choiceHint = null) {
    if (document.system.rules.length === 0) return;
    src_logger.debug(`addGrantedRules for ${document.name}`, duplicate(document));

    if (
      hasProperty(document, "system.level.value")
      && document.system.level.value > this.result.character.system.details.level.value
    ) {
      return;
    }

    const rulesToKeep = [];
    this.allFeatureRules[document._id] = deepClone(document.system.rules);
    this.autoAddedFeatureRules[document._id] = [];
    this.promptRules[document._id] = [];
    let featureRenamed = false;

    for (const ruleEntry of document.system.rules) {
      src_logger.debug(`Ping ${document.name} rule key: ${ruleEntry.key}`, ruleEntry);
      if (!["ChoiceSet", "GrantItem"].includes(ruleEntry.key)) {
        // size work around due to Pathbuilder not always adding the right size to json
        if (ruleEntry.key === "CreatureSize") this.size = ruleEntry.value;
        this.autoAddedFeatureRules[document._id].push(ruleEntry);
        rulesToKeep.push(ruleEntry);
        continue;
      }
      if (NO_AUTO_CHOICE().includes(document.name)) {
        src_logger.debug(`Deliberately skipping ${document.name} auto choice detection`);
        rulesToKeep.push(ruleEntry);
        continue;
      }
      src_logger.debug(`Checking ${document.name} rule key: ${ruleEntry.key}`, {
        ruleEntry,
        docRules: deepClone(document.system.rules),
        document: deepClone(document),
      });

      if (ruleEntry.key === "ChoiceSet" && ruleEntry.predicate) {
        src_logger.debug(`Checking for predicates`, {
          ruleEntry,
          document,
        });
        const testResult = await this.#checkRulePredicate(duplicate(document), ruleEntry);
        if (!testResult) {
          const data = { document, ruleEntry, testResult };
          src_logger.debug(
            `The test failed for ${document.name} rule key: ${ruleEntry.key} (This is probably not a problem).`,
            data
          );
          rulesToKeep.push(ruleEntry);
          continue;
        }
      }

      const choice = ruleEntry.key === "ChoiceSet" ? await this.#evaluateChoices(document, ruleEntry, choiceHint) : undefined;
      const uuid = ruleEntry.key === "GrantItem" ? await this.#resolveInjectedUuid(document, ruleEntry) : choice?.value;

      if (choice?.choiceQueryResults) {
        ruleEntry.choiceQueryResults = choice.choiceQueryResults;
      }

      const flagName = Pathmuncher.getFlag(document, ruleEntry);
      // if (flagName && choice?.value && !hasProperty(document, `flags.pf2e.rulesSelections.${flagName}`)) {
      //   setProperty(document, `flags.pf2e.rulesSelections.${flagName}`, choice.value);
      // }

      src_logger.debug(`UUID for ${document.name}: "${uuid}"`, { document, ruleEntry, choice, uuid });
      const ruleFeature = uuid && typeof uuid === "string" ? await fromUuid(uuid) : undefined;
      // console.warn("ruleFeature", ruleFeature);
      if (ruleFeature) {
        const featureDoc = ruleFeature.toObject();
        featureDoc._id = foundry.utils.randomID();
        if (featureDoc.system.rules) this.allFeatureRules[featureDoc._id] = deepClone(featureDoc.system.rules);
        setProperty(featureDoc, "flags.pathmuncher.origin.uuid", uuid);
        src_logger.debug(`Found rule feature ${featureDoc.name} for ${document.name} for`, ruleEntry);

        if (choice) {
          ruleEntry.selection = choice.value;
          setProperty(document, `flags.pf2e.rulesSelections.${flagName}`, choice.value);
        }

        if (src_utils.isString(ruleEntry.rollOption)) {
          ruleEntry.rollOption = `${ruleEntry.rollOption}:${flagName}`;
        }

        if (ruleEntry.predicate && ruleEntry.key === "GrantItem") {
          src_logger.debug(`Checking for grantitem predicates`, {
            ruleEntry,
            document,
            featureDoc,
          });
          const testResult = await this.#checkRule(featureDoc, ruleEntry);
          if (!testResult) {
            const data = { document, ruleEntry, featureDoc, testResult };
            src_logger.debug(
              `The test failed for ${document.name} rule key: ${ruleEntry.key} (This is probably not a problem).`,
              data
            );
            rulesToKeep.push(ruleEntry);
            // this.autoAddedFeatureRules[document._id].push(ruleEntry);
            continue;
          } else {
            src_logger.debug(`The test passed for ${document.name} rule key: ${ruleEntry.key}`, ruleEntry);
            // this.autoAddedFeatureRules[document._id].push(ruleEntry);
            // eslint-disable-next-line max-depth
            // if (!ruleEntry.flag) ruleEntry.flag = Seasoning.slugD(document.name);
            ruleEntry.pathmuncherImport = true;
            rulesToKeep.push(ruleEntry);
          }
        }

        // setProperty(ruleEntry, `preselectChoices.${ruleEntry.flag}`, ruleEntry.selection ?? ruleEntry.uuid);

        if (this.autoAddedFeatureIds.has(`${ruleFeature.id}${ruleFeature.type}`)) {
          src_logger.debug(`Feature ${featureDoc.name} found for ${document.name}, but has already been added (${ruleFeature.id})`, ruleFeature);
          // this.autoAddedFeatureRules[document._id].push(ruleEntry);
          // rulesToKeep.push(ruleEntry);
          if (ruleEntry.key === "GrantItem" && ruleEntry.flag) {
            this.autoAddedFeatureRules[document._id].push(ruleEntry);
            rulesToKeep.push(ruleEntry);
          }
          continue;
        } else {
          src_logger.debug(`Feature ${featureDoc.name} not found for ${document.name}, adding (${ruleFeature.id})`, ruleFeature);
          if (ruleEntry.selection || ruleEntry.flag) {
            rulesToKeep.push(ruleEntry);
          }
          this.autoAddedFeatureIds.add(`${ruleFeature.id}${ruleFeature.type}`);
          featureDoc._id = foundry.utils.randomID();
          this.#createGrantedItem(featureDoc, document, { itemGrantName: flagName, applyFeatLocation: false });
          if (hasProperty(featureDoc, "system.rules")) await this.#addGrantedRules(featureDoc);
        }
      } else if (getProperty(choice, "nouuid")) {
        src_logger.debug("Parsed no id rule", { choice, uuid, ruleEntry });
        if (!ruleEntry.flag) ruleEntry.flag = Seasoning.slugD(document.name);
        ruleEntry.selection = choice.value;
        if (choice.label) document.name = `${document.name} (${choice.label})`;
        rulesToKeep.push(ruleEntry);
      } else if (choice && uuid && !hasProperty(ruleEntry, "selection")) {
        src_logger.debug("Parsed odd choice rule", { choice, uuid, ruleEntry });
        // if (!ruleEntry.flag) ruleEntry.flag = Seasoning.slugD(document.name);
        ruleEntry.selection = choice.value;
        if (
          ((!ruleEntry.adjustName && choice.label && typeof uuid === "object")
          || (!choice.adjustName && choice.label))
          && !featureRenamed
        ) {
          document.name = Pathmuncher.adjustDocumentName(document.name, choice.label);
          featureRenamed = true;
        }
        rulesToKeep.push(ruleEntry);
      } else {
        src_logger.debug(`Final rule fallback for ${document.name}`, ruleEntry);
        const data = {
          uuid: ruleEntry.uuid,
          document,
          ruleEntry,
          choice,
        };
        if (
          ruleEntry.key === "GrantItem"
          && (ruleEntry.flag || ruleEntry.selection || ruleEntry.uuid.startsWith("Compendium"))
        ) {
          rulesToKeep.push(ruleEntry);
        } else if (ruleEntry.key === "ChoiceSet" && !hasProperty(ruleEntry, "flag")) {
          src_logger.debug("Prompting user for choices", ruleEntry);
          this.promptRules[document._id].push(ruleEntry);
          rulesToKeep.push(ruleEntry);
        } else if (ruleEntry.key === "ChoiceSet" && !choice && !uuid) {
          src_logger.warn("Unable to determine choice asking", data);
          rulesToKeep.push(ruleEntry);
          this.promptRules[document._id].push(ruleEntry);
        }
        src_logger.warn("Unable to determine granted rule feature, needs better parser", data);
      }
      if (ruleEntry.adjustName && choice?.label && !featureRenamed) {
        document.name = Pathmuncher.adjustDocumentName(document.name, choice.label);
      }
      this.autoAddedFeatureRules[document._id].push(ruleEntry);

      src_logger.debug(`End result for ${document.name} for a ${ruleEntry.key}`, {
        document: deepClone(document),
        rulesToKeep: deepClone(rulesToKeep),
        ruleEntry: deepClone(ruleEntry),
        choice: deepClone(choice),
        uuid: deepClone(uuid),
      });
    }
    // eslint-disable-next-line require-atomic-updates
    document.system.rules = rulesToKeep;

    src_logger.debug(`Final status for ${document.name}`, {
      document: deepClone(document),
      rulesToKeep: deepClone(rulesToKeep),
    });
  }

  async #addGrantedItems(document, { originType = null, applyFeatLocation = false, choiceHint = null } = {}) {
    const immediateDiveAdd = src_utils.setting("USE_IMMEDIATE_DEEP_DIVE");
    const subRuleDocuments = [];
    if (hasProperty(document, "system.items")) {
      src_logger.debug(`addGrantedItems for ${document.name}`, duplicate(document));
      if (!this.autoAddedFeatureItems[document._id]) {
        this.autoAddedFeatureItems[document._id] = duplicate(document.system.items);
      }
      const failedFeatureItems = {};
      for (const [key, grantedItemFeature] of Object.entries(document.system.items).sort(([, a], [, b]) => a.level - b.level)) {
        src_logger.debug(`Checking ${document.name} granted item ${grantedItemFeature.name}, level(${grantedItemFeature.level}) with key: ${key}`, grantedItemFeature);
        if (grantedItemFeature.level > getProperty(this.result.character, "system.details.level.value")) continue;
        const feature = await fromUuid(grantedItemFeature.uuid);
        if (!feature) {
          const data = { uuid: grantedItemFeature.uuid, grantedFeature: grantedItemFeature, feature };
          src_logger.warn("Unable to determine granted item feature, needs better parser", data);
          failedFeatureItems[key] = grantedItemFeature;
          continue;
        }
        this.autoAddedFeatureIds.add(`${feature.id}${feature.type}`);
        const featureDoc = feature.toObject();
        featureDoc._id = foundry.utils.randomID();
        setProperty(featureDoc.system, "location", document._id);
        this.#createGrantedItem(featureDoc, document, { originType, applyFeatLocation });
        if (hasProperty(featureDoc, "system.rules")) {
          src_logger.debug(`Processing granted rules for granted item document ${featureDoc.name}`, duplicate(featureDoc));
          if (immediateDiveAdd) {
            await this.#addGrantedItems(featureDoc, { originType, applyFeatLocation });
          } else {
            subRuleDocuments.push(featureDoc);
          }
        }
      }
      // eslint-disable-next-line require-atomic-updates
      document.system.items = failedFeatureItems;

      if (!immediateDiveAdd) {
        for (const subRuleDocument of subRuleDocuments) {
          src_logger.debug(
            `Processing granted rules for granted item document ${subRuleDocument.name}`,
            duplicate(subRuleDocument)
          );
          await this.#addGrantedItems(subRuleDocument, { originType, applyFeatLocation, choiceHint });
        }
      }
    }

    if (hasProperty(document, "system.rules")) {
      src_logger.debug(`Processing granted rules for core document ${document.name}`, duplicate(document));
      await this.#addGrantedRules(document, originType, choiceHint);
    }
  }

  #determineAbilityBoosts() {
    const breakdown = getProperty(this.source, "abilities.breakdown");
    const useCustomStats
      = breakdown
      && breakdown.ancestryFree.length === 0
      && breakdown.ancestryBoosts.length === 0
      && breakdown.ancestryFlaws.length === 0
      && breakdown.backgroundBoosts.length === 0
      && breakdown.classBoosts.length === 0;
    if (breakdown && !useCustomStats) {
      this.boosts.custom = false;
      const classBoostMap = {};
      for (const [key, boosts] of Object.entries(this.source.abilities.breakdown.mapLevelledBoosts)) {
        if (key <= this.source.level) {
          classBoostMap[key] = boosts.map((ability) => ability.toLowerCase());
        }
      }
      setProperty(this.result.character, "system.build.attributes.boosts", classBoostMap);
      this.boosts.class = classBoostMap;

      // ancestry
    } else {
      this.boosts.custom = true;
      ["str", "dex", "con", "int", "wis", "cha"].forEach((key) => {
        const mod = Math.min(Math.max(Math.trunc((this.source.abilities[key] - 10) / 2), -5), 10) || 0;
        setProperty(this.result.character, `system.abilities.${key}.mod`, mod);
      });
    }

    if (breakdown?.classBoosts.length > 0) {
      this.keyAbility = breakdown.classBoosts[0].toLowerCase();
    } else {
      this.keyAbility = this.source.keyability;
    }
    setProperty(this.result.character, "system.details.keyability.value", this.keyAbility);
  }

  #generateBackgroundAbilityBoosts() {
    if (!this.result.background[0]) return;
    const breakdown = getProperty(this.source, "abilities.breakdown");
    for (const boost of breakdown.backgroundBoosts) {
      for (const [key, boostSet] of Object.entries(this.result.background[0].system.boosts)) {
        if (this.result.background[0].system.boosts[key].selected) continue;
        if (boostSet.value.includes(boost.toLowerCase())) {
          this.result.background[0].system.boosts[key].selected = boost.toLowerCase();
          break;
        }
      }
    }
  }

  #generateAncestryAbilityBoosts() {
    if (!this.result.ancestry[0]) return;
    const breakdown = getProperty(this.source, "abilities.breakdown");
    const boosts = [];
    breakdown.ancestryBoosts.concat(breakdown.ancestryFree).forEach((boost) => {
      for (const [key, boostSet] of Object.entries(this.result.ancestry[0].system.boosts)) {
        if (this.result.ancestry[0].system.boosts[key].selected) continue;
        if (boostSet.value.includes(boost.toLowerCase())) {
          this.result.ancestry[0].system.boosts[key].selected = boost.toLowerCase();
          boosts.push(boost.toLowerCase());
          break;
        }
      }
    });
    if (breakdown.ancestryBoosts.length === 0) {
      setProperty(this.result.ancestry[0], "system.alternateAncestryBoosts", boosts);
    }
  }

  #setAbilityBoosts() {
    if (this.boosts.custom) return;
    this.#generateBackgroundAbilityBoosts();
    this.#generateAncestryAbilityBoosts();

    this.result.class[0].system.boosts = this.boosts.class;
  }

  static SKILL_LOOKUP = {
    "acrobatics": "acr",
    "arcana": "arc",
    "athletics": "ath",
    "crafting": "cra",
    "deception": "dec",
    "diplomacy": "dip",
    "intimidation": "itm",
    "medicine": "med",
    "nature": "nat",
    "occultism": "occ",
    "performance": "prf",
    "religion": "rel",
    "society": "soc",
    "stealth": "ste",
    "survival": "sur",
    "thievery": "thi",
  };

  #setSkills(removeSpecials = false) {
    for (const [key, value] of Object.entries(Pathmuncher.SKILL_LOOKUP)) {
      const calculatedValue = removeSpecials
        && (this.source.specials.some((s) => s.toLowerCase() === key)
         || this.parsed.specials.some((s) => s.name.toLowerCase() === key))
        ? 0
        : this.source.proficiencies[key] / 2;
      setProperty(this.result.character, `system.skills.${value}.rank`, calculatedValue);
    };
  }

  #setSaves() {
    ["fortitude", "reflex", "will"].forEach((key) => {
      setProperty(this.result.character, `system.savingThrows.${key}`, this.source.proficiencies[key] / 2);
    });
  }

  #setMartials() {
    ["advanced", "heavy", "light", "medium", "unarmored", "martial", "simple", "unarmed"].forEach((key) => {
      setProperty(this.result.character, `system.martial.${key}.rank`, this.source.proficiencies[key] / 2);
    });
  }

  async #processCore() {
    setProperty(this.result.character, "name", this.source.name);
    setProperty(this.result.character, "prototypeToken.name", this.source.name);
    setProperty(this.result.character, "system.details.level.value", this.source.level);
    if (this.source.age !== "Not set") setProperty(this.result.character, "system.details.age.value", this.source.age);
    if (this.source.gender !== "Not set") setProperty(this.result.character, "system.details.gender.value", this.source.gender);
    // setProperty(this.result.character, "system.details.alignment.value", this.source.alignment);

    if (this.source.deity !== "Not set") setProperty(this.result.character, "system.details.deity.value", this.source.deity);
    this.size = Seasoning.getSizeValue(this.source.size);
    setProperty(this.result.character, "system.traits.size.value", this.size);
    setProperty(this.result.character, "system.traits.languages.value", this.source.languages.map((l) => l.toLowerCase()));

    this.#processSenses();

    this.#determineAbilityBoosts();
    this.#setSaves();
    this.#setMartials();

    // setProperty(this.result.character, "system.attributes.perception.rank", this.source.proficiencies.perception / 2);
    // setProperty(this.result.character, "system.attributes.classDC.rank", this.source.proficiencies.classDC / 2);
  }

  #indexFind(index, arrayOfNameMatches) {
    for (const name of arrayOfNameMatches) {
      const indexMatch = index.find((i) => {
        const slug = i.system.slug ?? Seasoning.slug(i.name);
        return (
          slug === Seasoning.slug(name)
          || slug === Seasoning.slug(Seasoning.getClassAdjustedSpecialNameLowerCase(name, this.source.class))
          || slug === Seasoning.slug(Seasoning.getAncestryAdjustedSpecialNameLowerCase(name, this.source.ancestry))
          || slug === Seasoning.slug(Seasoning.getHeritageAdjustedSpecialNameLowerCase(name, this.source.heritage))
          || (src_utils.allowDualClasses()
            && slug === Seasoning.slug(Seasoning.getDualClassAdjustedSpecialNameLowerCase(name, this.source.dualClass)))
        );
      });
      if (indexMatch) return indexMatch;
    }
    return undefined;
  }

  #findInPackIndexes(type, arrayOfNameMatches) {
    const matcher = this.compendiumMatchers[type];
    for (const [packName, index] of Object.entries(matcher.indexes)) {
      const indexMatch = this.#indexFind(index, arrayOfNameMatches);
      if (indexMatch) return { i: indexMatch, pack: matcher.packs[packName] };
    }
    return undefined;
  }

  #sortParsedFeats() {
    // eslint-disable-next-line complexity
    this.parsed.feats.sort((f1, f2) => {
      const f1RefUndefined = !(typeof f1.type === "string" || f1.type instanceof String);
      const f2RefUndefined = !(typeof f2.type === "string" || f2.type instanceof String);
      if (f1RefUndefined || f2RefUndefined) {
        if (f1RefUndefined && f2RefUndefined) {
          return 0;
        } else if (f1RefUndefined) {
          return 1;
        } else {
          return -1;
        }
      } else if (f1.type === "Awarded Feat" && f2.type === "Awarded Feat") {
        return (f1.level ?? 20) - (f2.level ?? 20);
      } else if (f1.type === "Awarded Feat") {
        return 1;
      } else if (f2.type === "Awarded Feat") {
        return -1;
      } else if ((f1.level ?? 20) === (f2.level ?? 20)) {
        const f1Index = constants.FEAT_PRIORITY.indexOf(f1.type);
        const f2Index = constants.FEAT_PRIORITY.indexOf(f2.type);
        if (f1Index > f2Index) {
          return 1;
        } else if (f1Index < f2Index) {
          return -1;
        } else {
          return 0;
        }
      } else {
        return (f1.level ?? 20) - (f2.level ?? 20);
      }
    });
  }

  // eslint-disable-next-line complexity
  async #generateFeatItems(type,
    { levelCap = null, typeFilter = null, excludeChild = false, excludeParents = false, excludeStandard = false } = {}
  ) {
    src_logger.debug(`Generate feat items for ${type} with level cap "${levelCap}" and filter "${typeFilter}"`);

    for (const featArray of [this.parsed.feats, this.parsed.specials]) {
      for (const pBFeat of featArray) {
        if (pBFeat.added) continue;
        if (levelCap && (pBFeat.level ?? 20) > levelCap) continue;
        if (typeFilter && pBFeat.type !== typeFilter) continue;
        if (excludeChild && pBFeat.isChild === true) continue;
        if (excludeParents && pBFeat.isParent === true) continue;
        if (excludeStandard && pBFeat.isStandard === true) continue;
        src_logger.debug(`Generating feature for ${pBFeat.name}`, pBFeat);
        if (this.devMode) src_logger.error(`Generating feature for ${pBFeat.name}`, { pBFeat, this: this });

        const indexMatch = this.#findInPackIndexes(type, [pBFeat.name, pBFeat.originalName]);
        const displayName = pBFeat.extra ? Pathmuncher.adjustDocumentName(pBFeat.name, pBFeat.extra) : pBFeat.name;
        if (!indexMatch) {
          src_logger.debug(`Unable to match feat ${displayName}`, {
            displayName,
            name: pBFeat.name,
            extra: pBFeat.extra,
            pBFeat,
            type,
          });
          this.check[pBFeat.originalName] = {
            name: displayName,
            type: "feat",
            details: {
              displayName,
              name: pBFeat.name,
              originalName: pBFeat.originalName,
              extra: pBFeat.extra,
              pBFeat,
              type,
            },
          };
          continue;
        }
        if (this.check[pBFeat.originalName]) delete this.check[pBFeat.originalName];
        pBFeat.added = true;
        if (this.autoAddedFeatureIds.has(`${indexMatch._id}${indexMatch.type}`)) {
          src_logger.debug("Feat included in class features auto add", { displayName, pBFeat, type });
          pBFeat.addedAutoId = `${indexMatch._id}_${indexMatch.type}`;
          continue;
        }

        const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
        const docData = doc.toObject();
        docData._id = foundry.utils.randomID();
        pBFeat.addedId = docData._id;
        // docData.name = displayName;

        this.#generateFoundryFeatLocation(docData, pBFeat);
        this.result.feats.push(docData);
        const options = {
          originType: typeFilter,
          applyFeatLocation: false,
          choiceHint: pBFeat.extra && pBFeat.extra !== "" ? pBFeat.extra : null,
        };
        await this.#addGrantedItems(docData, "feat", options);
      }
    }
  }

  async #generateSpecialItems(type) {
    for (const special of this.parsed.specials) {
      if (special.added) continue;
      src_logger.debug("Generating special for", special);
      const indexMatch = this.#findInPackIndexes(type, [special.name, special.originalName]);
      if (!indexMatch) {
        src_logger.debug(`Unable to match special ${special.name}`, { special: special.name, type });
        this.check[special.originalName] = {
          name: special.name,
          type: "special",
          details: { displayName: special.name, name: special.name, originalName: special.originalName, special },
        };
        continue;
      }
      special.added = true;
      if (this.check[special.originalName]) delete this.check[special.originalName];
      if (this.autoAddedFeatureIds.has(`${indexMatch._id}${indexMatch.type}`)) {
        src_logger.debug("Special included in class features auto add", { special: special.name, type });
        special.addedAutoId = `${indexMatch._id}_${indexMatch.type}`;
        continue;
      }

      const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
      const docData = doc.toObject();
      docData._id = foundry.utils.randomID();
      special.addedId = docData._id;
      this.result.feats.push(docData);
      await this.#addGrantedItems(docData, { applyFeatLocation: true });
    }
  }

  #resizeItem(item) {
    if (Seasoning.isPhysicalItemType(item.type)) {
      const resizeItem = item.type !== "treasure" && !["med", "sm"].includes(this.size);
      if (resizeItem) item.system.size = this.size;
    }
  }

  async #generateAdventurersPack() {
    const defaultCompendium = game.packs.get("pf2e.equipment-srd");
    const index = await defaultCompendium.getIndex({ fields: ["name", "type", "system.slug"] });


    const adventurersPack = this.parsed.equipment.find((e) => e.pbName === "Adventurer's Pack");
    if (adventurersPack) {
      const compendiumBackpack = await defaultCompendium.getDocument("3lgwjrFEsQVKzhh7");
      const backpackInstance = compendiumBackpack.toObject();
      adventurersPack.added = true;
      backpackInstance._id = foundry.utils.randomID();
      adventurersPack.addedId = backpackInstance._id;
      this.result.adventurersPack.item = adventurersPack;
      this.result.equipment.push(backpackInstance);
      for (const content of this.result.adventurersPack.contents) {
        const indexMatch = index.find((i) => i.system.slug === content.slug);
        if (!indexMatch) {
          src_logger.error(`Unable to match adventurers kit item ${content.name}`, content);
          continue;
        }

        const doc = await defaultCompendium.getDocument(indexMatch._id);
        const itemData = doc.toObject();
        itemData._id = foundry.utils.randomID();
        itemData.system.quantity = content.qty;
        itemData.system.containerId = backpackInstance?._id;
        this.#resizeItem(itemData);
        this.result.equipment.push(itemData);
      }
    }
  }

  async #generateContainers() {
    for (const [key, data] of Object.entries(this.source.equipmentContainers)) {
      if (data.foundryId) continue;
      const name = Seasoning.getFoundryEquipmentName(data.containerName);
      const indexMatch = this.compendiumMatchers["equipment"].getMatch(data.containerName, name);
      const id = foundry.utils.randomID();
      const doc = indexMatch
        ? await indexMatch.pack.getDocument(indexMatch.i._id)
        : await Item.create({ name: data.containerName, type: "backpack" }, { temporary: true });
      const itemData = doc.toObject();
      itemData._id = id;
      this.#resizeItem(itemData);
      this.result["equipment"].push(itemData);
      this.parsed.equipment.push({
        foundryName: name,
        pbName: data.containerName,
        originalName: data.containerName,
        name,
        qty: 1,
        added: true,
        inContainer: undefined,
        container: this.#getContainerData(key),
        foundryId: id,
      });
    }
  }

  async #generateEquipmentItems() {
    for (const e of this.parsed.equipment) {
      if (e.pbName === "Adventurer's Pack") continue;
      if (e.added) continue;
      if (Seasoning.IGNORED_EQUIPMENT().includes(e.pbName)) {
        e.added = true;
        e.addedAutoId = "ignored";
        continue;
      }
      src_logger.debug("Generating item for", e);
      const indexMatch = this.compendiumMatchers["equipment"].getMatch(e.pbName, e.foundryName);
      if (!indexMatch) {
        src_logger.error(`Unable to match ${e.pbName}`, e);
        this.bad.push({ pbName: e.pbName, type: "equipment", details: { e } });
        continue;
      }

      const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
      if (doc.type != "kit") {
        const itemData = doc.toObject();
        itemData._id = e.foundryId || foundry.utils.randomID();
        itemData.system.quantity = e.qty;
        const type = doc.type === "treasure" ? "treasure" : "equipment";
        if (e.inContainer) {
          const containerMatch = this.parsed.equipment.find((con) => con.container?.id === e.inContainer);
          if (containerMatch) {
            itemData.system.containerId = containerMatch.foundryId;
            itemData.system.equipped.carryType = "stowed";
          }
        }
        if (e.invested) {
          itemData.system.equipped.carryType = "worn";
          itemData.system.equipped.invested = true;
          itemData.system.equipped.inSlot = true;
          itemData.system.equipped.handsHeld = 0;
        }
        this.#resizeItem(itemData);
        this.result[type].push(itemData);
        e.addedId = itemData._id;
      }
      // eslint-disable-next-line require-atomic-updates
      e.added = true;
    }
  }

  async #processEquipmentItems() {
    // just in case it's in the equipment, pathbuilder should have translated this to items
    await this.#generateAdventurersPack();
    await this.#generateContainers();
    await this.#generateEquipmentItems();
  }

  static RUNE_SCALE = [
    "",
    "Minor",
    "Lesser",
    "Moderate",
    "Greater",
    "Major",
    "Supreme",
  ];

  static REINFORCING_DATA = {
    "Minor": {
      value: 1,
      hp: 44,
    },
    "Lesser": {
      value: 2,
      hp: 52,
    },
    "Moderate": {
      value: 3,
      hp: 64,
    },
    "Greater": {
      value: 4,
      hp: 80,
    },
    "Major": {
      value: 5,
      hp: 84,
    },
    "Supreme": {
      value: 6,
      hp: 108,
    },
  };

  static POTENCY_SCALE = [
    "",
    "striking",
    "greaterStriking",
    "majorStriking",
  ];

  static RESILIENT_SCALE = [
    "",
    "resilient",
    "greaterResilient",
    "majorResilient",
  ];

  // eslint-disable-next-line complexity
  static applyRunes(parsedItem, itemData, type) {
    if (itemData.type == "shield") {
      parsedItem.runes.forEach((rune) => {
        if (rune.startsWith("Reinforcing")) {
          const runeScale = rune.split("(").pop().split(")").shift().trim();
          const runeMatch = Pathmuncher.REINFORCING_DATA[runeScale];
          if (runeMatch) {
            itemData.system.runes.reinforcing = runeMatch.value;
            itemData.system.hp.value += runeMatch.hp;
          }
        } else {
          const runeScale = rune.split("(").pop().split(")").shift().trim();
          const runeLevel = Pathmuncher.RUNE_SCALE.indexOf(runeScale);
          const runeType = rune.split("(").shift().toLowerCase().trim();
          if (runeLevel !== -1) {
            itemData.system.runes[runeType] = runeLevel;
          }
        }
      });
    } else if (hasProperty(itemData, "system.runes.potency")) {
      itemData.system.runes.potency = parsedItem.pot;
      if (type === "weapon") {
        const striking = Pathmuncher.POTENCY_SCALE.indexOf(parsedItem.str);
        if (striking !== -1) itemData.system.runes.striking = striking;
      } else if (type === "armor") {
        const resilient = Pathmuncher.RESILIENT_SCALE.indexOf(parsedItem.res);
        if (resilient !== -1) itemData.system.runes.resilient = resilient;
      }
    }

    if (type === "armor" && parsedItem.worn
      && ((Number.isInteger(parsedItem.pot) && parsedItem.pot > 0)
        || (parsedItem.res && parsedItem.res !== "")
      )
    ) {
      itemData.system.equipped.invested = true;
    }

    if (hasProperty(itemData, "system.runes.property")) {
      parsedItem.runes.forEach((property) => {
        const resistantRegex = /Energy Resistant - (.*)/i;
        const resistantMatch = property.match(resistantRegex);
        const rune = resistantMatch
          ? `${resistantMatch[1]} Resistant`
          : property;
        itemData.system.runes.property.push(Seasoning.slugD(rune));
      });
    }

    if (parsedItem.mat) {
      const material = parsedItem.mat.split(" (")[0];
      itemData.system.material.type = Seasoning.slugD(material);
      itemData.system.material.grade = Seasoning.getMaterialGrade(parsedItem.mat);
    }
  }

  async #createWeaponItem(data) {
    // { pbName, name, prof, qty, die, display, increasedDice, pot, str, mat, runes, attack, damageBonus, extraDamage, damageType }
    src_logger.debug("Generating weapon for", data);
    const indexMatch = this.compendiumMatchers["equipment"].getMatch(data.pbName, data.foundryName);
    if (!indexMatch) {
      src_logger.error(`Unable to match weapon item ${data.name}`, data);
      this.bad.push({ pbName: data.pbName, type: "weapon", details: { w: data } });
      return null;
    }

    const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
    const itemData = doc.toObject();
    itemData._id = foundry.utils.randomID();
    itemData.system.quantity = data.qty;
    // because some shields don't have damage dice, but come in as weapons on pathbuilder
    if (itemData.type === "weapon") {
      if (data.die) itemData.system.damage.die = data.die;
      Pathmuncher.applyRunes(data, itemData, "weapon");
    }

    if (data.display.startsWith("Large ") || data.increasedDice) {
      itemData.system.size = "lg";
    } else if (data.display && !Seasoning.IGNORED_EQUIPMENT_DISPLAY(data.display)) {
      itemData.name = data.display;
    }

    this.#resizeItem(itemData);
    this.result.weapons.push(itemData);
    data.added = true;
    data.addedId = itemData._id;
    return itemData;
  }

  async #generateWeaponItems() {
    for (const w of this.parsed.weapons) {
      if (Seasoning.IGNORED_EQUIPMENT().includes(w.pbName)) {
        w.added = true;
        w.addedAutoId = "ignored";
        continue;
      }
      await this.#createWeaponItem(w);
    }
  }

  #adjustArmorItem(itemData, parsedArmor) {
    itemData._id = foundry.utils.randomID();
    itemData.system.equipped.value = parsedArmor.worn ?? false;
    if (!Seasoning.RESTRICTED_EQUIPMENT().some((i) => itemData.name.startsWith(i))) {
      itemData.system.equipped.inSlot = parsedArmor.worn ?? false;
      itemData.system.quantity = parsedArmor.qty;

      const isShield = parsedArmor.prof === "shield";
      itemData.system.equipped.handsHeld = isShield && parsedArmor.worn ? 1 : 0;
      itemData.system.equipped.carryType = isShield && parsedArmor.worn ? "held" : "worn";

      Pathmuncher.applyRunes(parsedArmor, itemData, "armor");
    }
    if (parsedArmor.display) itemData.name = parsedArmor.display;

    this.#resizeItem(itemData);
    return itemData;
  }

  async #generateArmorItems() {
    for (const a of this.parsed.armor) {
      if (Seasoning.IGNORED_EQUIPMENT().includes(a.pbName)) {
        a.added = true;
        a.addedAutoId = "ignored";
        continue;
      }

      src_logger.debug("Generating armor for", a);
      if (Seasoning.GRANTED_ITEMS_LIST().includes(a.pbName)) {
        const existingItem = this.result.armor.find((i) => i.name === a.foundryName);
        if (existingItem) {
          a.added = true;
          a.addedId = existingItem._id;
          src_logger.debug(`Ignoring armor item ${a.pbName} as it has been granted by a feature`);
          continue;
        }
      }
      const indexMatch = this.compendiumMatchers["equipment"].getMatch(a.foundryName, `${a.pbName} Armor`);
      if (!indexMatch) {
        src_logger.error(`Unable to match armor kit item ${a.name}`, a);
        this.bad.push({ pbName: a.pbName, type: "armor", details: { a } });
        continue;
      }

      const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
      const itemData = this.#adjustArmorItem(doc.toObject(), a);
      this.result.armor.push(itemData);
      a.addedId = itemData._id;
      a.added = true;
    }
  }

  getClassSpellCastingType(dual = false) {
    const classCaster = dual
      ? this.source.spellCasters.find((caster) => caster.name === this.source.dualClass)
      : this.source.spellCasters.find((caster) => caster.name === this.source.class);
    const type = classCaster?.spellcastingType;
    if (type || this.source.spellCasters.length === 0) return type ?? "spontaneous";
    // if no type and multiple spell casters, then return the first spell casting type
    return this.source.spellCasters[0].spellcastingType ?? "spontaneous";
  }

  // aims to determine the class magic tradition for a spellcasting block
  getClassMagicTradition(caster) {
    const classCaster = [this.source.class, this.source.dualClass].includes(caster.name);
    const tradition = classCaster ? caster?.magicTradition : undefined;
    // if a caster tradition or no spellcasters, return divine
    if (tradition || this.source.spellCasters.length === 0) return tradition ?? "divine";

    // not a focus traditions
    if (caster.magicTradition !== "focus" && ["divine", "occult", "primal", "arcane"].includes(caster.magicTradition)) {
      return caster.magicTradition;
    }

    // this spell caster type is not a class, determine class tradition based on ability
    const abilityTradition = this.source.spellCasters.find((c) =>
      [this.source.class, this.source.dualClass].includes(c.name)
      && c.ability === caster.ability
    );
    if (abilityTradition) return abilityTradition.magicTradition;
    // if no type and multiple spell casters, then return the first spell casting type
    return this.source.spellCasters[0].magicTradition && this.source.spellCasters[0].magicTradition !== "focus"
      ? this.source.spellCasters[0].magicTradition
      : "divine";
  }

  #applySpellBlending(spellcastingEntity, caster) {
    if (caster.blendedSpells.length === 0) return;

    const remove = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    const add = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

    // find adjustments
    caster.blendedSpells.forEach((slot) => {
      remove[slot.levelFrom]++;
      add[slot.LevelTo]++;
    });

    for (let i = 0; i <= 10; i++) {
      const toAdd = this.options.adjustBlendedSlots ? 0 : Math.floor(add[i] / 2);
      const toRemove = this.options.adjustBlendedSlots ? remove[i] : 0;
      const adjustment = 0 - toRemove - toAdd;
      src_logger.debug("Adjusting spells for spell blending", { i, adjustment, add, remove, toAdd, max: spellcastingEntity.slots[`slot${i}`].max });
      spellcastingEntity.slots[`slot${i}`].max += adjustment;
      spellcastingEntity.slots[`slot${i}`].value += adjustment;
    }
  }

  #generateSpellCaster(caster) {
    const isFocus = caster.magicTradition === "focus";
    const magicTradition = this.getClassMagicTradition(caster);
    const spellcastingType = isFocus ? "focus" : caster.spellcastingType;
    const flexible = false; // placeholder

    const name = isFocus ? `${src_utils.capitalize(magicTradition)} ${caster.name}` : caster.name;

    const spellcastingEntity = {
      ability: {
        value: caster.ability,
      },
      proficiency: {
        value: caster.proficiency / 2,
      },
      spelldc: {
        item: 0,
      },
      tradition: {
        value: magicTradition,
      },
      prepared: {
        value: spellcastingType,
        flexible,
      },
      slots: {},
      showUnpreparedSpells: { value: true },
      showSlotlessLevels: { value: true },
    };

    // apply slot data
    for (let i = 0; i <= 10; i++) {

      spellcastingEntity.slots[`slot${i}`] = {
        max: caster.perDay[i],
        prepared: {},
        value: caster.perDay[i],
      };
    }
    // adjust slots for spell blended effects
    this.#applySpellBlending(spellcastingEntity, caster);

    const data = {
      _id: foundry.utils.randomID(),
      name,
      type: "spellcastingEntry",
      system: spellcastingEntity,
    };
    this.result.casters.push(data);
    return data;
  }

  #generateFocusSpellCaster(proficiency, ability, tradition) {
    const data = {
      _id: foundry.utils.randomID(),
      name: `${src_utils.capitalize(tradition)} Focus Tradition`,
      type: "spellcastingEntry",
      system: {
        ability: {
          value: ability,
        },
        proficiency: {
          value: proficiency / 2,
        },
        spelldc: {
          item: 0,
        },
        tradition: {
          value: tradition,
        },
        prepared: {
          value: "focus",
          flexible: false,
        },
        showUnpreparedSpells: { value: true },
      },
    };
    this.result.casters.push(data);
    return data;
  }

  async #loadSpell(spell, casterId, debugData) {
    const spellName = spellRename(spell.split("(")[0].trim());
    src_logger.debug("focus spell details", { spell, spellName, debugData });

    const indexMatch = this.compendiumMatchers["spells"].getMatch(spell, spellName, true);
    if (!indexMatch) {
      if (debugData.psychicAmpSpell) return undefined;
      src_logger.error(`Unable to match focus spell ${spell}`, { spell, spellName, debugData });
      this.bad.push({ pbName: spell, type: "spell", details: { originalName: spell, name: spellName, debugData } });
      return undefined;
    }

    const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
    const itemData = doc.toObject();
    itemData._id = foundry.utils.randomID();
    itemData.system.location.value = casterId;

    return itemData;
  }

  // eslint-disable-next-line complexity
  async #processCasterSpells(instance, caster, spellEnhancements, forcePrepare = false) {
    const spellNames = {};
    for (const spellSelection of caster.spells) {
      const level = spellSelection.spellLevel;
      const preparedAtLevel = caster.prepared?.length > 0
        ? (caster.prepared.find((p) => p.spellLevel === level)?.list ?? [])
        : [];
      let preparedValue = 0;

      // const preparedMap = preparedAtLevel.reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map());

      for (const [i, spell] of spellSelection.list.entries()) {
        src_logger.debug(`Checking spell at ${i} for level ${level}`, { spell });
        const itemData = await this.#loadSpell(spell, instance._id, {
          spellSelection,
          list: spellSelection.list,
          level,
          instance,
        });
        if (itemData) {
          itemData.system.location.heightenedLevel = level;
          spellNames[spell] = itemData._id;
          this.result.spells.push(itemData);

          // if the caster is prepared we don't prepare spells as all known spells come through in JSON
          if (instance.system.prepared.value !== "prepared"
            || spellEnhancements?.preparePBSpells
            || forcePrepare
            || (caster.spellcastingType === "prepared"
              && preparedAtLevel.length === 0 && spellSelection.list.length <= caster.perDay[level])
          ) {
            src_logger.debug(`Preparing spell ${itemData.name} for level ${level}`, { spell });
            // eslint-disable-next-line require-atomic-updates
            instance.system.slots[`slot${level}`].prepared[preparedValue] = { id: itemData._id };
            preparedValue++;
          }
        }
      }

      for (const spell of preparedAtLevel) {
        // if (spellNames.includes(spellName)) continue;
        const parsedSpell = getProperty(spellNames, spell);
        const itemData = parsedSpell
          ? this.result.spells.find((s) => s._id === parsedSpell)
          : await this.#loadSpell(spell, instance._id, {
            spellSelection,
            level,
            instance,
          });
        if (itemData) {
          itemData.system.location.heightenedLevel = level;
          if (itemData && !parsedSpell) {
            spellNames[spell] = itemData._id;
            this.result.spells.push(itemData);
          }

          src_logger.debug(`Preparing spell ${itemData.name} for level ${level}`, { spellName: spell });
          // eslint-disable-next-line require-atomic-updates
          instance.system.slots[`slot${level}`].prepared[preparedValue] = { id: itemData._id };
          preparedValue++;
        } else {
          src_logger.warn(`Unable to find spell ${spell}`);
        }
      }

      if (spellEnhancements?.knownSpells) {
        for (const spell of spellEnhancements.knownSpells) {
          const itemData = await this.#loadSpell(spell, instance._id, {
            spellEnhancements,
            instance,
          });
          if (itemData && !hasProperty(spellNames, itemData.name)) {
            itemData.system.location.heightenedLevel = level;
            spellNames[spell] = itemData._id;
            this.result.spells.push(itemData);
          }
        }
      }
    }
  }

  async #processFocusSpells(instance, spells) {
    for (const spell of spells) {
      const itemData = await this.#loadSpell(spell, instance._id, {
        instance,
        spells,
        spell,
      });
      if (itemData) this.result.spells.push(itemData);
      if (spell.endsWith("(Amped)")) {
        const psychicSpell = spell.replace("(Amped)", "(Psychic)");
        const psychicItemData = await this.#loadSpell(psychicSpell, instance._id, {
          instance,
          spells,
          spell: psychicSpell,
          psychicAmpSpell: true,
        });
        if (psychicItemData) {
          this.result.spells.push(psychicItemData);
        }
      }
    }
  }

  async #processRituals() {
    if (!this.source.rituals) return;
    const ritualCompendium = new CompendiumMatcher({
      type: "spells",
      indexFields: ["name", "type", "system.slug", "system.ritual"],
    });
    await ritualCompendium.loadCompendiums();

    for (const ritual of this.source.rituals) {
      const ritualName = ritual.split("(")[0].trim();
      src_logger.debug("focus spell details", { ritual, spellName: ritualName });

      const indexMatch = this.compendiumMatchers["spells"].getNameMatchWithFilter(ritualName, ritualName);
      if (!indexMatch || !hasProperty(indexMatch, "system.ritual")) {
        src_logger.error(`Unable to match ritual spell ${ritual}`, { spell: ritual, spellName: ritualName });
        this.bad.push({ pbName: ritual, type: "spell", details: { originalName: ritual, name: ritualName } });
        continue;
      }

      const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
      const itemData = doc.toObject();
      itemData._id = foundry.utils.randomID();

      this.result.spells.push(itemData);
    }
  }

  async #processSpells() {
    for (const caster of this.source.spellCasters) {
      src_logger.debug("Generating caster for", caster);
      if (Number.isInteger(parseInt(caster.focusPoints))) this.result.focusPool += caster.focusPoints;
      const instance = this.#generateSpellCaster(caster);
      src_logger.debug("Generated caster instance", instance);
      const spellEnhancements = Seasoning.getSpellCastingFeatureAdjustment(caster.name);
      let forcePrepare = false;
      if (hasProperty(spellEnhancements, "showSlotless")) {
        instance.system.showSlotlessLevels.value = getProperty(spellEnhancements, "showSlotless");
      } else if (
        caster.spellcastingType === "prepared"
        && ![this.source.class, this.source.dualClass].includes(caster.name)
      ) {
        const slotToPreparedMatch = caster.spells.every((spellBlock) => {
          const spellCount = spellBlock.list.length;
          const perDay = caster.perDay[spellBlock.spellLevel];
          return perDay === spellCount;
        });
        src_logger.debug(`Setting ${caster.name} show all slots to ${!slotToPreparedMatch}`);
        instance.system.showSlotlessLevels.value = !slotToPreparedMatch;
        forcePrepare = slotToPreparedMatch;
      }
      await this.#processCasterSpells(instance, caster, spellEnhancements, forcePrepare);
    }

    for (const tradition of ["occult", "primal", "divine", "arcane"]) {
      const traditionData = getProperty(this.source, `focus.${tradition}`);
      src_logger.debug(`Checking for focus tradition ${tradition}`);
      if (!traditionData) continue;
      for (const ability of ["str", "dex", "con", "int", "wis", "cha"]) {
        const abilityData = getProperty(traditionData, ability);
        src_logger.debug(`Checking for focus tradition ${tradition} with ability ${ability}`);
        if (!abilityData) continue;
        src_logger.debug("Generating focus spellcasting ", { tradition, traditionData, ability });
        const instance = this.#generateFocusSpellCaster(abilityData.proficiency, ability, tradition);
        if (abilityData.focusCantrips && abilityData.focusCantrips.length > 0) {
          await this.#processFocusSpells(instance, abilityData.focusCantrips);
        }
        if (abilityData.focusSpells && abilityData.focusSpells.length > 0) {
          await this.#processFocusSpells(instance, abilityData.focusSpells);
        }
      }
    }

    setProperty(this.result.character, "system.resources.focus.max", this.source.focusPoints);
    setProperty(this.result.character, "system.resources.focus.value", this.source.focusPoints);
  }

  async #generateLores() {
    for (const lore of this.source.lores) {
      const data = {
        name: lore[0],
        type: "lore",
        system: {
          proficient: {
            value: lore[1] / 2,
          },
          featType: "",
          mod: {
            value: 0,
          },
          item: {
            value: 0,
          },
        },
      };
      this.result.lores.push(data);
    }
  }

  async #generateMoney() {
    const compendium = game.packs.get("pf2e.equipment-srd");
    const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
    const moneyLookup = [
      { slug: "platinum-pieces", type: "pp" },
      { slug: "gold-pieces", type: "gp" },
      { slug: "silver-pieces", type: "sp" },
      { slug: "copper-pieces", type: "cp" },
    ];

    for (const lookup of moneyLookup) {
      const indexMatch = index.find((i) => i.system.slug === lookup.slug);
      if (indexMatch) {
        const doc = await compendium.getDocument(indexMatch._id);
        const itemData = doc.toObject();
        itemData._id = foundry.utils.randomID();
        itemData.system.quantity = this.source.money[lookup.type];
        this.result.money.push(itemData);
      }
    }
  }

  async #processFormulas() {
    const uuids = [];

    for (const formulaSource of this.source.formula) {
      for (const formulaName of formulaSource.known) {
        const indexMatch = this.compendiumMatchers["formulas"].getMatch(formulaName, formulaName);
        if (!indexMatch) {
          src_logger.error(`Unable to match formula ${formulaName}`, { formulaSource, name: formulaName });
          this.bad.push({ pbName: formulaName, type: "formula", details: { formulaSource, name: formulaName } });
          continue;
        }
        const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
        uuids.push({ uuid: doc.uuid });
      }
    }
    setProperty(this.result.character, "system.crafting.formulas", uuids);
  }

  async #processFeats() {
    this.#sortParsedFeats();
    // pre pass for standard items
    for (let i = 1; i <= this.result.character.system.details.level.value; i++) {
      await this.#generateFeatItems("feats", { typeFilter: "Ancestry Feat", levelCap: i, excludeChild: true, excludeParents: true });
      await this.#generateFeatItems("feats", { typeFilter: "Skill Feat", levelCap: i, excludeChild: true, excludeParents: true });
      await this.#generateFeatItems("feats", { typeFilter: "Class Feat", levelCap: i, excludeChild: true, excludeParents: true });
      await this.#generateFeatItems("feats", { typeFilter: "General Feat", levelCap: i, excludeChild: true, excludeParents: true });
    }
    await this.#generateFeatItems("ancestryFeatures", { excludeChild: true, excludeParents: true });
    // prepass for non-child items
    for (let i = 1; i <= this.result.character.system.details.level.value; i++) {
      await this.#generateFeatItems("feats", { typeFilter: "Ancestry Feat", levelCap: i, excludeChild: true });
      await this.#generateFeatItems("feats", { typeFilter: "Skill Feat", levelCap: i, excludeChild: true });
      await this.#generateFeatItems("feats", { typeFilter: "Class Feat", levelCap: i, excludeChild: true });
      await this.#generateFeatItems("feats", { typeFilter: "General Feat", levelCap: i, excludeChild: true });
      await this.#generateFeatItems("feats", { typeFilter: "Archetype Feat", levelCap: i, excludeChild: true });
    }
    await this.#generateFeatItems("ancestryFeatures", { excludeChild: true });

    this.#setSkills();
    // final pass, include all
    this.#statusUpdate(1, 5, "Feats");
    await this.#generateFeatItems("feats");
    this.#statusUpdate(2, 5, "Feats");
    await this.#generateFeatItems("ancestryFeatures");
    this.#statusUpdate(3, 5, "Feats");
    await this.#generateSpecialItems("ancestryFeatures");
    this.#statusUpdate(4, 5, "Feats");
    await this.#generateSpecialItems("classFeatures");
    this.#statusUpdate(5, 5, "Feats");
    await this.#generateSpecialItems("actions");
  }

  async #processEquipment() {
    this.#statusUpdate(1, 4, "Equipment");
    await this.#processEquipmentItems();
    this.#statusUpdate(2, 4, "Weapons");
    await this.#generateWeaponItems();
    this.#statusUpdate(3, 4, "Armor");
    await this.#generateArmorItems();
    this.#statusUpdate(2, 4, "Money");
    await this.#generateMoney();
  }

  async #generateTempActor(documents = [], includePassedDocumentsRules = false, includeGrants = false, includeFlagsOnly = false) {
    const actorData = mergeObject({ type: "character" }, this.result.character);
    actorData.name = `Mr Temp (${this.result.character.name})`;
    if (documents.map((d) => d.name.split("(")[0].trim().toLowerCase()).includes("skill training")) {
      delete actorData.system.skills;
    }

    const actor = await Actor.create(actorData, { renderSheet: false });
    const currentState = duplicate(this.result);

    // console.warn("Initial temp actor", deepClone(actor));

    const currentItems = [
      ...currentState.deity,
      ...currentState.ancestry,
      ...currentState.heritage,
      ...currentState.background,
      ...currentState.class,
      ...currentState.lores,
      ...currentState.feats,
      ...currentState.casters,
      // ...currentState.spells,
      // ...currentState.equipment,
      // ...currentState.weapons,
      // ...currentState.armor,
      // ...currentState.treasure,
      // ...currentState.money,
    ];
    for (const doc of documents) {
      if (!currentItems.some((d) => d._id === doc._id)) {
        currentItems.push(deepClone(doc));
      }
    }
    try {
      // if the rule selected is an object, id doesn't take on import
      const ruleUpdates = [];
      for (const i of deepClone(currentItems)) {
        if (!i.system.rules || i.system.rules.length === 0) continue;
        const isPassedDocument = documents.some((d) => d._id === i._id);
        if (isPassedDocument && !includePassedDocumentsRules && !includeFlagsOnly) continue;

        const objectSelectionRules = i.system.rules
          .filter((r) => {
            const evaluateRules = ["RollOption", "ChoiceSet"].includes(r.key) && r.selection;
            return !includeFlagsOnly || evaluateRules; // && ["RollOption", "GrantItem", "ChoiceSet", "ActiveEffectLike"].includes(r.key);
            // || (["ChoiceSet"].includes(r.key) && r.selection);
          })
          .map((r) => {
            r.ignored = false;
            return r;
          });

        if (objectSelectionRules.length > 0) {
          ruleUpdates.push({
            _id: i._id,
            system: {
              rules: objectSelectionRules,
            },
          });
        }
      }

      // console.warn("Rule updates", duplicate(ruleUpdates));

      const items = duplicate(currentItems).map((i) => {
        if (i.system.items) i.system.items = [];
        if (i.system.rules) {
          i.system.rules = i.system.rules
            .filter((r) => {
              const isPassedDocument = documents.some((d) => d._id === i._id);
              const isChoiceSetSelection = ["ChoiceSet"].includes(r.key) && r.selection;
              // const choiceSetSelectionObject = isChoiceSetSelection && utils.isObject(r.selection);
              const choiceSetSelectionNotObject = isChoiceSetSelection && !src_utils.isObject(r.selection);
              // const grantRuleWithFlag = includeGrants && ["GrantItem"].includes(r.key) && r.flag;
              const grantRuleWithoutFlag = includeGrants && ["GrantItem"].includes(r.key) && !r.flag;
              // const genericDiscardRule = ["ChoiceSet", "GrantItem", "ActiveEffectLike", "Resistance", "Strike", "AdjustModifier"].includes(r.key);
              const genericDiscardRule = ["ChoiceSet", "GrantItem"].includes(r.key);
              const grantRuleFromItemFlag
                = includeGrants && ["GrantItem"].includes(r.key) && r.uuid.startsWith("{item|flags");
              const rollOptionsRule = ["RollOption"].includes(r.key);

              const notPassedDocumentRules
                = !isPassedDocument
                && (choiceSetSelectionNotObject
                  // || grantRuleWithFlag
                  || grantRuleWithoutFlag
                  || !genericDiscardRule);

              const passedDocumentRules
                = isPassedDocument
                && includePassedDocumentsRules
                && (isChoiceSetSelection || grantRuleWithoutFlag || grantRuleFromItemFlag || rollOptionsRule);

              return notPassedDocumentRules || passedDocumentRules;
            })
            .map((r) => {
              // if choices is a string or an object then we replace with the query string results
              if ((src_utils.isString(r.choices) || src_utils.isObject(r.choices)) && r.choiceQueryResults) {
                r.choices = r.choiceQueryResults;
              }
              r.ignored = false;
              return r;
            });
        }
        return i;
      });

      // const items2 = duplicate(currentItems).map((i) => {
      //   if (i.system.items) i.system.items = [];
      //   if (i.system.rules) i.system.rules = i.system.rules.filter((r) =>
      //     (!documents.some((d) => d._id === i._id)
      //     && ((["ChoiceSet",].includes(r.key) && r.selection)
      //     //  || (["GrantItem"].includes(r.key) && r.flag)
      //       || !["ChoiceSet", "GrantItem"].includes(r.key)
      //     ))
      //     || (includePassedDocumentsRules && documents.some((d) => d._id === i._id) && ["ChoiceSet",].includes(r.key) && r.selection)
      //   ).map((r) => {
      //     if ((typeof r.choices === 'string' || r.choices instanceof String)
      //       || (typeof r.choices === 'object' && !Array.isArray(r.choices) && r.choices !== null && r.choiceQueryResults)
      //     ) {
      //       r.choices = r.choiceQueryResults;
      //     }
      //     r.ignored = false;
      //     return r;
      //   });
      //   return i;
      // });

      // console.warn("temp items", {
      //   documents: deepClone(currentItems),
      //   items: deepClone(items),
      //   // items2: deepClone(items2),
      //   // diff: diffObject(items, items2),
      //   includePassedDocumentsRules,
      //   includeGrants,
      // });
      await actor.createEmbeddedDocuments("Item", items, { keepId: true });
      // console.warn("restoring selection rules to temp items", ruleUpdates);
      await actor.updateEmbeddedDocuments("Item", ruleUpdates);

      const itemUpdates = [];
      for (const [key, value] of Object.entries(this.autoAddedFeatureItems)) {
        itemUpdates.push({
          _id: `${key}`,
          system: {
            items: deepClone(value),
          },
        });
      }

      // console.warn("restoring feature items to temp items", itemUpdates);
      await actor.updateEmbeddedDocuments("Item", itemUpdates);

      src_logger.debug("Final temp actor", actor);
    } catch (err) {
      src_logger.error("Temp actor creation failed", {
        actor,
        documents,
        thisData: deepClone(this.result),
        actorData,
        err,
        currentItems,
        this: this,
      });
    }
    return actor;
  }

  async processCharacter() {
    if (!this.source) return;
    await this.#prepare();
    this.#statusUpdate(1, 12, "Character");
    await this.#processCore();
    this.#statusUpdate(2, 12, "Formula");
    await this.#processFormulas();
    this.#statusUpdate(3, 12, "Deity");
    await this.#processGenericCompendiumLookup("deities", this.source.deity, "deity");

    this.#statusUpdate(4, 12, "Ancestry");
    await this.#processGenericCompendiumLookup("ancestries", this.source.ancestry, "ancestry");
    this.#statusUpdate(5, 12, "Heritage");
    await this.#processGenericCompendiumLookup("heritages", this.source.heritage, "heritage");
    this.#statusUpdate(6, 12, "Background");
    await this.#processGenericCompendiumLookup("backgrounds", this.source.background, "background");

    this.#setSkills(true);

    this.#statusUpdate(7, 12, "Class");
    await this.#processGenericCompendiumLookup("classes", this.source.class, "class");

    this.#setAbilityBoosts();

    this.#statusUpdate(8, 12, "FeatureRec");
    await this.#processFeats();
    this.#statusUpdate(10, 12, "Equipment");
    await this.#processEquipment();
    this.#statusUpdate(11, 12, "Spells");
    await this.#processSpells();
    this.#statusUpdate(11, 12, "Rituals");
    await this.#processRituals();
    this.#statusUpdate(12, 12, "Lores");
    await this.#generateLores();
  }

  async #removeDocumentsToBeUpdated() {
    const moneyIds = this.actor.items.filter((i) =>
      i.type === "treasure"
      && ["Platinum Pieces", "Gold Pieces", "Silver Pieces", "Copper Pieces"].includes(i.name)
    );
    const classIds = this.actor.items.filter((i) => i.type === "class").map((i) => i._id);
    const deityIds = this.actor.items.filter((i) => i.type === "deity").map((i) => i._id);
    const backgroundIds = this.actor.items.filter((i) => i.type === "background").map((i) => i._id);
    const heritageIds = this.actor.items.filter((i) => i.type === "heritage").map((i) => i._id);
    const ancestryIds = this.actor.items.filter((i) => i.type === "ancestry").map((i) => i._id);
    const treasureIds = this.actor.items
      .filter((i) => i.type === "treasure" && !moneyIds.includes(i.id))
      .map((i) => i._id);
    const featIds = this.actor.items.filter((i) => i.type === "feat").map((i) => i._id);
    const actionIds = this.actor.items.filter((i) => i.type === "action").map((i) => i._id);
    const equipmentIds = this.actor.items
      .filter((i) => i.type === "equipment" || i.type === "backpack" || i.type === "consumable")
      .map((i) => i._id);
    const weaponIds = this.actor.items.filter((i) => i.type === "weapon").map((i) => i._id);
    const armorIds = this.actor.items.filter((i) => i.type === "armor").map((i) => i._id);
    const loreIds = this.actor.items.filter((i) => i.type === "lore").map((i) => i._id);
    const spellIds = this.actor.items
      .filter((i) => i.type === "spell" || i.type === "spellcastingEntry")
      .map((i) => i._id);
    const formulaIds = this.actor.system.formulas;

    src_logger.debug("ids", {
      moneyIds,
      deityIds,
      classIds,
      backgroundIds,
      heritageIds,
      ancestryIds,
      treasureIds,
      featIds,
      actionIds,
      equipmentIds,
      weaponIds,
      armorIds,
      loreIds,
      spellIds,
      formulaIds,
    });
    // eslint-disable-next-line complexity
    const keepIds = this.actor.items.filter((i) =>
      (!this.options.addMoney && moneyIds.includes(i._id))
      || (!this.options.addClass && classIds.includes(i._id))
      || (!this.options.addDeity && deityIds.includes(i._id))
      || (!this.options.addBackground && backgroundIds.includes(i._id))
      || (!this.options.addHeritage && heritageIds.includes(i._id))
      || (!this.options.addAncestry && ancestryIds.includes(i._id))
      || (!this.options.addTreasure && treasureIds.includes(i._id))
      || (!this.options.addFeats && (featIds.includes(i._id) || actionIds.includes(i._id)))
      || (!this.options.addEquipment && equipmentIds.includes(i._id))
      || (!this.options.addWeapons && weaponIds.includes(i._id))
      || (!this.options.addArmor && armorIds.includes(i._id))
      || (!this.options.addLores && loreIds.includes(i._id))
      || (!this.options.addSpells && spellIds.includes(i._id))
    ).map((i) => i._id);

    const deleteIds = this.actor.items.filter((i) => !keepIds.includes(i._id)).map((i) => i._id);
    src_logger.debug("ids", {
      deleteIds,
      keepIds,
    });
    await this.actor.deleteEmbeddedDocuments("Item", deleteIds);
  }

  async #createAndUpdateItemsWithRuleRestore(items) {
    const ruleUpdates = [];

    const newItems = deepClone(items);

    for (const item of newItems) {
      if (item.system.rules?.length > 0) {
        ruleUpdates.push({
          _id: item._id,
          system: {
            rules: deepClone(item.system.rules).map((r) => {
              delete r.choiceQueryResults;
              return r;
            }),
          },
        });
        item.system.rules = item.system.rules
          .filter((r) => {
            const excludedKeys = ["ActiveEffectLike", "AdjustModifier", "Resistance", "Strike"].includes(r.key);
            const grantItemWithFlags = ["GrantItem"].includes(r.key) && (hasProperty(r, "flag") || getProperty(r, "pathmuncherImport"));
            const objectSelection = ["ChoiceSet"].includes(r.key) && src_utils.isObject(r.selection);
            return !excludedKeys && !grantItemWithFlags && !objectSelection;
          })
          .map((r) => {
            if (r.key === "ChoiceSet") {
              if ((src_utils.isString(r.choices) || src_utils.isObject(r.choices)) && r.choiceQueryResults) {
                r.choices = r.choiceQueryResults;
              }
            }
            if (r.pathmuncherImport) delete r.pathmuncherImport;
            return r;
          });
      }
    }

    src_logger.debug("Creating items", newItems);
    await this.actor.createEmbeddedDocuments("Item", newItems, { keepId: true });
    src_logger.debug("Rule updates", ruleUpdates);
    await this.actor.updateEmbeddedDocuments("Item", ruleUpdates);
  }

  async #updateItems(type) {
    src_logger.debug(`Updating ${type}`, this.result[type]);
    await this.actor.updateEmbeddedDocuments("Item", this.result[type]);
  }

  async #createActorEmbeddedDocuments() {
    this.#statusUpdate(1, 12, "Character", "Eating");
    if (this.options.addDeity) await this.#createAndUpdateItemsWithRuleRestore(this.result.deity);
    if (this.options.addAncestry) await this.#createAndUpdateItemsWithRuleRestore(this.result.ancestry);
    if (this.options.addHeritage) await this.#createAndUpdateItemsWithRuleRestore(this.result.heritage);
    if (this.options.addBackground) await this.#createAndUpdateItemsWithRuleRestore(this.result.background);
    if (this.options.addClass) await this.#createAndUpdateItemsWithRuleRestore(this.result.class);
    if (this.options.addLores) await this.#createAndUpdateItemsWithRuleRestore(this.result.lores);

    const featNums = this.result.feats.length;
    if (this.options.addFeats) {
      for (const [i, feat] of this.result.feats.entries()) {
        // console.warn(`creating ${feat.name}`, feat);
        this.#statusUpdate(i, featNums, "Feats", "Eating");
        await this.#createAndUpdateItemsWithRuleRestore([feat]);
      }
    }
    // if (this.options.addFeats) await this.#createAndUpdateItemsWithRuleRestore(this.result.feats);
    if (this.options.addSpells) {
      this.#statusUpdate(3, 12, "Spells", "Eating");
      await this.#createAndUpdateItemsWithRuleRestore(this.result.casters);
      await this.#createAndUpdateItemsWithRuleRestore(this.result.spells);
    }
    this.#statusUpdate(4, 12, "Equipment", "Eating");
    if (this.options.addEquipment) {
      await this.#createAndUpdateItemsWithRuleRestore(this.result.equipment);
      await this.#updateItems("equipment");
    }
    if (this.options.addWeapons) await this.#createAndUpdateItemsWithRuleRestore(this.result.weapons);
    if (this.options.addArmor) {
      await this.#createAndUpdateItemsWithRuleRestore(this.result.armor);
      await this.#updateItems("armor");
    }
    if (this.options.addTreasure) await this.#createAndUpdateItemsWithRuleRestore(this.result.treasure);
    if (this.options.addMoney) await this.#createAndUpdateItemsWithRuleRestore(this.result.money);
  }

  async #restoreEmbeddedRuleLogic() {
    const importedItems = this.actor.items.map((i) => i._id);
    // Loop back over items and add rule and item progression data back in.
    src_logger.debug("Restoring logic", { currentActor: duplicate(this.actor) });
    const itemUpdates = [];
    for (const [key, value] of Object.entries(this.autoAddedFeatureItems)) {
      if (importedItems.includes(key)) {
        itemUpdates.push({
          _id: `${key}`,
          system: {
            items: deepClone(value),
          },
        });
      }
    }
    this.#statusUpdate(1, 12, "Feats", "Clearing");
    src_logger.debug("Restoring granted item logic", itemUpdates);
    await this.actor.updateEmbeddedDocuments("Item", itemUpdates);

    await this.actor.update({
      "system.resources.focus": this.result.character.system.resources.focus,
    });
  }

  async updateActor() {
    await this.#removeDocumentsToBeUpdated();

    if (!this.options.addName) {
      delete this.result.character.name;
      delete this.result.character.prototypeToken.name;
    }
    if (!this.options.addFormulas) {
      delete this.result.character.system.formulas;
    }

    if (!this.boosts.custom) {
      setProperty(this.result.character, `system.abilities`, null);
    }

    src_logger.debug("Generated result", this.result);
    await this.actor.update(this.result.character);
    await this.#createActorEmbeddedDocuments();
    await this.#restoreEmbeddedRuleLogic();
  }

  async postImportCheck() {
    const badClass = this.options.addClass
      ? this.bad.filter((b) => b.type === "class").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Class")}: ${b.pbName}</li>`)
      : [];
    const badHeritage = this.options.addHeritage
      ? this.bad.filter((b) => b.type === "heritage").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Heritage")}: ${b.pbName}</li>`)
      : [];
    const badAncestry = this.options.addAncestry
      ? this.bad.filter((b) => b.type === "ancestry").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Ancestry")}: ${b.pbName}</li>`)
      : [];
    const badBackground = this.options.addBackground
      ? this.bad.filter((b) => b.type === "background").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Background")}: ${b.pbName}</li>`)
      : [];
    const badDeity = this.options.addDeity
      ? this.bad.filter((b) => b.type === "deity" && b.pbName !== "Not set" && b.pbName !== "").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Deity")}: ${b.pbName}</li>`)
      : [];
    const badFeats = this.options.addFeats
      ? this.bad.filter((b) => b.type === "feat").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Feats")}: ${b.pbName}</li>`)
      : [];
    const badFeats2 = this.options.addFeats
      ? Object.values(this.check).filter((b) =>
        b.type === "feat"
        && this.parsed.feats.some((f) => f.name === b.details.name && !f.added)
      ).map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Feats")}: ${b.details.name}</li>`)
      : [];
    const badSpecials = this.options.addFeats
      ? Object.values(this.check).filter((b) =>
        (b.type === "special")
        && this.parsed.specials.some((f) => f.name === b.details.name && !f.added)
      ).map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Specials")}: ${b.details.originalName}</li>`)
      : [];
    const badEquipment = this.options.addEquipment
      ? this.bad.filter((b) => b.type === "equipment").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Equipment")}: ${b.pbName}</li>`)
      : [];
    const badWeapons = this.options.addWeapons
      ? this.bad.filter((b) => b.type === "weapons").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Weapons")}: ${b.pbName}</li>`)
      : [];
    const badArmor = this.options.addArmor
      ? this.bad.filter((b) => b.type === "armor").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Armor")}: ${b.pbName}</li>`)
      : [];
    const badSpellcasting = this.options.addSpells
      ? this.bad.filter((b) => b.type === "spellcasting").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Spellcasting")}: ${b.pbName}</li>`)
      : [];
    const badSpells = this.options.addSpells
      ? this.bad.filter((b) => b.type === "spells").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Spells")}: ${b.pbName}</li>`)
      : [];
    const badFamiliars = this.options.addFamiliars
      ? this.bad.filter((b) => b.type === "familiars").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Familiars")}: ${b.pbName}</li>`)
      : [];
    const badFormulas = this.options.addFormulas
      ? this.bad.filter((b) => b.type === "formulas").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Formulas")}: ${b.pbName}</li>`)
      : [];
    const totalBad = [
      ...badClass,
      ...badAncestry,
      ...badHeritage,
      ...badBackground,
      ...badDeity,
      ...badFeats,
      ...badFeats2,
      ...badSpecials,
      ...badEquipment,
      ...badWeapons,
      ...badArmor,
      ...badSpellcasting,
      ...badSpells,
      ...badFamiliars,
      ...badFormulas,
    ];

    let warning = "";

    if (totalBad.length > 0) {
      warning += `<p>${game.i18n.localize("pathmuncher.Dialogs.Pathmuncher.MissingItemsOpen")}</p><ul>${totalBad.join("\n")}</ul><br>`;
    }

    src_logger.debug("Bad thing check", {
      badClass,
      badAncestry,
      badHeritage,
      badBackground,
      badDeity,
      badFeats,
      badFeats2,
      badSpecials,
      badEquipment,
      badWeapons,
      badArmor,
      badSpellcasting,
      badSpells,
      badFamiliars,
      badFormulas,
      totalBad,
      count: totalBad.length,
      focusPool: this.result.focusPool,
      warning,
    });

    if (totalBad.length > 0) {
      ui.notifications.warn(game.i18n.localize("pathmuncher.Dialogs.Pathmuncher.CompletedWithNotes"));
      new Dialog({
        title: game.i18n.localize("pathmuncher.Dialogs.Pathmuncher.ImportNotes"),
        content: warning,
        buttons: {
          yes: {
            icon: "<i class='fas fa-check'></i>",
            label: game.i18n.localize("pathmuncher.Labels.Finished"),
          },
        },
        default: "yes",
      }).render(true);
    } else {
      ui.notifications.info(game.i18n.localize("pathmuncher.Dialogs.Pathmuncher.CompletedSuccess"));
    }
  }
}

;// CONCATENATED MODULE: ./src/app/PetShop.js
/* eslint-disable no-await-in-loop */
/* eslint-disable no-continue */





/**
 * The PetShop class looks for familiars in a Pathmunch data set and creates/updates as appropriate.
 */
class PetShop {


  constructor ({ type = "familiar", parent, pathbuilderJson } = {}) {
    this.parent = parent;
    this.pathbuilderJson = pathbuilderJson;
    this.type = type;

    this.result = {
      pets: [],
      features: {},
    };

    this.bad = {};
    this.folders = {};
  }


  async ensureFolder(type) {
    const folderName = game.i18n.localize(`${constants.FLAG_NAME}.Folders.${type}`);
    this.folders[type] = await src_utils.getOrCreateFolder(this.parent.folder, "Actor", folderName);
  }

  async #existingPetCheck(petName, type) {
    const existingPet = game.actors.find((a) =>
      a.type === type.toLowerCase()
      && a.name === petName
      && a.system.master.id === this.parent._id
    );

    if (existingPet) return existingPet.toObject();

    const actorData = {
      type: type.toLowerCase(),
      name: petName,
      system: {
        master: {
          id: this.parent._id,
          ability: this.parent.system.details.keyability.value,
        },
      },
      prototypeToken: {
        name: petName,
      },
      folder: this.folders[type].id,
    };
    const actor = await Actor.create(actorData);
    return actor.toObject();

  }

  #buildCore(petData) {
    setProperty(petData, "system.attributes.value", this.parent.system.details.level.value * 5);
    return petData;
  }

  async #generatePetFeatures(pet, json) {
    const compendium = game.packs.get("pf2e.familiar-abilities");
    const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
    this.result.features[pet._id] = [];
    this.bad[pet._id] = [];

    for (const featureName of json.abilities) {
      const indexMatch = index.find((i) => i.system.slug === game.pf2e.system.sluggify(featureName));
      if (!indexMatch) {
        src_logger.warn(`Unable to match pet feature ${featureName}`, { pet, json, name: featureName });
        this.bad[pet._id].push({ pbName: featureName, type: "feature", details: { pet, json, name: featureName } });
        continue;
      }
      const doc = (await compendium.getDocument(indexMatch._id)).toObject();
      doc._id = foundry.utils.randomID();
      this.result.features[pet._id].push(doc);
    }
  }

  async buildPet(json) {
    const name = json.name === json.type || !json.name.includes("(")
      ? `${this.parent.name}'s ${json.type}`
      : json.name.split("(")[1].split(")")[0];
    const petData = await this.#existingPetCheck(name, json.type);
    const pet = this.#buildCore(petData);
    await this.#generatePetFeatures(pet, json);
    this.result.pets.push(pet);
  }

  async updatePets() {
    for (const petData of this.result.pets) {
      const actor = game.actors.get(petData._id);
      await actor.deleteEmbeddedDocuments("Item", [], { deleteAll: true });
      await actor.update(petData);
      await actor.createEmbeddedDocuments("Item", this.result.features[petData._id], { keepId: true });
    }
  }

  async processPets() {
    const petData = this.type === "familiar" && this.pathbuilderJson.familiars
      ? this.pathbuilderJson.familiars
      : this.pathbuilderJson.pets.filter((p) => this.type === p.type.toLowerCase());
    await this.ensureFolder(src_utils.capitalize(this.type));
    for (const petJson of petData) {
      await this.buildPet(petJson);
    }

    await this.updatePets();

    src_logger.debug("Pets", {
      results: this.results,
      bad: this.bad,
    });
  }

  async addPetEffects() {
    const features = [];
    for (const petData of this.result.pets) {
      for (const feature of this.result.features[petData._id].filter((f) => f.system.rules?.some((r) => r.key === "ActiveEffectLike"))) {
        if (!this.parent.items.some((i) => i.type === "effect" && i.system.slug === feature.system.slug)) {
          features.push(feature);
        }
      }
    }
    await this.parent.createEmbeddedDocuments("Item", features);
  }

}

;// CONCATENATED MODULE: ./src/app/PathmuncherImporter.js






class PathmuncherImporter extends FormApplication {

  constructor(options, actor) {
    super(options);
    this.actor = game.actors.get(actor.id ? actor.id : actor._id);
    this.backup = duplicate(this.actor);
    this.mode = "number";
  }

  static get defaultOptions() {
    const options = super.defaultOptions;
    options.title = game.i18n.localize(`${constants.FLAG_NAME}.Dialogs.PathmuncherImporter.Title`);
    options.template = `${constants.PATH}/templates/pathmuncher.hbs`;
    options.classes = ["pathmuncher"];
    options.id = "pathmuncher";
    options.width = 400;
    options.closeOnSubmit = false;
    options.tabs = [{ navSelector: ".tabs", contentSelector: "form", initial: "number" }];
    return options;
  }

  /** @override */
  async getData() {
    const flags = src_utils.getFlags(this.actor);

    return {
      flags,
      id: flags?.pathbuilderId ?? "",
      actor: this.actor,
    };
  }

  /** @override */
  activateListeners(html) {
    super.activateListeners(html);
    $("#pathmuncher").css("height", "auto");

    $(html)
      .find('.item')
      .on("click", (event) => {
        if (!event.target?.dataset?.tab) return;
        this.mode = event.target.dataset.tab;
      });
  }

  static _updateProgress(total, count, type, prefixLabel = "Cooking") {
    const localizedType = game.i18n.localize(`pathmuncher.Labels.${type}`);
    const progressBar = document.getElementById("pathmuncher-status");
    progressBar.style.width = `${Math.trunc((count / total) * 100)}%`;
    progressBar.innerHTML = `<span>${game.i18n.localize(`pathmuncher.Labels.${prefixLabel}`)} (${localizedType})...</span>`;
  }

  async _updateObject(event, formData) {
    document.getElementById("pathmuncher-button").disabled = true;
    const pathbuilderId = formData.textBoxBuildID;
    const options = {
      pathbuilderId,
      addMoney: formData.checkBoxMoney,
      addFeats: formData.checkBoxFeats,
      addSpells: formData.checkBoxSpells,
      adjustBlendedSlots: formData.checkBoxBlendedSlots,
      addEquipment: formData.checkBoxEquipment,
      addTreasure: formData.checkBoxTreasure,
      addLores: formData.checkBoxLores,
      addWeapons: formData.checkBoxWeapons,
      addArmor: formData.checkBoxArmor,
      addDeity: formData.checkBoxDeity,
      addName: formData.checkBoxName,
      addClass: formData.checkBoxClass,
      addBackground: formData.checkBoxBackground,
      addHeritage: formData.checkBoxHeritage,
      addAncestry: formData.checkBoxAncestry,
      addFamiliars: formData.checkBoxFamiliars,
      addFormulas: formData.checkBoxFormulas,
      statusCallback: PathmuncherImporter._updateProgress.bind(this),
    };
    src_logger.debug("Pathmuncher options", options);

    await src_utils.setFlags(this.actor, options);

    const statusBar = document.getElementById("pathmuncher-import-progress");
    statusBar.classList.toggle("import-hidden");

    const pathmuncher = new Pathmuncher(this.actor, options);
    if (this.mode === "number") {
      await pathmuncher.fetchPathbuilder(pathbuilderId);
    } else if (this.mode === "json") {
      try {
        const jsonData = JSON.parse(formData.textBoxBuildJSON.trim());
        pathmuncher.source = jsonData.build;
      } catch (err) {
        ui.notifications.error("Unable to parse JSON data");
        return;
      }
    }

    src_logger.debug("Pathmuncher Source", pathmuncher.source);
    await pathmuncher.processCharacter();
    src_logger.debug("Post processed character", pathmuncher);
    await pathmuncher.updateActor();
    src_logger.debug("Final import details", {
      actor: this.actor,
      pathmuncher,
      options,
      pathbuilderSource: pathmuncher.source,
      pathbuilderId,
    });

    if (options.addFamiliars) {
      const petShop = new PetShop({
        type: "familiar",
        parent: this.actor,
        pathbuilderJson: pathmuncher.source
      });
      await petShop.processPets();
      await petShop.addPetEffects();
    }
    this.close();
    await pathmuncher.postImportCheck();
  }

}

;// CONCATENATED MODULE: ./src/hooks/api.js











function registerAPI() {
  game.modules.get(constants.MODULE_NAME).api = {
    Pathmuncher: Pathmuncher,
    PathmuncherImporter: PathmuncherImporter,
    PetShop: PetShop,
    CompendiumMatcher: CompendiumMatcher,
    Seasoning: Seasoning,
    CompendiumSelector: CompendiumSelector,
    data: {
      generateFeatMap: FEAT_RENAME_MAP,
      equipment: EQUIPMENT_RENAME_MAP,
      restrictedEquipment: RESTRICTED_EQUIPMENT,
      feats: FEAT_RENAME_MAP(),
    },
    utils: src_utils,
    CONSTANTS: constants,
  };
}

;// CONCATENATED MODULE: ./src/hooks/settings.js



async function resetSettings() {
  for (const [name, data] of Object.entries(constants.GET_DEFAULT_SETTINGS())) {
    // eslint-disable-next-line no-await-in-loop
    await game.settings.set(constants.MODULE_NAME, name, data.default);
  }
  window.location.reload();
}

class ResetSettingsDialog extends FormApplication {
  constructor(...args) {
    super(...args);
    // eslint-disable-next-line no-constructor-return
    return new Dialog({
      title: game.i18n.localize(`${constants.FLAG_NAME}.Dialogs.ResetSettings.Title`),
      content: `<p class="${constants.FLAG_NAME}-dialog-important">${game.i18n.localize(
        `${constants.FLAG_NAME}.Dialogs.ResetSettings.Content`
      )}</p>`,
      buttons: {
        confirm: {
          icon: '<i class="fas fa-check"></i>',
          label: game.i18n.localize(`${constants.FLAG_NAME}.Dialogs.ResetSettings.Confirm`),
          callback: () => {
            resetSettings();
          },
        },
        cancel: {
          icon: '<i class="fas fa-times"></i>',
          label: game.i18n.localize(`${constants.FLAG_NAME}.Dialogs.ResetSettings.Cancel`),
        },
      },
      default: "cancel",
    });
  }
}

function registerSettings() {
  game.settings.registerMenu(constants.MODULE_NAME, "resetToDefaults", {
    name: `${constants.FLAG_NAME}.Settings.Reset.Title`,
    label: `${constants.FLAG_NAME}.Settings.Reset.Label`,
    hint: `${constants.FLAG_NAME}.Settings.Reset.Hint`,
    icon: "fas fa-refresh",
    type: ResetSettingsDialog,
    restricted: true,
  });

  for (const [name, data] of Object.entries(constants.GET_DEFAULT_SETTINGS())) {
    game.settings.register(constants.MODULE_NAME, name, data);
  }

  game.settings.registerMenu(constants.MODULE_NAME, "selectCustomCompendiums", {
    name: `${constants.FLAG_NAME}.Settings.UseCustomCompendiumMappings.Title`,
    label: `${constants.FLAG_NAME}.Settings.UseCustomCompendiumMappings.Label`,
    hint: `${constants.FLAG_NAME}.Settings.UseCustomCompendiumMappings.Hint`,
    icon: "fas fa-book",
    type: CompendiumSelector,
    restricted: true,
  });

}

;// CONCATENATED MODULE: ./src/hooks/sheets.js




function registerSheetButton() {

  const trustedUsersOnly = src_utils.setting("RESTRICT_TO_TRUSTED");
  if (trustedUsersOnly && !game.user.isTrusted) return;

  /**
   * Character sheets
   */
  const pcSheetNames = Object.values(CONFIG.Actor.sheetClasses.character)
    .map((sheetClass) => sheetClass.cls)
    .map((sheet) => sheet.name);

  pcSheetNames.forEach((sheetName) => {
    Hooks.on("render" + sheetName, (app, html, data) => {
      // only for GMs or the owner of this character
      if (!data.owner || !data.actor) return;

      const button = $(`<a class="pathmuncher-open" title="${constants.MODULE_FULL_NAME}"><i class="fas fa-hat-wizard"></i> ${constants.MODULE_FULL_NAME}</a>`);

      button.click(() => {
        if (game.user.can("ACTOR_CREATE")) {
          const muncher = new PathmuncherImporter(PathmuncherImporter.defaultOptions, data.actor);
          muncher.render(true);
        } else {
          ui.notifications.warn(game.i18n.localize(`${constants.FLAG_NAME}.Notifications.CreateActorPermission`), { permanent: true });
        }
      });

      html.closest('.app').find('.pathmuncher-open').remove();
      let titleElement = html.closest('.app').find('.window-title');
      if (!app._minimized) button.insertAfter(titleElement);
    });
  });

}

;// CONCATENATED MODULE: ./src/module.js




Hooks.once("init", () => {
  registerSettings();
});

Hooks.once("ready", () => {
  registerSheetButton();
  registerAPI();
});

/******/ })()
;
//# sourceMappingURL=main.js.map