const { HandlebarsApplicationMixin } = foundry.applications.api

export default class PrismRPGItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
  /**
   * 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: ["prismrpg", "item"],
    position: {
      width: 600,
      height: "auto",
    },
    form: {
      submitOnChange: true,
    },
    window: {
      resizable: true,
    },
    dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
    actions: {
      toggleSheet: PrismRPGItemSheet.#onToggleSheet,
      editImage: PrismRPGItemSheet.#onEditImage,
      "create-effect": PrismRPGItemSheet.#onCreateActiveEffect,
      "effect-edit": PrismRPGItemSheet.#onEffectEdit,
      "effect-delete": PrismRPGItemSheet.#onEffectDelete,
    },
  }

  /**
   * 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() {
    let context = await super._prepareContext()
    context.fields = this.document.schema.fields
    context.systemFields = this.document.system.schema.fields
    context.item = this.document
    context.system = this.document.system
    context.source = this.document.toObject()
    context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
    context.effectCategories = this.#prepareActiveEffectCategories(this.document.effects)
    context.isEditMode = this.isEditMode
    context.isPlayMode = this.isPlayMode
    context.isEditable = this.isEditable
    return context
  }

  /** @override */
  _onRender(context, options) {
    super._onRender(context, options)
    this.#dragDrop.forEach((d) => d.bind(this.element))

    // Activate tab navigation
    const nav = this.element.querySelector('nav.tabs[data-group]')
    if (nav) {
      const group = nav.dataset.group
      nav.querySelectorAll('[data-tab]').forEach(link => {
        link.addEventListener('click', (event) => {
          event.preventDefault()
          const tab = event.currentTarget.dataset.tab
          this.tabGroups[group] = tab
          this.render()
        })
      })
    }
  }

  // #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)
    })
  }

  /**
   * 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) {
    const el = event.currentTarget
    if ("link" in event.target.dataset) return

    // Extract the data you need
    let dragData = null

    if (!dragData) return

    // Set data transfer
    event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
  }

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

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

  // #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.
   *
   * @this PrismRPGCharacterSheet
   * @param {PointerEvent} event   The originating click event
   * @param {HTMLElement} target   The capturing HTML element which defined a [data-action]
   * @returns {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()
  }

  /**
   * Prepare Active Effects organized by category (temporary, passive, inactive).
   * @param {ActiveEffect[]} effects    The raw Active Effects collection
   * @returns {object}                  The categorized effects
   * @private
   */
  #prepareActiveEffectCategories(effects) {
    // Define effect header categories
    const categories = {
      temporary: {
        type: "temporary",
        label: game.i18n.localize("PRISMRPG.Label.temporary"),
        effects: [],
      },
      passive: {
        type: "passive",
        label: game.i18n.localize("PRISMRPG.Label.passive"),
        effects: [],
      },
      inactive: {
        type: "inactive",
        label: game.i18n.localize("PRISMRPG.Label.inactive"),
        effects: [],
      },
    }

    // Iterate over active effects, classifying them into categories
    for (let e of effects) {
      const effect = e.toObject()
      if (e.disabled) categories.inactive.effects.push(effect)
      else if (e.isTemporary) categories.temporary.effects.push(effect)
      else categories.passive.effects.push(effect)
    }
    return categories
  }

  /**
   * Handle creating a new Active Effect on the Item.
   * @param {Event} event             The initiating click event.
   * @param {HTMLElement} target      The current target of the event listener.
   * @private
   */
  static async #onCreateActiveEffect(event, target) {
    const effectType = target.dataset.effectType
    let durationValue = undefined
    let disabled = false

    if (effectType === "temporary") {
      durationValue = 10
    }
    if (effectType === "inactive") {
      disabled = true
    }

    const effectData = {
      name: game.i18n.format("DOCUMENT.New", { type: game.i18n.localize("DOCUMENT.ActiveEffect") }),
      img: "icons/svg/aura.svg",
      origin: this.document.uuid,
      disabled: disabled,
      changes: [],
      duration: durationValue !== undefined ? { rounds: durationValue } : {},
      flags: {}
    }
    await this.document.createEmbeddedDocuments("ActiveEffect", [effectData])
  }

  /**
   * Handle editing an Active Effect on the Item.
   * @param {Event} event             The initiating click event.
   * @param {HTMLElement} target      The current target of the event listener.
   * @private
   */
  static async #onEffectEdit(event, target) {
    const li = target.closest(".item")
    const effectId = li.dataset.itemId
    const effect = this.document.effects.get(effectId)
    if (!effect) return
    effect.sheet.render(true)
  }

  /**
   * Handle deleting an Active Effect from the Item.
   * @param {Event} event             The initiating click event.
   * @param {HTMLElement} target      The current target of the event listener.
   * @private
   */
  static async #onEffectDelete(event, target) {
    const li = target.closest(".item")
    const effectId = li.dataset.itemId
    const effect = this.document.effects.get(effectId)
    if (!effect) return
    await effect.delete()
  }
  // #endregion
}
