import { RollBasicParts } from "../roll/roll-basic-parts.mjs"

const ACTOR_EMBEDDED_DOCTYPES = ['Item', 'ActiveEffect']
/**
 * class designed to store actor modification instructions, to apply them in a single operation, and have the ability to revert these
 */
export class ActorImpacts {
  static $newDocumentImpacts(docType) {
    return { creates: [], deletes: [], updates: [], docType: docType }
  }
  static $checkDocType(docType) {
    if (!ACTOR_EMBEDDED_DOCTYPES.includes(docType)) {
      throw `Unsupported document type ${docType}`
    }
  }

  constructor(actorToken) {
    this.actorToken = actorToken
    this.updates = []
    this.deltas = []
    ACTOR_EMBEDDED_DOCTYPES.forEach(
      docType => this[docType] = ActorImpacts.$newDocumentImpacts(docType)
    )
  }

  addActorUpdate(path, value) {
    this.updates.push([path, value])
  }

  addActorDelta(path, value) {
    const intValue = Number.parseInt(value)
    if (Number.isInteger(intValue) && intValue != 0) {
      const delta = [path, intValue]
      this.deltas.push(delta)
    }
    else {
      console.error('Cannot use non integer value {} for delta update', value)
    }
  }

  addDeleted(docType, document) {
    ActorImpacts.$checkDocType(docType)
    this[docType].deletes.push(document)
  }

  addCreated(docType, document) {
    ActorImpacts.$checkDocType(docType)
    this[docType].creates.push(document)
  }

  addUpdate(docType, document, path, value) {
    ActorImpacts.$checkDocType(docType)
    const update = [path, value]
    const existing = this[docType].updates.find(it => it.id == document.id)
    if (existing) {
      existing.updates.push(update)
    }
    else {
      this[docType].updates.push({ id: document.id, updates: [update], deltas: [] })
    }
  }

  addDelta(document, path, value) {
    ActorImpacts.$checkDocType(document)
    const intValue = Number.parseInt(value)
    if (Number.isInteger(intValue)) {
      if (intValue != 0){
        const delta = [path, intValue]
        const existing = this[docType].updates.find(it => it.id == document.id)
        if (existing) {
          existing.deltas.push(delta)
        }
        else {
          this[docType].updates.push({ id: document.id, updates: [], deltas: [delta] })
        }
      }
    }
    else {
      console.error('Cannot use non-integer value {} for delta update', value)
    }
  }

  reverseImpacts() {
    const reverse = ActorImpacts.$computeReverts(new ActorImpacts(this.actorToken), this, __ => this.actorToken.actor)
    ACTOR_EMBEDDED_DOCTYPES.forEach(
      docType => {
        reverse[docType].creates = this[docType].deletes.map(it => foundry.utils.deepClone(it))
        reverse[docType].deletes = this[docType].creates.map(it => { return { id: it.id } })
        reverse[docType].updates = this[docType].updates.map(it => ActorImpacts.$computeReverts({ id: it.id }, it, id => this.$getEmbeddedDocument(docType, id)))
      }
    )
    return reverse
  }

  toStorable() {
    delete this.actorToken
    return this
  }

  async applyImpacts() {
    const actor = this.actorToken.actor
    for (let docType of ACTOR_EMBEDDED_DOCTYPES) {
      await this.$applyDocumentsImpacts(actor, docType)
    }
    const updates = ActorImpacts.$computeUpdates(this, id => actor)
    await actor.update(updates, { render: true })
  }


  async $applyDocumentsImpacts(actor, docType) {
    if (this[docType].deletes.length > 0) {
      const deletes = this[docType].deletes.map(it => it.id)
      await actor.deleteEmbeddedDocuments(docType, deletes, { render: false })
    }

    if (this[docType].creates.length > 0) {
      const creates = this[docType].creates
      const created = await actor.createEmbeddedDocuments(docType, creates, { render: false })
      for (let i = 0; i < creates.length; i++) {
        creates[i].createdId = created[i].id
      }
    }

    if (this[docType].updates.length > 0) {
      const updates = this[docType].updates.map(u => ActorImpacts.$computeUpdates(u, id => this.$getEmbeddedDocument(docType, id)))
      await actor.updateEmbeddedDocuments(docType, updates, { render: false })
    }
  }

  findCreatedId(docType, origId) {
    return this[docType].creates.find(it => it.id = origId)?.createdId
  }

  $getEmbeddedDocument(docType, id) {
    return this.actorToken.actor.getEmbeddedDocument(docType, id)
  }

  static $computeUpdates(u, getSource) {
    if (u.updates.length == 0 && u.deltas.length == 0) {
      return {}
    }
    const source = getSource(u.id)
    const instruction = { _id: u.id }
    u.updates.forEach(u => instruction[u[0]] = u[1])
    u.deltas.forEach(u => instruction[u[0]] = foundry.utils.getProperty(source, u[0]) + u[1])
    return instruction
  }

  static $computeReverts(target, u, getSource) {
    const source = getSource(u.id)
    target.updates = u.updates.map(u => [u[0], foundry.utils.getProperty(source, u[0])])
    target.deltas = u.deltas.map(d => [d[0], -d[1]])
    return target
  }
}
