import { onManageActiveEffect } from "../../system/effects.mjs"

const { HandlebarsApplicationMixin } = foundry.applications.api

/**
 * Fiche de base pour tous les acteurs Vermine 2047 (ApplicationV2).
 * Remplace VermineActorSheet (AppV1).
 */
export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {

  // ── Mode édition / jeu ──────────────────────────────────────────────

  static SHEET_MODES = { EDIT: 0, PLAY: 1 }

  _sheetMode = this.constructor.SHEET_MODES.PLAY

  get isPlayMode() { return this._sheetMode === this.constructor.SHEET_MODES.PLAY }
  get isEditMode() { return this._sheetMode === this.constructor.SHEET_MODES.EDIT }

  // ── Options par défaut ──────────────────────────────────────────────

  static DEFAULT_OPTIONS = {
    classes: ["vermine2047", "actor"],
    position: { width: 800, height: "auto" },
    form: { submitOnChange: true },
    window: { resizable: true },
    dragDrop: [{ dragSelector: ".item", dropSelector: null }],
    actions: {
      editImage: VermineBaseActorSheet.#onEditImage,
      toggleSheet: VermineBaseActorSheet.#onToggleSheet,
      edit: VermineBaseActorSheet.#onItemEdit,
      delete: VermineBaseActorSheet.#onItemDelete,
      toggleEquip: VermineBaseActorSheet.#onToggleEquip,
      create: VermineBaseActorSheet.#onItemCreate,
      createVehicle: VermineBaseActorSheet.#onCreateVehicle,
      editVehicle: VermineBaseActorSheet.#onEditVehicle,
      deleteVehicle: VermineBaseActorSheet.#onDeleteVehicle,
      roll: VermineBaseActorSheet.#onRollItem,
      attack: VermineBaseActorSheet.#onAttack,
      clickRadio: VermineBaseActorSheet.#onClickRadioHexa,
      clickWound: VermineBaseActorSheet.#onClickWound,
      effectControl: VermineBaseActorSheet.#onEffectControl,
      chooseTotem: VermineBaseActorSheet.#onChooseTotem
    }
  }

  // ── Drag & Drop ─────────────────────────────────────────────────────

  #dragDrop

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

  #createDragDropHandlers() {
    return this.options.dragDrop.map(d => {
      d.permissions = {
        dragstart: this._canDragStart.bind(this),
        drop: this._canDragDrop.bind(this)
      }
      d.callbacks = {
        dragover: this._onDragOver.bind(this),
        drop: this._onDrop.bind(this)
      }
      return new foundry.applications.ux.DragDrop.implementation(d)
    })
  }

  _canDragStart() { return this.isEditable }
  _canDragDrop() { return this.isEditable }

  // ── Soumission du formulaire ────────────────────────────────────────

  /** @override - coerce string values from HTML form inputs to numbers */
  _prepareSubmitData(event, form, formData, updateData) {
    const fd = foundry.utils.deepClone(formData.object)

    for (const [key, value] of Object.entries(fd)) {
      if (!key.startsWith("system.") || typeof value === "number") continue

      const segments = key.slice(7).split(".")
      let node = this.document.system.schema
      for (const seg of segments) {
        if (node instanceof foundry.data.fields.SchemaField) node = node.fields[seg]
        else { node = undefined; break }
      }
      if (!(node instanceof foundry.data.fields.NumberField)) continue

      // Handle arrays from duplicate-named form inputs
      let raw = Array.isArray(value) ? value.filter(v => v !== "" && v !== null).pop() : value
      if (raw === undefined) continue
      if (typeof raw === "string" && raw.trim() === "") { fd[key] = 0; continue }

      const num = Number(typeof raw === "string" ? raw.trim() : raw)
      if (!isNaN(num)) fd[key] = num
    }

    return fd
  }

  // ── Contexte commun ─────────────────────────────────────────────────

  async _prepareContext() {
    const enrich = async (path) => {
      const val = foundry.utils.getProperty(this.document.system, path);
      return val ? await foundry.applications.ux.TextEditor.implementation.enrichHTML(val, { async: true }) : "";
    };
    return {
      fields: this.document.schema.fields,
      systemFields: this.document.system.schema.fields,
      actor: this.document,
      system: this.document.system,
      source: this.document.toObject(),
      config: CONFIG.VERMINE,
      rollData: this.document.getRollData(),
      isGM: game.user.isGM,
      isEditMode: this.isEditMode,
      isPlayMode: this.isPlayMode,
      isEditable: this.isEditable,
      enrichedNotes: await enrich("identity.notes"),
      enrichedBiography: await enrich("identity.biography"),
      enrichedRelations: await enrich("identity.relations"),
      enrichedEquipmentNotes: await enrich("equipment.description")
    }
  }

  // ── Rendu ───────────────────────────────────────────────────────────

  async _onRoll(event) {
    event.preventDefault()
    const el = event.currentTarget
    const type = el.dataset.type
    const label = el.dataset.label
    if (!type || !label) return
    const { default: RollDialog } = await import("../../system/dialogs/rollDialog.mjs")
    const dialog = await RollDialog.create({
      actorId: this.document.id,
      rolltype: type,
      label
    })
    if (dialog) dialog.render(true)
  }

  // ── Actions ─────────────────────────────────────────────────────────

  _onRender(context, options) {
    super._onRender(context, options)
    // Activate initial tabs (force to bypass changeTab's early-return when the
    // tab is already set as active in tabGroups — Foundry v12 doesn't call
    // changeTab on initial render, so the active class is never applied)
    for (const [group, tab] of Object.entries(this.tabGroups ?? {})) {
      this.changeTab(tab, group, {force: true})
    }
    // Move toggle from hidden main tab to visible position (only for sheets where
    // .tab.main is not already displayed as a permanent sidebar via !important)
    const mainTab = this.element.querySelector(".tab.main")
    const tabs = this.element.querySelector('nav.tabs[data-application-part="tabs"]')
    if ( mainTab && tabs && getComputedStyle(mainTab).display === "none" ) {
      const existing = tabs.parentNode.querySelector('.sheet-header-toggle[data-moved]')
      if (existing) existing.remove()
      const toggle = mainTab.querySelector(".sheet-header-toggle")
      if (toggle) {
        toggle.dataset.moved = "true"
        tabs.parentNode.insertBefore(toggle, tabs)
      }
    }
    this.#dragDrop.forEach(d => d.bind(this.element))
    this.element.querySelectorAll(".rollable").forEach(el => {
      el.addEventListener("click", this._onRoll.bind(this))
    })
    // Auto-fill empty number inputs on change to prevent validation errors
    this.element.addEventListener("change", e => {
      const input = e.target
      if (input?.type === "number" && !input.value && input.name && input !== document.activeElement) {
        input.value = "0"
      }
    }, { capture: true })
  }

  /** @override */
  async _onDropItem(event, item) {
    const doc = item instanceof foundry.abstract.Document ? item : await fromUuid(item.uuid)
    if (!doc) return
    const itemData = doc.toObject()
    await this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
  }

  static #onToggleSheet() {
    const modes = this.constructor.SHEET_MODES
    this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
    this.render()
  }

  static async #onEditImage(event, target) {
    const attr = target.dataset.edit ?? "img"
    const current = foundry.utils.getProperty(this.document, attr)
    const fp = new FilePicker({
      current,
      type: "image",
      callback: (path) => this.document.update({ [attr]: path }),
      top: this.position.top + 40,
      left: this.position.left + 10
    })
    return fp.browse()
  }

  static async #onItemEdit(event, target) {
    const id = target.closest("[data-item-id]")?.dataset?.itemId
    const uuid = target.closest("[data-item-uuid]")?.dataset?.itemUuid
    let item
    if (uuid) item = await fromUuid(uuid)
    if (!item) item = this.document.items.get(id)
    item?.sheet.render(true)
  }

  static async #onItemDelete(event, target) {
    const itemUuid = target.closest("[data-item-uuid]")?.dataset?.itemUuid
    if (itemUuid) {
      const item = await fromUuid(itemUuid)
      await item?.deleteDialog()
      return
    }
    const id = target.closest("[data-item-id]")?.dataset?.itemId
    const item = this.document.items.get(id)
    await item?.deleteDialog()
  }

  static async #onItemCreate(event, target) {
    const type = target.dataset.type
    if (!type) return
    const name = game.i18n.localize("ITEMS.new_" + type)
    await this.document.createEmbeddedDocuments("Item", [{ name, type }])
  }

  // ── Véhicules (acteurs) ──────────────────────────────────────────────

  /** Crée un acteur "vehicle" rattaché à cet acteur (groupe ou personnage). */
  static async #onCreateVehicle(event, target) {
    const name = game.i18n.localize("ITEMS.new_vehicle")
    const vehicle = await Actor.create({
      name,
      type: "vehicle",
      system: { ownerId: this.document.id },
      // Le véhicule est un bien partagé : reprendre la propriété du propriétaire
      ownership: foundry.utils.deepClone(this.document.ownership)
    })
    vehicle?.sheet.render(true)
  }

  static async #onEditVehicle(event, target) {
    const id = target.closest("[data-vehicle-id]")?.dataset?.vehicleId
    const vehicle = game.actors.get(id)
    vehicle?.sheet.render(true)
  }

  static async #onDeleteVehicle(event, target) {
    const id = target.closest("[data-vehicle-id]")?.dataset?.vehicleId
    const vehicle = game.actors.get(id)
    await vehicle?.deleteDialog()
  }

  static async #onRollItem(event, target) {
    const id = target.closest("[data-item-id]")?.dataset?.itemId
    if (!id) return
    const item = this.document.items.get(id)
    if (!item) return
    // Armes : PNJ et créatures → dialogue de combat (valeur d'Attaque) ;
    // personnage → dialogue de compétence. PNJ/créatures n'ont ni
    // caractéristiques ni compétences.
    if (item.type === "weapon" && (this.document.type === 'npc' || this.document.type === 'creature')) {
      const { default: CombatDialog } = await import("../../system/dialogs/combatDialog.mjs")
      const dialog = await CombatDialog.create({ actorId: this.document.id, weapon: item })
      if (dialog) dialog.render(true)
      return
    }
    if (item.type === "weapon" && this.document.system.abilities && this.document.system.skills) {
      const { default: RollDialog } = await import("../../system/dialogs/rollDialog.mjs")
      const dialog = await RollDialog.create({
        actorId: this.document.id,
        rolltype: "skill",
        label: item.system.skill,
        weapon: item
      })
      if (dialog) dialog.render(true)
      return
    }
    item?.roll()
  }

  static async #onAttack(event, target) {
    const id = target.closest("[data-item-id]")?.dataset?.itemId
    if (!id) return
    const item = this.document.items.get(id)
    if (!item) return
    const { default: CombatDialog } = await import("../../system/dialogs/combatDialog.mjs")
    const dialog = await CombatDialog.create({ actorId: this.document.id, weapon: item })
    if (dialog) dialog.render(true)
  }

  static async #onToggleEquip(event, target) {
    event.preventDefault()
    event.stopPropagation()
    const id = target.closest("[data-item-id]")?.dataset?.itemId
    if (!id) return
    const item = this.document.items.get(id)
    if (!item) return
    await item.update({ "system.equipped": !item.system.equipped })
  }

  static #onClickRadioHexa(event, target) {
    event.preventDefault()
    event.stopPropagation()
    // Évite une erreur de permission pour un utilisateur non éditeur.
    if (!this.isEditable) {
      ui.notifications.warn(game.i18n.localize("VERMINE.error_not_editable"))
      return
    }
    const input = target
    const update = {}
    let current = this.document
    const propTree = input.name.split(".")
    for (const prop of propTree) {
      current = current[prop]
    }
    if (current != input.value) {
      update[input.name] = parseInt(input.value)
    } else {
      update[input.name] = parseInt(input.value) - 1
    }
    this.document.update(update)
  }

  /**
   * Coche/décoche les cases de Blessure en respectant l'ordre de gravité
   * (règles p.39) : on ne peut cocher que la case suivante dans l'ordre
   * (une blessure s'applique toujours au Seuil égal ou inférieur le plus
   * élevé), et on ne peut pas en sauter. Décocher une case retire toutes
   * celles situées après elle.
   */
  static #onClickWound(event, target) {
    event.preventDefault()
    event.stopPropagation()
    const input = target
    const box = parseInt(input.value, 10) || 0 // numéro de case (1-based)
    let current = this.document
    const propTree = input.name.split(".")
    for (const prop of propTree) {
      current = current[prop]
    }
    const currentValue = parseInt(current, 10) || 0
    let next = currentValue
    if (box === currentValue + 1) {
      // Cocher la case suivante dans l'ordre.
      next = box
    } else if (box <= currentValue) {
      // Décocher : retire la case cliquée et toutes celles après.
      next = box - 1
    }
    // Si box > currentValue + 1 : on ne permet pas de sauter des cases.
    this.document.update({ [input.name]: next })
  }

  static #onEffectControl(event, target) {
    onManageActiveEffect(event, this.document)
  }

  static async #onChooseTotem(event, target) {
    const { TotemPicker } = await import("../../system/applications.mjs")
    new TotemPicker(target, this.document).render(true)
  }
}
