import { EcrymeUtility } from "../common/ecryme-utility.js";

const { HandlebarsApplicationMixin } = foundry.applications.api

/**
 * Main confrontation dialog — Application V2 version.
 * Features drag-and-drop of dice from the pool to execution/preservation slots.
 * All event listeners (change + drag-drop) are bound once via _listenersAdded guard.
 */
export class EcrymeConfrontDialog extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) {

  #dragDrop
  _listenersAdded = false

  /* -------------------------------------------- */
  constructor(actor, rollData, options = {}) {
    super(options)
    this.actor        = actor
    this.rollData     = rollData
    this.buttonDisabled = true
    this.#dragDrop    = this.#createDragDropHandlers()
  }

  /* -------------------------------------------- */
  /** @override */
  static DEFAULT_OPTIONS = {
    classes: ["fvtt-ecryme", "ecryme-confrontation-dialog"],
    position: { width: 640 },
    window: { title: "ECRY.ui.confront" },
    dragDrop: [{ dragSelector: ".confront-dice-container", dropSelector: null }],
    actions: {
      launchConfront: EcrymeConfrontDialog.#onLaunchConfront,
      cancel:         EcrymeConfrontDialog.#onCancel,
    },
  }

  /** @override */
  static PARTS = {
    content: { template: "systems/fvtt-ecryme/templates/dialogs/confront-dialog.hbs" },
  }

  /* -------------------------------------------- */
  static async create(actor, rollData) {
    return new EcrymeConfrontDialog(actor, rollData)
  }

  /* -------------------------------------------- */
  async _prepareContext() {
    return {
      ...this.rollData,
      config:         game.ecryme.config,
      buttonDisabled: this.buttonDisabled,
    }
  }

  /* -------------------------------------------- */
  /** Bind drag-drop and form-change listeners once; re-bind DragDrop on each render. */
  _onRender(context, options) {
    // DragDrop must be re-bound each render because the DOM is replaced
    this.#dragDrop.forEach(d => d.bind(this.element))

    // Form-change listener is bound once (event delegation survives DOM re-renders)
    if (!this._listenersAdded) {
      this._listenersAdded = true
      this.element.addEventListener('change', this.#onFormChange.bind(this))
    }
  }

  /* -------------------------------------------- */
  #onFormChange(event) {
    const target = event.target
    switch (target.id) {
      case 'bonusMalusPerso':
        this.rollData.bonusMalusPerso = Number(target.value)
        this.computeTotals()
        break
      case 'roll-specialization':
        this.rollData.selectedSpecs = Array.from(target.selectedOptions).map(o => o.value)
        this.computeTotals()
        break
      case 'roll-trait-bonus':
        this.rollData.traitsBonusSelected = Array.from(target.selectedOptions).map(o => o.value)
        this.computeTotals()
        break
      case 'roll-trait-malus':
        this.rollData.traitsMalusSelected = Array.from(target.selectedOptions).map(o => o.value)
        this.computeTotals()
        break
      case 'roll-select-transcendence':
        this.rollData.skillTranscendence = Number(target.value)
        this.computeTotals()
        break
      case 'roll-apply-transcendence':
        this.rollData.applyTranscendence = target.value
        this.computeTotals()
        break
      case 'annency-bonus':
        this.rollData.annencyBonus = Number(target.value)
        break
    }
  }

  // #region Drag-and-Drop

  #createDragDropHandlers() {
    return this.options.dragDrop.map(d => {
      d.permissions = {
        dragstart: () => true,
        drop:      () => true,
      }
      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)
    })
  }

  /* -------------------------------------------- */
  _onDragStart(event) {
    const target   = event.target
    const dragType = target.dataset.dragType
    let diceData

    if (dragType === "dice") {
      diceData = {
        dragType:  "dice",
        diceIndex: target.dataset.diceIdx,
        diceValue: target.dataset.diceValue,
      }
    } else {
      diceData = {
        dragType:   "bonus",
        bonusIndex: target.dataset.bonusIdx,
        bonusValue: 1,
      }
    }
    event.dataTransfer.setData("text/plain", JSON.stringify(diceData))
  }

  /* -------------------------------------------- */
  _onDragOver(event) {
    event.preventDefault()
  }

  /* -------------------------------------------- */
  _onDrop(event) {
    let data
    try { data = JSON.parse(event.dataTransfer.getData("text/plain")) }
    catch (e) { return }

    // Walk up the DOM to find a meaningful drop area
    const executionArea   = event.target.closest('.confront-execution-area')
    const preservationArea = event.target.closest('.confront-preservation-area')
    const diceList        = event.target.closest('.confrontation-dice-list')
    const bonusList       = event.target.closest('.confrontation-bonus-list')

    if (data.dragType === "dice") {
      const idx = Number(data.diceIndex)
      if (executionArea && this.rollData.availableDices.filter(d => d.location === "execution").length < 2) {
        this.rollData.availableDices[idx].location = "execution"
      } else if (preservationArea && this.rollData.availableDices.filter(d => d.location === "preservation").length < 2) {
        this.rollData.availableDices[idx].location = "preservation"
      } else if (diceList) {
        this.rollData.availableDices[idx].location = "mainpool"
      }

      const execCount  = this.rollData.availableDices.filter(d => d.location === "execution").length
      const presCount  = this.rollData.availableDices.filter(d => d.location === "preservation").length
      this.buttonDisabled = !(execCount === 2 && presCount === 2)

    } else if (data.dragType === "bonus") {
      const idx = Number(data.bonusIndex)
      if (executionArea)   this.rollData.confrontBonus[idx].location = "execution"
      else if (preservationArea) this.rollData.confrontBonus[idx].location = "preservation"
      else if (bonusList)  this.rollData.confrontBonus[idx].location = "mainpool"
    }

    this.computeTotals()
  }

  // #endregion

  /* -------------------------------------------- */
  processTranscendence() {
    if (this.rollData.skillTranscendence > 0) {
      if (this.rollData.applyTranscendence === "execution") {
        this.rollData.executionTotal   += Number(this.rollData.skillTranscendence)
      } else {
        this.rollData.preservationTotal += Number(this.rollData.skillTranscendence)
      }
    }
  }

  /* -------------------------------------------- */
  computeTotals() {
    const rollData = this.rollData
    const actor    = game.actors.get(rollData.actorId)

    rollData.executionTotal = rollData.availableDices
      .filter(d => d.location === "execution")
      .reduce((acc, d) => acc + d.result, rollData.skill.value)
    rollData.executionTotal = rollData.confrontBonus
      .filter(d => d.location === "execution")
      .reduce((acc) => acc + 1, rollData.executionTotal)

    rollData.preservationTotal = rollData.availableDices
      .filter(d => d.location === "preservation")
      .reduce((acc, d) => acc + d.result, rollData.skill.value)
    rollData.preservationTotal = rollData.confrontBonus
      .filter(d => d.location === "preservation")
      .reduce((acc) => acc + 1, rollData.preservationTotal)

    this.processTranscendence()

    // Specialization
    if (rollData.selectedSpecs?.length > 0) {
      rollData.spec      = foundry.utils.deepClone(actor.getSpecialization(rollData.selectedSpecs[0]))
      rollData.specApplied = true
      rollData.executionTotal   += 2
      rollData.preservationTotal += 2
    }
    if (rollData.specApplied && rollData.selectedSpecs?.length === 0) {
      rollData.spec      = undefined
      rollData.specApplied = false
    }

    // Traits bonus/malus
    rollData.bonusMalusTraits = 0
    for (const t of rollData.traitsBonus)  t.activated = false
    for (const t of rollData.traitsMalus)  t.activated = false

    for (const id of (rollData.traitsBonusSelected ?? [])) {
      const trait = rollData.traitsBonus.find(t => t.id === id)
      if (trait) { trait.activated = true; rollData.bonusMalusTraits += Number(trait.system.level) }
    }
    for (const id of (rollData.traitsMalusSelected ?? [])) {
      const trait = rollData.traitsMalus.find(t => t.id === id)
      if (trait) { trait.activated = true; rollData.bonusMalusTraits -= Number(trait.system.level) }
    }

    rollData.executionTotal   += Number(rollData.bonusMalusTraits) + Number(rollData.bonusMalusPerso)
    rollData.preservationTotal += Number(rollData.bonusMalusTraits) + Number(rollData.bonusMalusPerso)

    this.render()
  }

  /* -------------------------------------------- */
  async launchConfront() {
    const msg = await EcrymeUtility.createChatMessage(this.rollData.alias, "blindroll", {
      content: await foundry.applications.handlebars.renderTemplate(
        `systems/fvtt-ecryme/templates/chat/chat-confrontation-pending.hbs`, this.rollData
      ),
    })
    EcrymeUtility.blindMessageToGM({
      rollData: this.rollData,
      template: "systems/fvtt-ecryme/templates/chat/chat-confrontation-pending.hbs",
    })
    msg.setFlag("world", "ecryme-rolldata", this.rollData)
  }

  /* -------------------------------------------- */
  static async #onLaunchConfront(event, target) {
    await this.launchConfront()
    this.close()
  }

  static #onCancel(event, target) {
    this.close()
  }
}
