text: import { EntitySheetHelper } from "./helper.js";
  
  /**
   * Extend the base Actor document to support attributes and groups with a custom template creation dialog.
   * @extends {Actor}
   */
  export class SimpleActor extends Actor {
  
    /** @inheritdoc */
    prepareDerivedData() {
      super.prepareDerivedData();
      this.data.data.groups = this.data.data.groups || {};
      this.data.data.attributes = this.data.data.attributes || {};
      EntitySheetHelper.clampResourceValues(this.data.data.attributes);
    }
  
    /* -------------------------------------------- */
  
    /** @override */
    static async createDialog(data={}, options={}) {
      return EntitySheetHelper.createDialog.call(this, data, options);
    }
  
    /* -------------------------------------------- */
  
    /**
     * Is this Actor used as a template for other Actors?
     * @type {boolean}
     */
    get isTemplate() {
      return !!this.getFlag("worldbuilding", "isTemplate");
    }
  
    /* -------------------------------------------- */
    /*  Roll Data Preparation                       */
    /* -------------------------------------------- */
  
    /** @inheritdoc */
    getRollData() {
  
      // Copy the actor's system data
      const data = this.toObject(false).data;
      const shorthand = game.settings.get("worldbuilding", "macroShorthand");
      const formulaAttributes = [];
      const itemAttributes = [];
  
      // Handle formula attributes when the short syntax is disabled.
      this._applyShorthand(data, formulaAttributes, shorthand);
  
      // Map all items data using their slugified names
      this._applyItems(data, itemAttributes, shorthand);
  
      // Evaluate formula replacements on items.
      this._applyItemsFormulaReplacements(data, itemAttributes, shorthand);
  
      // Evaluate formula attributes after all other attributes have been handled, including items.
      this._applyFormulaReplacements(data, formulaAttributes, shorthand);
  
      // Remove the attributes if necessary.
      if ( !!shorthand ) {
        delete data.attributes;
        delete data.attr;
        delete data.groups;
      }
      return data;
    }
  
    /* -------------------------------------------- */
  
    /**
     * Apply shorthand syntax to actor roll data.
     * @param {Object} data The actor's data object.
     * @param {Array} formulaAttributes Array of attributes that are derived formulas.
     * @param {Boolean} shorthand Whether or not the shorthand syntax is used.
     */
    _applyShorthand(data, formulaAttributes, shorthand) {
      // Handle formula attributes when the short syntax is disabled.
      for ( let [k, v] of Object.entries(data.attributes || {}) ) {
        // Make an array of formula attributes for later reference.
        if ( v.dtype === "Formula" ) formulaAttributes.push(k);
        // Add shortened version of the attributes.
        if ( !!shorthand ) {
          if ( !(k in data) ) {
            // Non-grouped attributes.
            if ( v.dtype ) {
              data[k] = v.value;
            }
            // Grouped attributes.
            else {
              data[k] = {};
              for ( let [gk, gv] of Object.entries(v) ) {
                data[k][gk] = gv.value;
                if ( gv.dtype === "Formula" ) formulaAttributes.push(`${k}.${gk}`);
              }
            }
          }
        }
      }
    }
  
    /* -------------------------------------------- */
  
    /**
     * Add items to the actor roll data object. Handles regular and shorthand
     * syntax, and calculates derived formula attributes on the items.
     * @param {Object} data The actor's data object.
     * @param {string[]} itemAttributes
     * @param {Boolean} shorthand Whether or not the shorthand syntax is used.
     */
    _applyItems(data, itemAttributes, shorthand) {
      // Map all items data using their slugified names
      data.items = this.items.reduce((obj, item) => {
        const key = item.name.slugify({strict: true});
        const itemData = item.toObject(false).data;
  
        // Add items to shorthand and note which ones are formula attributes.
        for ( let [k, v] of Object.entries(itemData.attributes) ) {
          // When building the attribute list, prepend the item name for later use.
          if ( v.dtype === "Formula" ) itemAttributes.push(`${key}..${k}`);
          // Add shortened version of the attributes.
          if ( !!shorthand ) {
            if ( !(k in itemData) ) {
              // Non-grouped item attributes.
              if ( v.dtype ) {
                itemData[k] = v.value;
              }
              // Grouped item attributes.
              else {
                if ( !itemData[k] ) itemData[k] = {};
                for ( let [gk, gv] of Object.entries(v) ) {
                  itemData[k][gk] = gv.value;
                  if ( gv.dtype === "Formula" ) itemAttributes.push(`${key}..${k}.${gk}`);
                }
              }
            }
          }
          // Handle non-shorthand version of grouped attributes.
          else {
            if ( !v.dtype ) {
              if ( !itemData[k] ) itemData[k] = {};
              for ( let [gk, gv] of Object.entries(v) ) {
                itemData[k][gk] = gv.value;
                if ( gv.dtype === "Formula" ) itemAttributes.push(`${key}..${k}.${gk}`);
              }
            }
          }
        }
  
        // Delete the original attributes key if using the shorthand syntax.
        if ( !!shorthand ) {
          delete itemData.attributes;
        }
        obj[key] = itemData;
        return obj;
      }, {});
    }
  
    /* -------------------------------------------- */
  
    _applyItemsFormulaReplacements(data, itemAttributes, shorthand) {
      for ( let k of itemAttributes ) {
        // Get the item name and separate the key.
        let item = null;
        let itemKey = k.split('..');
        item = itemKey[0];
        k = itemKey[1];
  
        // Handle group keys.
        let gk = null;
        if ( k.includes('.') ) {
          let attrKey = k.split('.');
          k = attrKey[0];
          gk = attrKey[1];
        }
  
        let formula = '';
        if ( !!shorthand ) {
          // Handle grouped attributes first.
          if ( data.items[item][k][gk] ) {
            formula = data.items[item][k][gk].replace('@item.', `@items.${item}.`);
            data.items[item][k][gk] = Roll.replaceFormulaData(formula, data);
          }
          // Handle non-grouped attributes.
          else if ( data.items[item][k] ) {
            formula = data.items[item][k].replace('@item.', `@items.${item}.`);
            data.items[item][k] = Roll.replaceFormulaData(formula, data);
          }
        }
        else {
          // Handle grouped attributes first.
          if ( data.items[item]['attributes'][k][gk] ) {
            formula = data.items[item]['attributes'][k][gk]['value'].replace('@item.', `@items.${item}.attributes.`);
            data.items[item]['attributes'][k][gk]['value'] = Roll.replaceFormulaData(formula, data);
          }
          // Handle non-grouped attributes.
          else if ( data.items[item]['attributes'][k]['value'] ) {
            formula = data.items[item]['attributes'][k]['value'].replace('@item.', `@items.${item}.attributes.`);
            data.items[item]['attributes'][k]['value'] = Roll.replaceFormulaData(formula, data);
          }
        }
      }
    }
  
    /* -------------------------------------------- */
  
    /**
     * Apply replacements for derived formula attributes.
     * @param {Object} data The actor's data object.
     * @param {Array} formulaAttributes Array of attributes that are derived formulas.
     * @param {Boolean} shorthand Whether or not the shorthand syntax is used.
     */
    _applyFormulaReplacements(data, formulaAttributes, shorthand) {
      // Evaluate formula attributes after all other attributes have been handled, including items.
      for ( let k of formulaAttributes ) {
        // Grouped attributes are included as `group.attr`, so we need to split them into new keys.
        let attr = null;
        if ( k.includes('.') ) {
          let attrKey = k.split('.');
          k = attrKey[0];
          attr = attrKey[1];
        }
        // Non-grouped attributes.
        if ( data.attributes[k]?.value ) {
          data.attributes[k].value = Roll.replaceFormulaData(String(data.attributes[k].value), data);
        }
        // Grouped attributes.
        else if ( attr ) {
          data.attributes[k][attr].value = Roll.replaceFormulaData(String(data.attributes[k][attr].value), data);
        }
  
        // Duplicate values to shorthand.
        if ( !!shorthand ) {
          // Non-grouped attributes.
          if ( data.attributes[k]?.value ) {
            data[k] = data.attributes[k].value;
          }
          // Grouped attributes.
          else {
            if ( attr ) {
              // Initialize a group key in case it doesn't exist.
              if ( !data[k] ) {
                data[k] = {};
              }
              data[k][attr] = data.attributes[k][attr].value;
            }
          }
        }
      }
    }
  
    /* -------------------------------------------- */
  
    /** @inheritdoc */
    async modifyTokenAttribute(attribute, value, isDelta = false, isBar = true) {
      const current = foundry.utils.getProperty(this.data.data, attribute);
      if ( !isBar || !isDelta || (current?.dtype !== "Resource") ) {
        return super.modifyTokenAttribute(attribute, value, isDelta, isBar);
      }
      const updates = {[`data.${attribute}.value`]: Math.clamped(current.value + value, current.min, current.max)};
      const allowed = Hooks.call("modifyTokenAttribute", {attribute, value, isDelta, isBar}, updates);
      return allowed !== false ? this.update(updates) : this;
    }
  }
  