/**
 * This entire file was coppied from the following github repo: https://github.com/4teamwork/cypress-drag-drop/blob/master/index.js
 *
 * Full credit to the "cypress-drag-drop" package...
 */

const dataTransfer = new DataTransfer()

function omit(object = {}, keys = []) {
    return Object.entries(object).reduce((accum, [key, value]) => (key in keys ? accum : { ...accum, [key]: value }), {})
}

function isAttached(element) {
    return !!element.closest('html')
}

const DragSimulator = {
    MAX_TRIES: 5,
    DELAY_INTERVAL_MS: 10,
    counter: 0,
    targetElement: null,
    rectsEqual(r1, r2) {
        return r1.top === r2.top && r1.right === r2.right && r1.bottom === r2.bottom && r1.left === r2.left
    },
    createDefaultOptions(options) {
        const commonOptions = omit(options, ['source', 'target'])
        const source = { ...commonOptions, ...options.source }
        const target = { ...commonOptions, ...options.target }
        return { source, target }
    },
    get dropped() {
        const currentSourcePosition = this.source.getBoundingClientRect()
        return !this.rectsEqual(this.initialSourcePosition, currentSourcePosition)
    },
    get hasTriesLeft() {
        return this.counter < this.MAX_TRIES
    },
    set target(target) {
        this.targetElement = target
    },
    get target() {
        return cy.wrap(this.targetElement)
    },
    dragstart(clientPosition = {}) {
        return cy
            .wrap(this.source)
            .trigger('pointerdown', {
                which: 1,
                button: 0,
                ...clientPosition,
                eventConstructor: 'PointerEvent',
                ...this.options.source,
            })
            .trigger('mousedown', {
                which: 1,
                button: 0,
                ...clientPosition,
                eventConstructor: 'MouseEvent',
                ...this.options.source,
            })
            .trigger('dragstart', { dataTransfer, eventConstructor: 'DragEvent', ...this.options.source })
    },
    drop(clientPosition = {}) {
        return this.target
            .trigger('drop', {
                dataTransfer,
                eventConstructor: 'DragEvent',
                ...this.options.target,
            })
            .then(() => {
                if (isAttached(this.targetElement)) {
                    this.target
                        .trigger('mouseup', {
                            which: 1,
                            button: 0,
                            ...clientPosition,
                            eventConstructor: 'MouseEvent',
                            ...this.options.target,
                        })
                        .then(() => {
                            if (isAttached(this.targetElement)) {
                                this.target.trigger('pointerup', {
                                    which: 1,
                                    button: 0,
                                    ...clientPosition,
                                    eventConstructor: 'PointerEvent',
                                    ...this.options.target,
                                })
                            }
                        })
                }
            })
    },
    dragover(clientPosition = {}) {
        if (!this.counter || (!this.dropped && this.hasTriesLeft)) {
            this.counter += 1
            return this.target
                .trigger('dragover', {
                    dataTransfer,
                    eventConstructor: 'DragEvent',
                    ...this.options.target,
                })
                .trigger('mousemove', {
                    ...this.options.target,
                    ...clientPosition,
                    eventConstructor: 'MouseEvent',
                })
                .trigger('pointermove', {
                    ...this.options.target,
                    ...clientPosition,
                    eventConstructor: 'PointerEvent',
                })
                .wait(this.DELAY_INTERVAL_MS)
                .then(() => this.dragover(clientPosition))
        }
        if (!this.dropped) {
            console.error(`Exceeded maximum tries of: ${this.MAX_TRIES}, aborting`)
            return false
        } else {
            return true
        }
    },
    init(source, target, options = {}) {
        this.options = this.createDefaultOptions(options)
        this.counter = 0
        this.source = source.get(0)
        this.initialSourcePosition = this.source.getBoundingClientRect()
        return cy.get(target).then((targetWrapper) => {
            this.target = targetWrapper.get(0)
        })
    },
    drag(sourceWrapper, targetSelector, options) {
        this.init(sourceWrapper, targetSelector, options)
            .then(() => this.dragstart())
            .then(() => this.dragover())
            .then((success) => {
                if (success) {
                    return this.drop().then(() => true)
                } else {
                    return false
                }
            })
    },
    move(sourceWrapper, options) {
        const { deltaX, deltaY } = options
        const { top, left } = sourceWrapper.offset()
        const finalCoords = { clientX: left + deltaX, clientY: top + deltaY }
        this.init(sourceWrapper, sourceWrapper, options)
            .then(() => this.dragstart({ clientX: left, clientY: top }))
            .then(() => this.dragover(finalCoords))
            .then(() => this.drop(finalCoords))
    },
}

function addChildCommand(name, command) {
    Cypress.Commands.add(name, { prevSubject: 'element' }, (...args) => command(...args))
}

addChildCommand('drag', DragSimulator.drag.bind(DragSimulator))
addChildCommand('move', DragSimulator.move.bind(DragSimulator))