import { RemoveMarkStep } from '@tiptap/pm/transform'

import { Extension } from '../Extension.js'
import { combineTransactionSteps, getChangedRanges } from '../helpers/index.js'

/**
 * This extension allows you to be notified when the user deletes content you are interested in.
 */
export const Delete = Extension.create({
  name: 'delete',

  onUpdate({ transaction, appendedTransactions }) {
    const callback = () => {
      if (
        this.editor.options.coreExtensionOptions?.delete?.filterTransaction?.(transaction) ??
        transaction.getMeta('y-sync$')
      ) {
        return
      }
      const nextTransaction = combineTransactionSteps(transaction.before, [
        transaction,
        ...appendedTransactions,
      ])
      const changes = getChangedRanges(nextTransaction)

      changes.forEach(change => {
        if (
          nextTransaction.mapping.mapResult(change.oldRange.from).deletedAfter &&
          nextTransaction.mapping.mapResult(change.oldRange.to).deletedBefore
        ) {
          nextTransaction.before.nodesBetween(
            change.oldRange.from,
            change.oldRange.to,
            (node, from) => {
              const to = from + node.nodeSize - 2
              const isFullyWithinRange = change.oldRange.from <= from && to <= change.oldRange.to

              this.editor.emit('delete', {
                type: 'node',
                node,
                from,
                to,
                newFrom: nextTransaction.mapping.map(from),
                newTo: nextTransaction.mapping.map(to),
                deletedRange: change.oldRange,
                newRange: change.newRange,
                partial: !isFullyWithinRange,
                editor: this.editor,
                transaction,
                combinedTransform: nextTransaction,
              })
            },
          )
        }
      })

      const mapping = nextTransaction.mapping
      nextTransaction.steps.forEach((step, index) => {
        if (step instanceof RemoveMarkStep) {
          const newStart = mapping.slice(index).map(step.from, -1)
          const newEnd = mapping.slice(index).map(step.to)
          const oldStart = mapping.invert().map(newStart, -1)
          const oldEnd = mapping.invert().map(newEnd)

          const foundBeforeMark =
            newStart > 0
              ? nextTransaction.doc.nodeAt(newStart - 1)?.marks.some(mark => mark.eq(step.mark))
              : false
          const foundAfterMark = nextTransaction.doc
            .nodeAt(newEnd)
            ?.marks.some(mark => mark.eq(step.mark))

          this.editor.emit('delete', {
            type: 'mark',
            mark: step.mark,
            from: step.from,
            to: step.to,
            deletedRange: {
              from: oldStart,
              to: oldEnd,
            },
            newRange: {
              from: newStart,
              to: newEnd,
            },
            partial: Boolean(foundAfterMark || foundBeforeMark),
            editor: this.editor,
            transaction,
            combinedTransform: nextTransaction,
          })
        }
      })
    }

    if (this.editor.options.coreExtensionOptions?.delete?.async ?? true) {
      setTimeout(callback, 0)
    } else {
      callback()
    }
  },
})
