import ActorSheet5e from "./base.js";

/**
 * An Actor sheet for Vehicle type actors.
 * Extends the base ActorSheet5e class.
 * @type {ActorSheet5e}
 */
export default class ActorSheet5eVehicle extends ActorSheet5e {
  /**
   * Define default rendering options for the Vehicle sheet.
   * @returns {Object}
   */
  static get defaultOptions() {
    return mergeObject(super.defaultOptions, {
      classes: ["sw5e", "sheet", "actor", "vehicle"],
      width: 605,
      height: 680
    });
  }

  /* -------------------------------------------- */

  /**
   * Creates a new cargo entry for a vehicle Actor.
   */
  static get newCargo() {
    return {
      name: '',
      quantity: 1
    };
  }

  /* -------------------------------------------- */

  /**
   * Compute the total weight of the vehicle's cargo.
   * @param {Number} totalWeight    The cumulative item weight from inventory items
   * @param {Object} actorData      The data object for the Actor being rendered
   * @returns {{max: number, value: number, pct: number}}
   * @private
   */
  _computeEncumbrance(totalWeight, actorData) {

    // Compute currency weight
    const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0);
    totalWeight += totalCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;

    // Vehicle weights are an order of magnitude greater.
    totalWeight /= CONFIG.SW5E.encumbrance.vehicleWeightMultiplier;

    // Compute overall encumbrance
    const max = actorData.data.attributes.capacity.cargo;
    const pct = Math.clamped((totalWeight * 100) / max, 0, 100);
    return {value: totalWeight.toNearest(0.1), max, pct};
  }

  /* -------------------------------------------- */

  /** @override */
  _getMovementSpeed(actorData, largestPrimary=true) {
    return super._getMovementSpeed(actorData, largestPrimary);
  }

  /* -------------------------------------------- */

  /**
   * Prepare items that are mounted to a vehicle and require one or more crew
   * to operate.
   * @private
   */
  _prepareCrewedItem(item) {

    // Determine crewed status
    const isCrewed = item.data.crewed;
    item.toggleClass = isCrewed ? 'active' : '';
    item.toggleTitle = game.i18n.localize(`SW5E.${isCrewed ? 'Crewed' : 'Uncrewed'}`);

    // Handle crew actions
    if (item.type === 'feat' && item.data.activation.type === 'crew') {
      item.crew = item.data.activation.cost;
      item.cover = game.i18n.localize(`SW5E.${item.data.cover ? 'CoverTotal' : 'None'}`);
      if (item.data.cover === .5) item.cover = '½';
      else if (item.data.cover === .75) item.cover = '¾';
      else if (item.data.cover === null) item.cover = '—';
      if (item.crew < 1 || item.crew === null) item.crew = '—';
    }

    // Prepare vehicle weapons
    if (item.type === 'equipment' || item.type === 'weapon') {
      item.threshold = item.data.hp.dt ? item.data.hp.dt : '—';
    }
  }

  /* -------------------------------------------- */

  /**
   * Organize Owned Items for rendering the Vehicle sheet.
   * @private
   */
  _prepareItems(data) {
    const cargoColumns = [{
      label: game.i18n.localize('SW5E.Quantity'),
      css: 'item-qty',
      property: 'quantity',
      editable: 'Number'
    }];

    const equipmentColumns = [{
      label: game.i18n.localize('SW5E.Quantity'),
      css: 'item-qty',
      property: 'data.quantity'
    }, {
      label: game.i18n.localize('SW5E.AC'),
      css: 'item-ac',
      property: 'data.armor.value'
    }, {
      label: game.i18n.localize('SW5E.HP'),
      css: 'item-hp',
      property: 'data.hp.value',
      editable: 'Number'
    }, {
      label: game.i18n.localize('SW5E.Threshold'),
      css: 'item-threshold',
      property: 'threshold'
    }];

    const features = {
      actions: {
        label: game.i18n.localize('SW5E.ActionPl'),
        items: [],
        crewable: true,
        dataset: {type: 'feat', 'activation.type': 'crew'},
        columns: [{
          label: game.i18n.localize('SW5E.VehicleCrew'),
          css: 'item-crew',
          property: 'crew'
        }, {
          label: game.i18n.localize('SW5E.Cover'),
          css: 'item-cover',
          property: 'cover'
        }]
      },
      equipment: {
        label: game.i18n.localize('SW5E.ItemTypeEquipment'),
        items: [],
        crewable: true,
        dataset: {type: 'equipment', 'armor.type': 'vehicle'},
        columns: equipmentColumns
      },
      passive: {
        label: game.i18n.localize('SW5E.Features'),
        items: [],
        dataset: {type: 'feat'}
      },
      reactions: {
        label: game.i18n.localize('SW5E.ReactionPl'),
        items: [],
        dataset: {type: 'feat', 'activation.type': 'reaction'}
      },
      weapons: {
        label: game.i18n.localize('SW5E.ItemTypeWeaponPl'),
        items: [],
        crewable: true,
        dataset: {type: 'weapon', 'weapon-type': 'siege'},
        columns: equipmentColumns
      }
    };

    const cargo = {
      crew: {
        label: game.i18n.localize('SW5E.VehicleCrew'),
        items: data.data.cargo.crew,
        css: 'cargo-row crew',
        editableName: true,
        dataset: {type: 'crew'},
        columns: cargoColumns
      },
      passengers: {
        label: game.i18n.localize('SW5E.VehiclePassengers'),
        items: data.data.cargo.passengers,
        css: 'cargo-row passengers',
        editableName: true,
        dataset: {type: 'passengers'},
        columns: cargoColumns
      },
      cargo: {
        label: game.i18n.localize('SW5E.VehicleCargo'),
        items: [],
        dataset: {type: 'loot'},
        columns: [{
          label: game.i18n.localize('SW5E.Quantity'),
          css: 'item-qty',
          property: 'data.quantity',
          editable: 'Number'
        }, {
          label: game.i18n.localize('SW5E.Price'),
          css: 'item-price',
          property: 'data.price',
          editable: 'Number'
        }, {
          label: game.i18n.localize('SW5E.Weight'),
          css: 'item-weight',
          property: 'data.weight',
          editable: 'Number'
        }]
      }
    };

    let totalWeight = 0;
    for (const item of data.items) {
      this._prepareCrewedItem(item);
      if (item.type === 'weapon') features.weapons.items.push(item);
      else if (item.type === 'equipment') features.equipment.items.push(item);
      else if (item.type === 'loot') {
        totalWeight += (item.data.weight || 0) * item.data.quantity;
        cargo.cargo.items.push(item);
      }
      else if (item.type === 'feat') {
        if (!item.data.activation.type || item.data.activation.type === 'none') {
          features.passive.items.push(item);
        }
        else if (item.data.activation.type === 'reaction') features.reactions.items.push(item);
        else features.actions.items.push(item);
      }
    }

    data.features = Object.values(features);
    data.cargo = Object.values(cargo);
    data.data.attributes.encumbrance = this._computeEncumbrance(totalWeight, data);
  }

  /* -------------------------------------------- */
  /*  Event Listeners and Handlers                */
  /* -------------------------------------------- */

  /** @override */
  activateListeners(html) {
    super.activateListeners(html);
    if (!this.options.editable) return;

    html.find('.item-toggle').click(this._onToggleItem.bind(this));
    html.find('.item-hp input')
      .click(evt => evt.target.select())
      .change(this._onHPChange.bind(this));

    html.find('.item:not(.cargo-row) input[data-property]')
      .click(evt => evt.target.select())
      .change(this._onEditInSheet.bind(this));

    html.find('.cargo-row input')
      .click(evt => evt.target.select())
      .change(this._onCargoRowChange.bind(this));

    if (this.actor.data.data.attributes.actions.stations) {
      html.find('.counter.actions, .counter.action-thresholds').hide();
    }
  }

  /* -------------------------------------------- */

  /**
   * Handle saving a cargo row (i.e. crew or passenger) in-sheet.
   * @param event {Event}
   * @returns {Promise<Actor>|null}
   * @private
   */
  _onCargoRowChange(event) {
    event.preventDefault();
    const target = event.currentTarget;
    const row = target.closest('.item');
    const idx = Number(row.dataset.itemId);
    const property = row.classList.contains('crew') ? 'crew' : 'passengers';

    // Get the cargo entry
    const cargo = duplicate(this.actor.data.data.cargo[property]);
    const entry = cargo[idx];
    if (!entry) return null;

    // Update the cargo value
    const key = target.dataset.property || 'name';
    const type = target.dataset.dtype;
    let value = target.value;
    if (type === 'Number') value = Number(value);
    entry[key] = value;

    // Perform the Actor update
    return this.actor.update({[`data.cargo.${property}`]: cargo});
  }

  /* -------------------------------------------- */

  /**
   * Handle editing certain values like quantity, price, and weight in-sheet.
   * @param event {Event}
   * @returns {Promise<Item>}
   * @private
   */
  _onEditInSheet(event) {
    event.preventDefault();
    const itemID = event.currentTarget.closest('.item').dataset.itemId;
    const item = this.actor.items.get(itemID);
    const property = event.currentTarget.dataset.property;
    const type = event.currentTarget.dataset.dtype;
    let value = event.currentTarget.value;
    switch (type) {
      case 'Number': value = parseInt(value); break;
      case 'Boolean': value = value === 'true'; break;
    }
    return item.update({[`${property}`]: value});
  }

  /* -------------------------------------------- */

  /**
   * Handle creating a new crew or passenger row.
   * @param event {Event}
   * @returns {Promise<Actor|Item>}
   * @private
   */
  _onItemCreate(event) {
    event.preventDefault();
    const target = event.currentTarget;
    const type = target.dataset.type;
    if (type === 'crew' || type === 'passengers') {
      const cargo = duplicate(this.actor.data.data.cargo[type]);
      cargo.push(this.constructor.newCargo);
      return this.actor.update({[`data.cargo.${type}`]: cargo});
    }
    return super._onItemCreate(event);
  }

  /* -------------------------------------------- */

  /**
   * Handle deleting a crew or passenger row.
   * @param event {Event}
   * @returns {Promise<Actor|Item>}
   * @private
   */
  _onItemDelete(event) {
    event.preventDefault();
    const row = event.currentTarget.closest('.item');
    if (row.classList.contains('cargo-row')) {
      const idx = Number(row.dataset.itemId);
      const type = row.classList.contains('crew') ? 'crew' : 'passengers';
      const cargo = duplicate(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx);
      return this.actor.update({[`data.cargo.${type}`]: cargo});
    }

    return super._onItemDelete(event);
  }

  /* -------------------------------------------- */

  /**
   * Special handling for editing HP to clamp it within appropriate range.
   * @param event {Event}
   * @returns {Promise<Item>}
   * @private
   */
  _onHPChange(event) {
    event.preventDefault();
    const itemID = event.currentTarget.closest('.item').dataset.itemId;
    const item = this.actor.items.get(itemID);
    const hp = Math.clamped(0, parseInt(event.currentTarget.value), item.data.data.hp.max);
    event.currentTarget.value = hp;
    return item.update({'data.hp.value': hp});
  }

  /* -------------------------------------------- */

  /**
   * Handle toggling an item's crewed status.
   * @param event {Event}
   * @returns {Promise<Item>}
   * @private
   */
  _onToggleItem(event) {
    event.preventDefault();
    const itemID = event.currentTarget.closest('.item').dataset.itemId;
    const item = this.actor.items.get(itemID);
    const crewed = !!item.data.data.crewed;
    return item.update({'data.crewed': !crewed});
  }
};
