const { HandlebarsApplicationMixin } = foundry.applications.api

export default class AwEActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
  /**
   * Different sheet modes.
   * @enum {number}
   */
  static SHEET_MODES = { EDIT: 0, PLAY: 1 }

  constructor(options = {}) {
    super(options)
    this.#dragDrop = this.#createDragDropHandlers()
  }

  #dragDrop

  /** @override */
  static DEFAULT_OPTIONS = {
    classes: ["awemmy", "actor"],
    position: {
      width: 900,
      height: "auto"
    },
    form: {
      submitOnChange: true
    },
    window: {
      resizable: true
    },
    dragDrop: [{ dragSelector: '[data-drag="true"], .rollable', dropSelector: null }],
    actions: {
      editImage: AwEActorSheet.#onEditImage,
      toggleSheet: AwEActorSheet.#onToggleSheet,
      edit: AwEActorSheet.#onItemEdit,
      delete: AwEActorSheet.#onItemDelete
    }
  }

  /**
   * The current sheet mode.
   * @type {number}
   */
  _sheetMode = this.constructor.SHEET_MODES.PLAY

  /**
   * Is the sheet currently in 'Play' mode?
   * @type {boolean}
   */
  get isPlayMode() {
    return this._sheetMode === this.constructor.SHEET_MODES.PLAY
  }

  /**
   * Is the sheet currently in 'Edit' mode?
   * @type {boolean}
   */
  get isEditMode() {
    return this._sheetMode === this.constructor.SHEET_MODES.EDIT
  }

  /** @override */
  async _prepareContext() {
    const context = {
      fields: this.document.schema.fields,
      systemFields: this.document.system.schema.fields,
      actor: this.document,
      system: this.document.system,
      source: this.document.toObject(),
      isEditMode: this.isEditMode,
      isPlayMode: this.isPlayMode,
      isEditable: this.isEditable
    }
    return context
  }

  /** @override */
  _onRender(context, options) {
    this.#dragDrop.forEach(d => d.bind(this.element))
    const rollables = this.element.querySelectorAll(".rollable")
    rollables.forEach(d => d.addEventListener("click", this._onRoll.bind(this)))
  }

  /**
   * Handle rolling an attribute check.
   * @param {PointerEvent} event - The click event.
   */
  async _onRoll(event) {
    if (this.isEditMode) return
    const attributeId = event.currentTarget.dataset.attributeId
    if (!attributeId) return
    await this.document.rollAttribute(attributeId)
  }

  // #region Drag-and-Drop Workflow
  /**
   * Create drag-and-drop workflow handlers for this Application.
   * @returns {DragDrop[]} An array of DragDrop handlers.
   * @private
   */
  #createDragDropHandlers() {
    return this.options.dragDrop.map(d => {
      d.permissions = {
        dragstart: this._canDragStart.bind(this),
        drop: this._canDragDrop.bind(this)
      }
      d.callbacks = {
        dragstart: this._onDragStart.bind(this),
        dragover: this._onDragOver.bind(this),
        drop: this._onDrop.bind(this)
      }
      return new foundry.applications.ux.DragDrop.implementation(d)
    })
  }

  /**
   * Callback actions which occur when a dragged element is dropped on a target.
   * @param {DragEvent} event - The originating DragEvent.
   * @protected
   */
  async _onDrop(event) {}

  /**
   * Define whether a user is able to begin a dragstart workflow for a given drag selector.
   * @param {string} selector - The candidate HTML selector for dragging.
   * @returns {boolean} Can the current user drag this selector?
   * @protected
   */
  _canDragStart(selector) {
    return this.isEditable
  }

  /**
   * Define whether a user is able to conclude a drag-and-drop workflow for a given drop selector.
   * @param {string} selector - The candidate HTML selector for the drop target.
   * @returns {boolean} Can the current user drop on this selector?
   * @protected
   */
  _canDragDrop(selector) {
    return this.isEditable && this.document.isOwner
  }

  /**
   * Callback actions which occur at the beginning of a drag start workflow.
   * @param {DragEvent} event - The originating DragEvent.
   * @protected
   */
  _onDragStart(event) {
    if ("link" in event.target.dataset) return
    const el = event.currentTarget
    const itemId = el.dataset.itemId
    const itemUuid = el.dataset.itemUuid
    if (!itemUuid && !itemId) return
    const item = itemUuid
      ? fromUuidSync(itemUuid)
      : this.document.items.get(itemId)
    if (!item) return
    event.dataTransfer.setData("text/plain", JSON.stringify({ type: "Item", uuid: item.uuid }))
  }

  /**
   * Callback actions which occur when a dragged element is over a drop target.
   * @param {DragEvent} event - The originating DragEvent.
   * @protected
   */
  _onDragOver(event) {}

  /**
   * Drop an item onto the actor.
   * @param {Item} item - The item being dropped.
   */
  async _onDropItem(item) {
    const itemData = item.toObject()
    await this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
  }

  // #endregion

  // #region Actions
  /**
   * Handle toggling between Edit and Play mode.
   * @param {Event} event - The initiating click event.
   * @param {HTMLElement} target - The current target of the event listener.
   */
  static #onToggleSheet(event, target) {
    const modes = this.constructor.SHEET_MODES
    this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
    this.render()
  }

  /**
   * Handle changing a Document's image.
   * @param {PointerEvent} event - The originating click event.
   * @param {HTMLElement} target - The capturing HTML element which defined a [data-action].
   * @returns {Promise} The file picker promise.
   * @private
   */
  static async #onEditImage(event, target) {
    const attr = target.dataset.edit
    const current = foundry.utils.getProperty(this.document, attr)
    const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}
    const fp = new FilePicker({
      current,
      type: "image",
      redirectToRoot: img ? [img] : [],
      callback: path => {
        this.document.update({ [attr]: path })
      },
      top: this.position.top + 40,
      left: this.position.left + 10
    })
    return fp.browse()
  }

  /**
   * Edit an existing item within the Actor.
   * @param {PointerEvent} event - The originating click event.
   * @param {HTMLElement} target - The capturing HTML element which defined a [data-action].
   */
  static async #onItemEdit(event, target) {
    const id = target.getAttribute("data-item-id")
    const uuid = target.getAttribute("data-item-uuid")
    let item = await fromUuid(uuid)
    if (!item) item = this.document.items.get(id)
    if (!item) return
    item.sheet.render(true)
  }

  /**
   * Delete an existing item within the Actor.
   * @param {PointerEvent} event - The originating click event.
   * @param {HTMLElement} target - The capturing HTML element which defined a [data-action].
   */
  static async #onItemDelete(event, target) {
    const itemUuid = target.getAttribute("data-item-uuid")
    const item = await fromUuid(itemUuid)
    await item.deleteDialog()
  }

  // #endregion
}
