/******/ (() => { // 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",
  },

  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.ADD_VISION_FEATS]: {
    name: `${CONSTANTS.FLAG_NAME}.Settings.AddVisionFeats.Name`,
    hint: `${CONSTANTS.FLAG_NAME}.Settings.AddVisionFeats.Hint`,
    scope: "player",
    config: true,
    type: Boolean,
    default: true,
  },

  // 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/data/equipment.js
const SWAPS = [
  /^(Greater) (.*)/,
  /^(Lesser) (.*)/,
  /^(Major) (.*)/,
  /^(Moderate) (.*)/,
  /^(Standard) (.*)/,
];

// this equipment is named differently in foundry vs pathbuilder
const EQUIPMENT_RENAME_STATIC_MAP = [
  { 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" },
];

function generateDynamicNames(pbName) {
  const result = [];
  // if we have a hardcoded map, don't return here
  for (const reg of SWAPS) {
    const match = pbName.match(reg);
    if (match) {
      result.push({ pbName, foundryName: `${match[2]} (${match[1]})`, details: match[2] });
    }
  }
  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"
];

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


const utils = {

  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, key);
  },

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

  getFlags: (actor) => {
    const flags = actor.flags[constants.FLAG_NAME]
      ? actor.flags[constants.FLAG_NAME]
      : {
        pathbuilderId: undefined,
        addFeats: true,
        addEquipment: true,
        addBackground: true,
        addHeritage: true,
        addAncestry: true,
        addSpells: true,
        addMoney: true,
        addTreasure: true,
        addLores: true,
        addWeapons: true,
        addArmor: true,
        addDeity: true,
        addName: true,
        addClass: true,
        addFamiliars: true,
        addFormulas: true,
        askForChoices: false,
      };
    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;
    }
  },

};


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

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




const POSTFIX_PB_REMOVALS = [
  /(.*) (Racket)$/,
  /(.*) (Style)$/,
];

const PREFIX_PB_REMOVALS = [
  /^(Arcane Thesis): (.*)/,
  /^(Arcane School): (.*)/,
  /^(The) (.*)/,
];

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

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

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

const FEAT_RENAME_STATIC_MAP = [
  { 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: "Cave Climber Kobold", foundryName: "Caveclimber Kobold" },
  { 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: "Constructed (Android)", foundryName: "Constructed" },
  { 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: "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: "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: "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: "Paladin [Lawful Good]", foundryName: "Paladin" },
  { pbName: "Parry", foundryName: "Aldori Parry" },
  { pbName: "Polymath", foundryName: "Polymath Muse" },
  { pbName: "Precise Debilitation", foundryName: "Precise Debilitations" },
  { pbName: "Quick Climber", foundryName: "Quick Climb" },
  { 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: "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" },
  { 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" },
];

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 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) {
      result.push({ pbName, foundryName: match[2], details: match[1] });
    }
  }
  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 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 IGNORED_FEATS_LIST = [
  "Unarmored",
  "Simple Weapon Expertise",
  "Spellbook",
  "Energy Emanation", // pathbuilder does not pass through a type for this
  "Imprecise Sense", // this gets picked up and added by granted features
];

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

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



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

    const setting = src_utils.setting(constants.SETTINGS.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/app/Pathmuncher.js
/* eslint-disable no-await-in-loop */
/* eslint-disable no-continue */






class Pathmuncher {

  // eslint-disable-next-line class-methods-use-this
  EQUIPMENT_RENAME_MAP(name) {
    return EQUIPMENT_RENAME_MAP(name);
  }

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

  FEAT_RENAME_MAP(name) {
    const dynamicItems = [
      { pbName: "Shining Oath", foundryName: `Shining Oath (${this.getChampionType()})` },
      { 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 FEAT_RENAME_MAP(name).concat(dynamicItems);
  }

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

  // eslint-disable-next-line class-methods-use-this
  get RESTRICTED_EQUIPMENT() {
    return RESTRICTED_EQUIPMENT;
  }

  // specials that are handled by Foundry and shouldn't be added
  // eslint-disable-next-line class-methods-use-this
  get IGNORED_FEATURES() {
    return IGNORED_FEATS();
  };

  // eslint-disable-next-line class-methods-use-this
  get IGNORED_EQUIPMENT() {
    return IGNORED_EQUIPMENT;
  };

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

  constructor(actor, { addFeats = true, addEquipment = true, addSpells = true, addMoney = true, addLores = true,
    addWeapons = true, addArmor = true, addTreasure = true, addDeity = true, addName = true, addClass = true,
    addBackground = true, addHeritage = true, addAncestry = true, askForChoices = false } = {}
  ) {
    this.actor = actor;
    // note not all these options do anything yet!
    this.options = {
      addTreasure,
      addMoney,
      addFeats,
      addSpells,
      addEquipment,
      addLores,
      addWeapons,
      addArmor,
      addDeity,
      addName,
      addClass,
      addBackground,
      addHeritage,
      addAncestry,
      askForChoices,
    };
    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.allFeatureRules = {};
    this.autoAddedFeatureRules = {};
    this.grantItemLookUp = {};
    this.autoFeats = [];
    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 = [];
  }

  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`));
    }
  }

  getClassAdjustedSpecialNameLowerCase(name) {
    return `${name} (${this.source.class})`.toLowerCase();
  }

  getDualClassAdjustedSpecialNameLowerCase(name) {
    return `${name} (${this.source.dualClass})`.toLowerCase();
  }

  getAncestryAdjustedSpecialNameLowerCase(name) {
    return `${name} (${this.source.ancestry})`.toLowerCase();
  }

  getHeritageAdjustedSpecialNameLowerCase(name) {
    return `${name} (${this.source.heritage})`.toLowerCase();
  }

  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 {
      return null;
    }
  }

  #generateFoundryFeatLocation(document, feature) {
    if (feature.type && feature.level) {
      const ancestryParagonVariant = game.settings.get("pf2e", "ancestryParagonVariant");
      const dualClassVariant = game.settings.get("pf2e", "dualClassVariant");
      // const freeArchetypeVariant = game.settings.get("pf2e", "freeArchetypeVariant");
      const location = Pathmuncher.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, location, feature });
        // eslint-disable-next-line max-depth
        if (ancestryParagonVariant && feature.type === "Ancestry Feat") {
          document.system.location = "ancestry-bonus";
          this.usedLocationsAlternateRules.add(location);
        } else if (dualClassVariant && 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;
    }
  }

  #nameMap() {
    src_logger.debug("Starting Equipment Rename");
    this.source.equipment
      .filter((e) => e[0] && e[0] !== "undefined")
      .forEach((e) => {
        const name = this.getFoundryEquipmentName(e[0]);
        const item = { pbName: name, qty: e[1], added: false };
        this.parsed.equipment.push(item);
      });
    this.source.armor
      .filter((e) => e && e !== "undefined")
      .forEach((e) => {
        const name = this.getFoundryEquipmentName(e.name);
        const item = mergeObject({ pbName: name, originalName: e.name, added: false }, e);
        this.parsed.armor.push(item);
      });
    this.source.weapons
      .filter((e) => e && e !== "undefined")
      .forEach((e) => {
        const name = this.getFoundryEquipmentName(e.name);
        const item = mergeObject({ pbName: name, originalName: e.name, added: false }, e);
        this.parsed.weapons.push(item);
      });
    src_logger.debug("Finished Equipment Rename");

    src_logger.debug("Starting Special Rename");
    this.source.specials
      .filter((special) => special
        && special !== "undefined"
        && special !== "Not Selected"
        && special !== this.source.heritage
      )
      .forEach((special) => {
        const name = this.getFoundryFeatureName(special).foundryName;
        if (!this.#processSpecialData(name) && !this.IGNORED_FEATURES.includes(name)) {
          this.parsed.specials.push({ name, originalName: special, added: false });
        }
      });
    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: false,
          type: feat[2],
          level: feat[3],
          originalName: feat[0],
        };
        this.parsed.feats.push(data);
      });
    src_logger.debug("Finished Feat Rename");
  }

  #prepare() {
    this.#nameMap();
  }

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

  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);
  }

  async #addDualClass(klass) {
    if (!game.settings.get("pf2e", "dualClassVariant")) {
      if (this.source.dualClass && this.source.dualClass !== "") {
        src_logger.warn(`Imported character is dual class but system is not configured for dual class`, {
          class: this.source.class,
          dualClass: this.source.dualClass,
        });
        ui.notifications.warn(`Imported character is dual class but system is not configured for dual class`);
      }
      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;
    }

    // find the dual class
    const compendium = await game.packs.get("pf2e.classes");
    const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
    const foundryName = this.getFoundryFeatureName(this.source.dualClass).foundryName;
    const indexMatch = index.find((i) => i.system.slug === game.pf2e.system.sluggify(foundryName))
      ?? index.find((i) => i.system.slug === game.pf2e.system.sluggify(this.source.dualClass));

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

    src_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(compendiumLabel, name, target) {
    src_logger.debug(`Checking for compendium documents for ${name} (${target}) in ${compendiumLabel}`);
    const compendium = await game.packs.get(compendiumLabel);
    const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
    const foundryName = this.getFoundryFeatureName(name).foundryName;
    const indexMatch = index.find((i) => i.system.slug === game.pf2e.system.sluggify(foundryName))
     ?? index.find((i) => i.system.slug === game.pf2e.system.sluggify(name));

    if (indexMatch) {
      const doc = await compendium.getDocument(indexMatch._id);
      const itemData = doc.toObject();
      if (target === "class") {
        itemData.system.keyAbility.selected = this.source.keyability;
        await this.#addDualClass(itemData);
      }
      itemData._id = foundry.utils.randomID();
      this.#generateGrantItemData(itemData);
      this.result[target].push(itemData);
      await this.#addGrantedItems(itemData);
      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"
  //     }

  #parsedFeatureMatch(type, slug, ignoreAdded) {
    // console.warn(`Trying to find ${slug} in ${type}, ignoreAdded? ${ignoreAdded}`);
    const parsedMatch = this.parsed[type].find((f) =>
      (!ignoreAdded || (ignoreAdded && !f.added))
      && (
        slug === game.pf2e.system.sluggify(f.name)
        || slug === game.pf2e.system.sluggify(this.getClassAdjustedSpecialNameLowerCase(f.name))
        || slug === game.pf2e.system.sluggify(this.getAncestryAdjustedSpecialNameLowerCase(f.name))
        || slug === game.pf2e.system.sluggify(this.getHeritageAdjustedSpecialNameLowerCase(f.name))
        || slug === game.pf2e.system.sluggify(f.originalName)
        || slug === game.pf2e.system.sluggify(this.getClassAdjustedSpecialNameLowerCase(f.originalName))
        || slug === game.pf2e.system.sluggify(this.getAncestryAdjustedSpecialNameLowerCase(f.originalName))
        || slug === game.pf2e.system.sluggify(this.getHeritageAdjustedSpecialNameLowerCase(f.originalName))
        || (game.settings.get("pf2e", "dualClassVariant")
          && (slug === game.pf2e.system.sluggify(this.getDualClassAdjustedSpecialNameLowerCase(f.name))
            || slug === game.pf2e.system.sluggify(this.getDualClassAdjustedSpecialNameLowerCase(f.originalName))
          )
        )
      )
    );
    // console.warn(`Results of find ${slug} in ${type}, ignoreAdded? ${ignoreAdded}`, {
    //   slug,
    //   parsedMatch,
    //   parsed: duplicate(this.parsed),
    // });
    return parsedMatch;
  }

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

  #findAllFeatureMatch(slug, ignoreAdded) {
    const featMatch = this.#parsedFeatureMatch("feats", slug, ignoreAdded);
    if (featMatch) return featMatch;
    const specialMatch = this.#parsedFeatureMatch("specials", slug, ignoreAdded);
    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) {
    src_logger.debug(`Adding granted item flags to ${document.name} (parent ${parent.name})`);
    const camelCase = game.pf2e.system.sluggify(document.system.slug, { camel: "dromedary" });
    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);
    if (!this.options.askForChoices) {
      this.result.feats.push(document);
    }
    const featureMatch = this.#findAllFeatureMatch(document.system.slug, true)
      ?? (document.name.includes("(")
        ? this.#findAllFeatureMatch(game.pf2e.system.sluggify(document.name.split("(")[0].trim()), true)
        : undefined
      );

    // console.warn(`Matching feature for ${document.name}?`, {
    //   featureMatch,
    // });

    if (featureMatch) {
      if (hasProperty(featureMatch, "added")) {
        featureMatch.added = true;
        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 });
  }

  async #featureChoiceMatch(choices, ignoreAdded, adjustName) {
    for (const choice of choices) {
      const doc = adjustName
        ? game.i18n.localize(choice.label)
        : await fromUuid(choice.value);
      if (!doc) continue;
      const slug = adjustName
        ? game.pf2e.system.sluggify(doc)
        : doc.system.slug;
      const featMatch = this.#findAllFeatureMatch(slug, ignoreAdded);
      if (featMatch) {
        if (adjustName && hasProperty(featMatch, "added")) featMatch.added = true;
        src_logger.debug("Choices evaluated", { choices, document, featMatch, choice });
        return choice;
      }
    }
    return undefined;
  }

  async #evaluateChoices(document, choiceSet) {
    src_logger.debug(`Evaluating choices for ${document.name}`, { document, choiceSet });
    const tempActor = await this.#generateTempActor();
    const cleansedChoiceSet = deepClone(choiceSet);
    try {
      const item = tempActor.getEmbeddedDocument("Item", document._id);
      const choiceSetRules = new game.pf2e.RuleElements.all.ChoiceSet(cleansedChoiceSet, item);
      const rollOptions = [tempActor.getRollOptions(), item.getRollOptions("item")].flat();
      const choices = (await choiceSetRules.inflateChoices()).filter((c) => !c.predicate || c.predicate.test(rollOptions));

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

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

      if (typeof cleansedChoiceSet.choices === "string" || Array.isArray(choices)) {
        for (const choice of choices) {
          const featMatch = this.#findAllFeatureMatch(choice.value, true, cleansedChoiceSet.adjustName);
          if (featMatch) {
            src_logger.debug("Choices evaluated", { cleansedChoiceSet, choices, document, featMatch, choice });
            featMatch.added = true;
            choice.nouuid = true;
            return choice;
          }
        }
      }

    } 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(source, propertyData) {
    if (source === null || typeof source === "number" || (typeof source === "string" && !source.includes("{"))) {
      return source;
    }

    // Walk the object tree and resolve any string values found
    if (Array.isArray(source)) {
      for (let i = 0; i < source.length; i++) {
        source[i] = this.#resolveInjectedUuid(source[i]);
      }
    } else if (typeof source === 'object' && source !== null) {
      for (const [key, value] of Object.entries(source)) {
        if (typeof value === "string" || (typeof value === 'object' && value !== null)) {
          source[key] = this.#resolveInjectedUuid(value);
        }
      }
      return source;
    } else if (typeof source === "string") {
      const match = source.match(/{(actor|item|rule)\|(.*?)}/);
      if (match && match[1] === "actor") {
        return String(getProperty(this.result.character, match[1]));
      } else if (match) {
        const value = this.grantItemLookUp[match[0]].uuid;
        if (!value) {
          src_logger.error("Failed to resolve injected property", {
            source,
            propertyData,
            key: match[1],
            prop: match[2],
          });
        }
        return String(value);
      } else {
        src_logger.error("Failed to resolve injected property", {
          source,
          propertyData,
        });
      }
    }

    return source;
  }

  async #generateGrantItemData(document) {
    src_logger.debug(`Generating grantItem rule lookups for ${document.name}...`, { document: deepClone(document) });
    for (const rule of document.system.rules.filter((r) => r.key === "GrantItem" && r.uuid.includes("{"))) {
      src_logger.debug("Generating rule for...", { document: deepClone(document), rule });
      const match = rule.uuid.match(/{(item|rule)\|(.*?)}/);
      if (match) {
        const flagName = match[2].split(".").pop();
        const choiceSet = document.system.rules.find((rule) => rule.key === "ChoiceSet" && rule.flag === flagName)
          ?? document.system.rules.find((rule) => rule.key === "ChoiceSet");
        const choice = choiceSet ? (await this.#evaluateChoices(document, choiceSet)) : undefined;
        const value = choice?.value ?? undefined;
        if (!value) {
          src_logger.warn("Failed to resolve injected uuid", {
            ruleData: choiceSet,
            flagName,
            key: match[1],
            prop: match[2],
            value,
          });
        } else {
          src_logger.debug(`Generated lookup ${value} for key ${document.name}`);
        }
        this.grantItemLookUp[rule.uuid] = {
          docId: document.id,
          key: rule.uuid,
          choice,
          uuid: value,
          flag: flagName,
          choiceSet,
        };
        this.grantItemLookUp[`${document._id}-${flagName}`] = {
          docId: document.id,
          key: rule.uuid,
          choice,
          uuid: value,
          flag: flagName,
          choiceSet,
        };
        this.grantItemLookUp[`${document._id}`] = {
          docId: document.id,
          key: rule.uuid,
          choice,
          uuid: value,
          flag: flagName,
          choiceSet,
        };
        this.grantItemLookUp[`${document._id}-${flagName}`] = {
          docId: document.id,
          key: rule.uuid,
          choice,
          uuid: value,
          flag: flagName,
          choiceSet,
        };
      } else {
        src_logger.error("Failed to resolve injected uuid", {
          document,
          rule,
        });
      }
    }
  }

  async #checkRule(document, rule) {
    const tempActor = await this.#generateTempActor([document]);
    const cleansedRule = deepClone(rule);
    try {
      const item = tempActor.getEmbeddedDocument("Item", document._id);
      const ruleElement = cleansedRule.key === "ChoiceSet"
        ? new game.pf2e.RuleElements.all.ChoiceSet(cleansedRule, item)
        : new game.pf2e.RuleElements.all.GrantItem(cleansedRule, item);
      const rollOptions = [tempActor.getRollOptions(), item.getRollOptions("item")].flat();
      const choices = cleansedRule.key === "ChoiceSet"
        ? (await ruleElement.inflateChoices()).filter((c) => !c.predicate || c.predicate.test(rollOptions))
        : [ruleElement.resolveValue()];

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

      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]);
    }
  }

  // eslint-disable-next-line complexity
  async #addGrantedRules(document) {
    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 = document.system.rules.filter((r) => !["GrantItem", "ChoiceSet", "MartialProficiency"].includes(r.key));
    const rulesToKeep = [];
    this.allFeatureRules[document._id] = deepClone(document.system.rules);
    this.autoAddedFeatureRules[document._id] = deepClone(document.system.rules.filter((r) => !["GrantItem", "ChoiceSet"].includes(r.key)));
    await this.#generateGrantItemData(document);

    const grantRules = document.system.rules.filter((r) => r.key === "GrantItem");
    const choiceRules = document.system.rules.filter((r) => r.key === "ChoiceSet");

    for (const ruleTypes of [choiceRules, grantRules]) {
      for (const rawRuleEntry of ruleTypes) {
        const ruleEntry = deepClone(rawRuleEntry);
        src_logger.debug(`Checking ${document.name} rule key: ${ruleEntry.key}`);

        const lookupName = ruleEntry.flag ? `${document._id}-${ruleEntry.flag}` : document._id;

        src_logger.debug("Rule check, looking up", {
          id: `${document._id}-${ruleEntry.flag}`,
          lookup: this.grantItemLookUp[lookupName],
          lookups: this.grantItemLookUp,
          ruleEntry,
          lookupName,
        });

        // have we pre-evaluated this choice?
        const choice = ruleEntry.key === "ChoiceSet"
          ? this.grantItemLookUp[lookupName]?.choice
            ? this.grantItemLookUp[lookupName].choice
            : await this.#evaluateChoices(document, ruleEntry)
          : undefined;

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

        src_logger.debug(`UUID for ${document.name}: "${uuid}"`, document, ruleEntry, choice);
        const ruleFeature = uuid ? await fromUuid(uuid) : undefined;
        if (ruleFeature) {
          const featureDoc = ruleFeature.toObject();
          featureDoc._id = foundry.utils.randomID();
          if (featureDoc.system.rules) this.allFeatureRules[document._id] = deepClone(document.system.rules);
          setProperty(featureDoc, "flags.pathmuncher.origin.uuid", 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);
            continue;
          }
          src_logger.debug(`Found rule feature ${featureDoc.name} for ${document.name} for`, ruleEntry);

          if (ruleEntry.predicate) {
            const testResult = await this.#checkRule(featureDoc, ruleEntry);
            // eslint-disable-next-line max-depth
            if (!testResult) {
              const data = { document, ruleEntry, featureDoc, testResult };
              src_logger.debug(`The test failed for ${document.name} rule key: ${ruleFeature.key} (This is probably not a problem).`, data);
              continue;
            }
          }
          if (choice) {
            ruleEntry.selection = choice.value;
          }
          this.autoAddedFeatureIds.add(`${ruleFeature.id}${ruleFeature.type}`);
          featureDoc._id = foundry.utils.randomID();
          this.#createGrantedItem(featureDoc, document);
          if (hasProperty(ruleFeature, "system.rules.length")) await this.#addGrantedRules(featureDoc);
        } else if (choice?.nouuid) {
          src_logger.debug("Parsed no id rule", { choice, uuid, ruleEntry });
          if (!ruleEntry.flag) ruleEntry.flag = game.pf2e.system.sluggify(document.name, { camel: "dromedary" });
          ruleEntry.selection = choice.value;
          if (choice.label) document.name = `${document.name} (${choice.label})`;
        } else if (choice && uuid && !hasProperty(ruleEntry, "selection")) {
          src_logger.debug("Parsed odd choice rule", { choice, uuid, ruleEntry });
          if (!ruleEntry.flag) ruleEntry.flag = game.pf2e.system.sluggify(document.name, { camel: "dromedary" });
          ruleEntry.selection = choice.value;
          if (ruleEntry.adjustName && choice.label) {
            const label = game.i18n.localize(choice.label);
            const name = `${document.name} (${label})`;
            const pattern = (() => {
              const escaped = RegExp.escape(label);
              return new RegExp(`\\(${escaped}\\) \\(${escaped}\\)$`);
            })();
            document.name = name.replace(pattern, `(${label})`);
          }
        } else {
          const data = {
            uuid: ruleEntry.uuid,
            document,
            ruleEntry,
            choice,
            lookup: this.grantItemLookUp[ruleEntry.uuid],
          };
          if (ruleEntry.key === "GrantItem" && this.grantItemLookUp[ruleEntry.uuid]) {
            rulesToKeep.push(ruleEntry);
            // const lookup = this.grantItemLookUp[ruleEntry.uuid].choiceSet
            // eslint-disable-next-line max-depth
            // if (!rulesToKeep.some((r) => r.key == lookup && r.prompt === lookup.prompt)) {
            //   rulesToKeep.push(this.grantItemLookUp[ruleEntry.uuid].choiceSet);
            // }
          } else if (ruleEntry.key === "ChoiceSet" && !hasProperty(ruleEntry, "flag")) {
            src_logger.debug("Prompting user for choices", ruleEntry);
            rulesToKeep.push(ruleEntry);
          }
          src_logger.warn("Unable to determine granted rule feature, needs better parser", data);
        }
        this.autoAddedFeatureRules[document._id].push(ruleEntry);
      }
    }
    if (!this.options.askForChoices) {
      // eslint-disable-next-line require-atomic-updates
      document.system.rules = rulesToKeep;
    }
  }

  async #addGrantedItems(document) {
    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)) {
        src_logger.debug(`Checking granted item ${document.name}, 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);
        if (hasProperty(featureDoc, "system.rules")) await this.#addGrantedRules(featureDoc);
      }
      if (!this.options.askForChoices) {
        // eslint-disable-next-line require-atomic-updates
        document.system.items = failedFeatureItems;
      }
    }
    if (hasProperty(document, "system.rules")) await this.#addGrantedRules(document);

  }

  async #detectGrantedFeatures() {
    if (this.result.class.length > 0) await this.#addGrantedItems(this.result.class[0]);
    if (this.result.ancestry.length > 0) await this.#addGrantedItems(this.result.ancestry[0]);
    if (this.result.heritage.length > 0) await this.#addGrantedItems(this.result.heritage[0]);
    if (this.result.background.length > 0) await this.#addGrantedItems(this.result.background[0]);
  }

  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);
    setProperty(this.result.character, "system.details.keyability.value", this.source.keyability);
    if (this.source.deity !== "Not set") setProperty(this.result.character, "system.details.deity.value", this.source.deity);
    setProperty(this.result.character, "system.traits.size.value", Pathmuncher.getSizeValue(this.source.size));
    setProperty(this.result.character, "system.traits.languages.value", this.source.languages.map((l) => l.toLowerCase()));

    this.#processSenses();

    setProperty(this.result.character, "system.abilities.str.value", this.source.abilities.str);
    setProperty(this.result.character, "system.abilities.dex.value", this.source.abilities.dex);
    setProperty(this.result.character, "system.abilities.con.value", this.source.abilities.con);
    setProperty(this.result.character, "system.abilities.int.value", this.source.abilities.int);
    setProperty(this.result.character, "system.abilities.wis.value", this.source.abilities.wis);
    setProperty(this.result.character, "system.abilities.cha.value", this.source.abilities.cha);

    setProperty(this.result.character, "system.saves.fortitude.tank", this.source.proficiencies.fortitude / 2);
    setProperty(this.result.character, "system.saves.reflex.value", this.source.proficiencies.reflex / 2);
    setProperty(this.result.character, "system.saves.will.value", this.source.proficiencies.will / 2);

    setProperty(this.result.character, "system.martial.advanced.rank", this.source.proficiencies.advanced / 2);
    setProperty(this.result.character, "system.martial.heavy.rank", this.source.proficiencies.heavy / 2);
    setProperty(this.result.character, "system.martial.light.rank", this.source.proficiencies.light / 2);
    setProperty(this.result.character, "system.martial.medium.rank", this.source.proficiencies.medium / 2);
    setProperty(this.result.character, "system.martial.unarmored.rank", this.source.proficiencies.unarmored / 2);
    setProperty(this.result.character, "system.martial.martial.rank", this.source.proficiencies.martial / 2);
    setProperty(this.result.character, "system.martial.simple.rank", this.source.proficiencies.simple / 2);
    setProperty(this.result.character, "system.martial.unarmed.rank", this.source.proficiencies.unarmed / 2);

    setProperty(this.result.character, "system.skills.acr.rank", this.source.proficiencies.acrobatics / 2);
    setProperty(this.result.character, "system.skills.arc.rank", this.source.proficiencies.arcana / 2);
    setProperty(this.result.character, "system.skills.ath.rank", this.source.proficiencies.athletics / 2);
    setProperty(this.result.character, "system.skills.cra.rank", this.source.proficiencies.crafting / 2);
    setProperty(this.result.character, "system.skills.dec.rank", this.source.proficiencies.deception / 2);
    setProperty(this.result.character, "system.skills.dip.rank", this.source.proficiencies.diplomacy / 2);
    setProperty(this.result.character, "system.skills.itm.rank", this.source.proficiencies.intimidation / 2);
    setProperty(this.result.character, "system.skills.med.rank", this.source.proficiencies.medicine / 2);
    setProperty(this.result.character, "system.skills.nat.rank", this.source.proficiencies.nature / 2);
    setProperty(this.result.character, "system.skills.occ.rank", this.source.proficiencies.occultism / 2);
    setProperty(this.result.character, "system.skills.prf.rank", this.source.proficiencies.performance / 2);
    setProperty(this.result.character, "system.skills.rel.rank", this.source.proficiencies.religion / 2);
    setProperty(this.result.character, "system.skills.soc.rank", this.source.proficiencies.society / 2);
    setProperty(this.result.character, "system.skills.ste.rank", this.source.proficiencies.stealth / 2);
    setProperty(this.result.character, "system.skills.sur.rank", this.source.proficiencies.survival / 2);
    setProperty(this.result.character, "system.skills.thi.rank", this.source.proficiencies.thievery / 2);

    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) =>
        i.system.slug === game.pf2e.system.sluggify(name)
        || i.system.slug === game.pf2e.system.sluggify(this.getClassAdjustedSpecialNameLowerCase(name))
        || i.system.slug === game.pf2e.system.sluggify(this.getAncestryAdjustedSpecialNameLowerCase(name))
        || i.system.slug === game.pf2e.system.sluggify(this.getHeritageAdjustedSpecialNameLowerCase(name))
        || (game.settings.get("pf2e", "dualClassVariant")
          && (i.system.slug === game.pf2e.system.sluggify(this.getDualClassAdjustedSpecialNameLowerCase(name))
          )
        )
      );
      if (indexMatch) return indexMatch;
    }
    return undefined;
  }

  async #generateFeatItems(compendiumLabel) {
    const compendium = await game.packs.get(compendiumLabel);
    const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });

    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;
        }
      }
      return 0;
    });
    for (const featArray of [this.parsed.feats, this.parsed.specials]) {
      for (const pBFeat of featArray) {
        if (pBFeat.added) continue;
        src_logger.debug("Generating feature for", pBFeat);

        const indexMatch = this.#indexFind(index, [pBFeat.name, pBFeat.originalName]);
        const displayName = pBFeat.extra ? `${pBFeat.name} (${pBFeat.extra})` : pBFeat.name;
        if (!indexMatch) {
          src_logger.debug(`Unable to match feat ${displayName}`, { displayName, name: pBFeat.name, extra: pBFeat.extra, pBFeat, compendiumLabel });
          this.check[pBFeat.originalName] = { name: displayName, type: "feat", details: { displayName, name: pBFeat.name, originalName: pBFeat.originalName, extra: pBFeat.extra, pBFeat, compendiumLabel } };
          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, compendiumLabel });
          continue;
        }

        const doc = await compendium.getDocument(indexMatch._id);
        const item = doc.toObject();
        item._id = foundry.utils.randomID();
        item.name = displayName;

        this.#generateFoundryFeatLocation(item, pBFeat);
        this.result.feats.push(item);
        await this.#addGrantedItems(item);
      }
    }
  }

  async #generateSpecialItems(compendiumLabel) {
    const compendium = await game.packs.get(compendiumLabel);
    const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });

    for (const special of this.parsed.specials) {
      if (special.added) continue;
      src_logger.debug("Generating special for", special);
      const indexMatch = this.#indexFind(index, [special.name, special.originalName]);
      if (!indexMatch) {
        src_logger.debug(`Unable to match special ${special.name}`, { special: special.name, compendiumLabel });
        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, compendiumLabel });
        continue;
      }

      const doc = await compendium.getDocument(indexMatch._id);
      const docData = doc.toObject();
      docData._id = foundry.utils.randomID();
      this.result.feats.push(docData);
      await this.#addGrantedItems(docData);
    }
  }

  async #generateEquipmentItems(pack = "pf2e.equipment-srd") {
    const compendium = game.packs.get(pack);
    const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
    const compendiumBackpack = await compendium.getDocument("3lgwjrFEsQVKzhh7");

    const adventurersPack = this.parsed.equipment.find((e) => e.pbName === "Adventurer's Pack");
    const backpackInstance = adventurersPack ? compendiumBackpack.toObject() : null;
    if (backpackInstance) {
      adventurersPack.added = true;
      backpackInstance._id = foundry.utils.randomID();
      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 compendium.getDocument(indexMatch._id);
        const itemData = doc.toObject();
        itemData._id = foundry.utils.randomID();
        itemData.system.quantity = content.qty;
        itemData.system.containerId = backpackInstance?._id;
        this.result.equipment.push(itemData);
      }
    }

    for (const e of this.parsed.equipment) {
      if (e.pbName === "Adventurer's Pack") continue;
      if (e.added) continue;
      if (this.IGNORED_EQUIPMENT.includes(e.pbName)) {
        e.added = true;
        continue;
      }
      src_logger.debug("Generating item for", e);
      const indexMatch = index.find((i) => i.system.slug === game.pf2e.system.sluggify(e.pbName));
      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 compendium.getDocument(indexMatch._id);
      if (doc.type != "kit") {
        const itemData = doc.toObject();
        itemData._id = foundry.utils.randomID();
        itemData.system.quantity = e.qty;
        const type = doc.type === "treasure" ? "treasure" : "equipment";
        this.result[type].push(itemData);
      }
      // eslint-disable-next-line require-atomic-updates
      e.added = true;
    }
  }

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

    for (const w of this.parsed.weapons) {
      if (this.IGNORED_EQUIPMENT.includes(w.pbName)) {
        w.added = true;
        continue;
      }
      src_logger.debug("Generating weapon for", w);
      const indexMatch = index.find((i) => i.system.slug === game.pf2e.system.sluggify(w.pbName));
      if (!indexMatch) {
        src_logger.error(`Unable to match weapon item ${w.name}`, w);
        this.bad.push({ pbName: w.pbName, type: "weapon", details: { w } });
        continue;
      }

      const doc = await compendium.getDocument(indexMatch._id);
      const itemData = doc.toObject();
      itemData._id = foundry.utils.randomID();
      itemData.system.quantity = w.qty;
      itemData.system.damage.die = w.die;
      itemData.system.potencyRune.value = w.pot;
      itemData.system.strikingRune.value = w.str;

      if (w.runes[0]) itemData.system.propertyRune1.value = game.pf2e.system.sluggify(w.runes[0], { camel: "dromedary" });
      if (w.runes[1]) itemData.system.propertyRune2.value = game.pf2e.system.sluggify(w.runes[1], { camel: "dromedary" });
      if (w.runes[2]) itemData.system.propertyRune3.value = game.pf2e.system.sluggify(w.runes[2], { camel: "dromedary" });
      if (w.runes[3]) itemData.system.propertyRune4.value = game.pf2e.system.sluggify(w.runes[3], { camel: "dromedary" });
      if (w.mat) {
        const material = w.mat.split(" (")[0];
        itemData.system.preciousMaterial.value = game.pf2e.system.sluggify(material, { camel: "dromedary" });
        itemData.system.preciousMaterialGrade.value = Pathmuncher.getMaterialGrade(w.mat);
      }
      if (w.display) itemData.name = w.display;

      this.result.weapons.push(itemData);
      w.added = true;
    }
  }

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

    for (const a of this.parsed.armor) {
      if (this.IGNORED_EQUIPMENT.includes(a.pbName)) {
        a.added = true;
        continue;
      }
      src_logger.debug("Generating armor for", a);
      const indexMatch = index.find((i) =>
        i.system.slug === game.pf2e.system.sluggify(a.pbName)
        || i.system.slug === game.pf2e.system.sluggify(`${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 compendium.getDocument(indexMatch._id);
      const itemData = doc.toObject();
      itemData._id = foundry.utils.randomID();
      itemData.system.equipped.value = a.worn ?? false;
      if (!this.RESTRICTED_EQUIPMENT.some((i) => itemData.name.startsWith(i))) {
        itemData.system.equipped.inSlot = a.worn ?? false;
        itemData.system.quantity = a.qty;
        itemData.system.category = a.prof;
        itemData.system.potencyRune.value = a.pot;
        itemData.system.resiliencyRune.value = a.res;

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

        if (a.runes[0]) itemData.system.propertyRune1.value = game.pf2e.system.sluggify(a.runes[0], { camel: "dromedary" });
        if (a.runes[1]) itemData.system.propertyRune2.value = game.pf2e.system.sluggify(a.runes[1], { camel: "dromedary" });
        if (a.runes[2]) itemData.system.propertyRune3.value = game.pf2e.system.sluggify(a.runes[2], { camel: "dromedary" });
        if (a.runes[3]) itemData.system.propertyRune4.value = game.pf2e.system.sluggify(a.runes[3], { camel: "dromedary" });
        if (a.mat) {
          const material = a.mat.split(" (")[0];
          itemData.system.preciousMaterial.value = game.pf2e.system.sluggify(material, { camel: "dromedary" });
          itemData.system.preciousMaterialGrade.value = Pathmuncher.getMaterialGrade(a.mat);
        }
      }
      if (a.display) itemData.name = a.display;

      this.result.armor.push(itemData);
      // eslint-disable-next-line require-atomic-updates
      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";
    // 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;
    // final fallback
    // 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";
  }

  async #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: {
        slot0: {
          max: caster.perDay[0],
          prepared: {},
          value: caster.perDay[0],
        },
        slot1: {
          max: caster.perDay[1],
          prepared: {},
          value: caster.perDay[1],
        },
        slot2: {
          max: caster.perDay[2],
          prepared: {},
          value: caster.perDay[2],
        },
        slot3: {
          max: caster.perDay[3],
          prepared: {},
          value: caster.perDay[3],
        },
        slot4: {
          max: caster.perDay[4],
          prepared: {},
          value: caster.perDay[4],
        },
        slot5: {
          max: caster.perDay[5],
          prepared: {},
          value: caster.perDay[5],
        },
        slot6: {
          max: caster.perDay[6],
          prepared: {},
          value: caster.perDay[6],
        },
        slot7: {
          max: caster.perDay[7],
          prepared: {},
          value: caster.perDay[7],
        },
        slot8: {
          max: caster.perDay[8],
          prepared: {},
          value: caster.perDay[8],
        },
        slot9: {
          max: caster.perDay[9],
          prepared: {},
          value: caster.perDay[9],
        },
        slot10: {
          max: caster.perDay[10],
          prepared: {},
          value: caster.perDay[10],
        },
      },
      showUnpreparedSpells: { value: true },
    };
    const data = {
      _id: foundry.utils.randomID(),
      name,
      type: "spellcastingEntry",
      system: spellcastingEntity,
    };
    this.result.casters.push(data);
    return data;
  }

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

    const psiCompendium = game.packs.get("pf2e-psychic-amps.psychic-psi-cantrips");
    const psiIndex = psiCompendium ? await psiCompendium.getIndex({ fields: ["name", "type", "system.slug"] }) : undefined;

    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 = await this.#generateSpellCaster(caster);
      src_logger.debug("Generated caster instance", instance);

      for (const spellSelection of caster.spells) {
        const level = spellSelection.spellLevel;

        for (const [i, spell] of spellSelection.list.entries()) {
          const spellName = spell.split("(")[0].trim();
          src_logger.debug("spell details", { spell, spellName, spellSelection, list: spellSelection.list });

          const psiMatch = psiIndex ? psiIndex.find((i) => i.name === spell) : undefined;
          const indexMatch = index.find((i) => i.system.slug === game.pf2e.system.sluggify(spellName));
          if (!indexMatch && !psiMatch) {
            src_logger.error(`Unable to match spell ${spell}`, { spell, spellName, spellSelection, caster, instance });
            this.bad.push({ pbName: spell, type: "spell", details: { originalName: spell, name: spellName, spellSelection, caster } });
            continue;
          }

          const doc = psiMatch
            ? await psiCompendium.getDocument(psiMatch._id)
            : await compendium.getDocument(indexMatch._id);
          const itemData = doc.toObject();
          itemData._id = foundry.utils.randomID();
          itemData.system.location.heightenedLevel = level;
          itemData.system.location.value = instance._id;
          this.result.spells.push(itemData);

          instance.system.slots[`slot${level}`].prepared[i] = { id: itemData._id };
        }
      }
    }

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

  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 compendium = game.packs.get("pf2e.equipment-srd");
    const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
    const uuids = [];

    for (const formulaSource of this.source.formula) {
      for (const formulaName of formulaSource.known) {
        const indexMatch = index.find((i) => i.system.slug === game.pf2e.system.sluggify(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 compendium.getDocument(indexMatch._id);
        uuids.push({ uuid: doc.uuid });
      }
    }
    setProperty(this.result.character, "system.crafting.formulas", uuids);
  }

  async #processFeats() {
    await this.#generateFeatItems("pf2e.feats-srd");
    await this.#generateFeatItems("pf2e.ancestryfeatures");
    await this.#generateSpecialItems("pf2e.ancestryfeatures");
    await this.#generateSpecialItems("pf2e.classfeatures");
    await this.#generateSpecialItems("pf2e.actionspf2e");
  }

  async #processEquipment() {
    await this.#generateEquipmentItems();
    await this.#generateWeaponItems();
    await this.#generateArmorItems();
    await this.#generateMoney();
  }

  async #generateTempActor(documents = []) {
    const actorData = mergeObject({ type: "character" }, this.result.character);
    actorData.name = "Mr Temp";
    const actor = await Actor.create(actorData);
    const currentState = duplicate(this.result);

    const currentItems = [
      ...(this.options.askForChoices ? this.autoFeats : []),
      ...currentState.feats,
      ...currentState.class,
      ...currentState.background,
      ...currentState.ancestry,
      ...currentState.heritage,
      ...currentState.deity,
      ...currentState.lores,
    ];
    for (const doc of documents) {
      if (!currentItems.some((d) => d._id === doc._id)) {
        currentItems.push(doc);
      }
    }
    try {
      const items = duplicate(currentItems).map((i) => {
        if (i.system.items) i.system.items = [];
        if (i.system.rules) i.system.rules = [];
        return i;
      });

      await actor.createEmbeddedDocuments("Item", items, { keepId: true });
      const ruleIds = currentItems.map((i) => i._id);
      const ruleUpdates = [];
      for (const [key, value] of Object.entries(this.allFeatureRules)) {
        if (ruleIds.includes(key)) {
          ruleUpdates.push({
            _id: key,
            system: {
              // rules: value,
              rules: value.filter((r) => ["GrantItem", "ChoiceSet", "RollOption"].includes(r.key)),
            },
          });
        }
      }
      // console.warn("rule updates", 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),
          },
        });
      }

      for (const doc of documents) {
        if (getProperty(doc, "system.rules")?.length > 0 && !ruleUpdates.some((r) => r._id === doc._id)) {
          ruleUpdates.push({
            _id: doc._id,
            system: {
              rules: deepClone(doc.system.rules),
            },
          });
        }
      }

      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;
    this.#prepare();
    await this.#processCore();
    await this.#processFormulas();
    await this.#processGenericCompendiumLookup("pf2e.deities", this.source.deity, "deity");
    await this.#processGenericCompendiumLookup("pf2e.backgrounds", this.source.background, "background");
    await this.#processGenericCompendiumLookup("pf2e.classes", this.source.class, "class");
    await this.#processGenericCompendiumLookup("pf2e.ancestries", this.source.ancestry, "ancestry");
    await this.#processGenericCompendiumLookup("pf2e.heritages", this.source.heritage, "heritage");
    await this.#detectGrantedFeatures();
    await this.#processFeats();
    await this.#processEquipment();
    await this.#processSpells();
    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 #createActorEmbeddedDocuments() {
    if (this.options.addDeity) await this.actor.createEmbeddedDocuments("Item", this.result.deity, { keepId: true });
    if (this.options.addAncestry) await this.actor.createEmbeddedDocuments("Item", this.result.ancestry, { keepId: true });
    if (this.options.addHeritage) await this.actor.createEmbeddedDocuments("Item", this.result.heritage, { keepId: true });
    if (this.options.addBackground) await this.actor.createEmbeddedDocuments("Item", this.result.background, { keepId: true });
    if (this.options.addClass) await this.actor.createEmbeddedDocuments("Item", this.result.class, { keepId: true });
    if (this.options.addLores) await this.actor.createEmbeddedDocuments("Item", this.result.lores, { keepId: true });
    // for (const feat of this.result.feats.reverse()) {
    //   console.warn(`creating ${feat.name}`, feat);
    //   await this.actor.createEmbeddedDocuments("Item", [feat], { keepId: true });
    // }
    if (this.options.addFeats) await this.actor.createEmbeddedDocuments("Item", this.result.feats, { keepId: true });
    if (this.options.addSpells) {
      await this.actor.createEmbeddedDocuments("Item", this.result.casters, { keepId: true });
      await this.actor.createEmbeddedDocuments("Item", this.result.spells, { keepId: true });
    }
    if (this.options.addEquipment) await this.actor.createEmbeddedDocuments("Item", this.result.equipment, { keepId: true });
    if (this.options.addWeapons) await this.actor.createEmbeddedDocuments("Item", this.result.weapons, { keepId: true });
    if (this.options.addArmor) {
      await this.actor.createEmbeddedDocuments("Item", this.result.armor, { keepId: true });
      await this.actor.updateEmbeddedDocuments("Item", this.result.armor, { keepId: true });
    }
    if (this.options.addTreasure) await this.actor.createEmbeddedDocuments("Item", this.result.treasure, { keepId: true });
    if (this.options.addMoney) await this.actor.createEmbeddedDocuments("Item", this.result.money, { keepId: true });
  }

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

      const itemUpdates = [];
      for (const [key, value] of Object.entries(this.autoAddedFeatureItems)) {
        if (importedItems.includes(key)) {
          itemUpdates.push({
            _id: `${key}`,
            system: {
              items: deepClone(value),
            },
          });
        }
      }
      src_logger.debug("Restoring granted item logic", itemUpdates);
      await this.actor.updateEmbeddedDocuments("Item", itemUpdates);
    }
  }

  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;
    }

    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").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" || b.type === "special")
        && this.parsed.feats.concat(this.parsed.specials).some((f) => f.name === b.details.name && !f.added)
      ).map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Feats")}: ${b.details.name}</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,
      ...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>`;
    }
    if (this.result.focusPool > 0) {
      warning += `<strong>${game.i18n.localize("pathmuncher.Dialogs.Pathmuncher.FocusSpells")}</strong><br>`;
    }

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

    if (totalBad.length > 0 || this.result.focusPool > 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 ({ parent, pathbuilderJson } = {}) {
    this.parent = parent;
    this.pathbuilderJson = pathbuilderJson;

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

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


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

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

    if (existingPet) return existingPet.toObject();

    const actorData = {
      type: type.toLowerCase(),
      name: familiarName,
      system: {
        master: {
          id: this.parent._id,
          ability: this.parent.system.details.keyability.value,
        },
      },
      prototypeToken: {
        name: familiarName,
      },
      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() {

    for (const petJson of this.pathbuilderJson.pets) {
      // only support familiars at this time
      if (petJson.type !== "Familiar") {
        src_logger.warn(`Pets with type ${petJson.type} are not supported at this time`);
        continue;
      }
      await this.ensureFolder(petJson.type);
      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) {
    const localizedType = `pathmuncher.Label.${type}`;
    $(".import-progress-bar")
      .width(`${Math.trunc((count / total) * 100)}%`)
      .html(
        `<span>${game.i18n.localize("pathmuncher.Label.Working")} (${game.i18n.localize(localizedType)})...</span>`
      );
  }

  async _updateObject(event, formData) {
    const pathbuilderId = formData.textBoxBuildID;
    const options = {
      pathbuilderId,
      addMoney: formData.checkBoxMoney,
      addFeats: formData.checkBoxFeats,
      addSpells: formData.checkBoxSpells,
      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,
      askForChoices: formData.checkBoxAskForChoices,
    };
    src_logger.debug("Pathmuncher options", options);

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

    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);
        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({ 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,
    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);
  }

}

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




function registerSheetButton() {

  const trustedUsersOnly = src_utils.setting(constants.SETTINGS.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> Pathmuncher</a>`);

      button.click(() => {
        const muncher = new PathmuncherImporter(PathmuncherImporter.defaultOptions, data.actor);
        muncher.render(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