/**  
 * @since Last modified: 2024-09-10 11:20:41
 * @class Drag
 * @description Delay the execution of enter and leave events, mouse over the element to prohibit repeated triggering of the event.
 * @param {getEl} elem - The target node.The writing is the same as getEl.
 * @param {Object} [options] - The parameters.
 * @param {number} [options.timeout=50] - How long does the incident return to the initial state after leaving the element, the unit in ms.
 * @param {number} [options.intvl=50] - When moving on the element, check the intvl time of the cursor coordinate, the unit in ms.
 * @param {number} [options.throttle=200] - How often to listen to the move event on "document.body", the unit in ms.
 * @param {number} [options.threshold=7] - A distance threshold.During the intvl time, the enter event is triggered by the move, and the unit px.
 * @param {fn_cb} [options.onMove] - The event of moving on "document.body".
 * @param {fn_cb} [options.onEnter] - The event of entering target element.
 * @param {fn_cb} [options.onLeave] - The event of leaving target element.
 * @param {getEl} [options.hold] - A element, the leave event will not be executed.The writing is the same as getEl.
 * @see {@link https://codepen.io/axui/pen/rNbagwR|demo @ codepen}
 * @example
 * ax.Drag('#id',{});
 */
import getEl from '../utils/getEl';
import extend from '../utils/extend';
import pipe from '../utils/pipe';
import PubBase from './PubBase';
import PubListen from './PubListen';
import { T_el, T_fn, T_obj, T_required, T_sel } from '../../../types/ax';
import { T_drag } from '../../../types/drag';
import instance from '../utils/instance';
import optDrag from '../options/optDrag';
import PubUpdate from './PubUpdate';
import PubCommon from './PubCommon';
import createModule from '../utils/createModule';
import contains from '../utils/contains';
import throttle from '../utils/throttle';
import getRectPoints from '../utils/getRectPoints';
import prefix from '../global/prefix';
import alias from '../global/alias';
import createEl from '../utils/createEl';
import style from '../utils/style';
import elState from '../utils/elState';
import Gesture from './Gesture';
import elsSort from '../utils/elsSort';
import eventMap from '../utils/eventMap';
import transformTools from '../utils/transformTools';
import debounce from '../utils/debounce';
import ease from '../utils/ease';
class Drag extends pipe(PubListen, PubCommon)(PubBase) {
    targetEl: any;
    options: T_required<T_drag<0>> | T_obj = {};
    plans: any;
    curX: any;
    curY: any;
    preX: any;
    preY: any;
    isActive: any;
    enterTimer: any;
    leaveTimer: any;
    eventToggle: any;
    holdEl: any;
    enterCompare: any;
    globalEvent: any;
    moveEvent: any;
    enterEvent: any;
    leaveEvent: any;
    enterHold: any;
    leaveHold: any;
    static hostType = 'node';
    constructor(elem: T_sel, options: T_drag = {}, initial: boolean = true) {
        super();
        this.ready({
            options,
            maps: optDrag,
            host: elem,
            component: false,
            spread: []
        });
        if (!this.targetEl) throw new Error('The target node for drag start is missing!');
        this.dragHolder = createEl('div', { class: `${prefix}holder-drag` }, this.options.lang.holderDrag);
        this.dropHolder = createEl('div', { class: `${prefix}holder-drop` }, this.options.lang.holderDrop);
        this.dropArrow = createEl('i', { class: `${prefix}drop-arrow ${this.options.arrow.icon}`, placement: this.options.arrow.placement, point: 'inside' });
        this.orgHolder = null;
        this.drops = [this.dropHolder];
        this.lastDrop = null;
        this.lastPoint = '';
        this.dftParams = { data: this.options.data, from: this.targetEl, holder: this.dropHolder, to: this.lastDrop, mode: this.options.mode, purpose: this.options.purpose, drops: this.drops, point: this.lastPoint };
        this.dropOver = debounce((evt: T_obj, target: T_el) => {
            let drop = this.drops.find((k: any) => contains(target, k));
            if (drop) {
                if (drop === this.dropHolder) {
                    this.toggleDropHolderAttr('set', this.lastDrop);
                    this.toggleDropAttr();
                } else {
                    this.toggleDropHolderAttr('remove');
                    this.toggleDropAttr(drop, 'ing');
                    this.updateHolderSize(this.dropHolder, drop, this.holderAttr === 'width' ? 'height' : this.holderAttr === 'height' ? 'width' : '');
                }
                if (drop === this.dropHolder || drop === this.targetEl) return;
                this.lastDrop = drop;
                this.options.arrow.enable && elState(this.dropArrow).isVirtual && drop.appendChild(this.dropArrow);
                let points = getRectPoints(evt, drop),
                    isBefore = points.find((k: any) => this.options.point.before.includes(k)),
                    isAfter = points.find((k: any) => this.options.point.after.includes(k)),
                    params = { ...this.dftParams, orgEvt: evt, to: drop, points };
                if (this.options.purpose === 'sort' || this.options.purpose === 'auto') {
                    if (isBefore) {
                        this.lastPoint = 'before';
                        this.options.holder.to && drop.insertAdjacentElement('beforebegin', this.dropHolder);
                    } else if (isAfter) {
                        this.lastPoint = 'after';
                        this.options.holder.to && drop.insertAdjacentElement('afterend', this.dropHolder);
                    } else {
                        if (this.options.purpose === 'auto') {
                            this.lastPoint = 'inside';
                            elState(this.dropHolder).isCalc && this.dropHolder.remove();
                        }
                    }
                } else {
                    this.lastPoint = 'inside';
                }
                drop.setAttribute('point', this.lastPoint);
                this.options.arrow.enable && this.dropArrow.setAttribute('point', this.lastPoint);
                super.listen({ name: 'dropping', params: [{ ...params, point: this.lastPoint }] });
            } else {
                this.toggleDropAttr();
                this.toggleDropHolderAttr('remove');
                elState(this.dropHolder).isCalc && this.dropHolder.remove();
            }
        }, this.options.delay);
        this.dropEnd = async (evt: T_obj, target: T_el) => {
            let drop = this.drops.find((k: any) => contains(target, k)),
                params = { ...this.dftParams, orgEvt: evt, to: this.lastDrop, point: this.lastPoint };
            if (drop) {
                if (this.options.b4Drop) {
                    let tmp = await this.options.b4Drop.call(this, params);
                    if (tmp === false) {
                        this.goback(this.options.mode, 'fail');
                        return true;
                    }
                }
                this.clearTransform();
                this.toggleDropAttr(drop, 'ed');
                super.listen({ name: 'dropped', params: [params] });
                return false;
            } else {
                this.goback(this.options.mode, 'fail');
                return true;
            }
        }
        super.listen({ name: 'ready' });
        initial && this.init() ;
    }
    async init(cb?: T_fn): Promise<any> {
        super.listen({ name: 'launch' });
        this.options.b4Init && await this.options.b4Init.call(this);
        this.setAttrs();
        this.holderAttr = ['left', 'right'].includes(this.options.arrow.placement) ? 'height' :
            ['top', 'bottom'].includes(this.options.arrow.placement) ? 'width' : '';
        this.orgVal = transformTools.get(this.targetEl, ['translate'])['translate'];
        this.nowVal = { ...this.orgVal };
        this.orgStyle = this.targetEl.style.cssText;
        this.getGestureIns();
        super.listen({ name: 'init', cb });
    }
    setAttrs() {
        this.targetEl.setAttribute('draggable', '');
    }
    getGestureIns() {
        this.gestureIns = new Gesture(this.targetEl, {
            rotate: false,
            scale: false,
            drift: false,
            wheel: false,
            onTranslate: (data: T_obj) => {
                this.orgHolder = this.targetEl.cloneNode(true);
                this.orgHolder.classList.add(`${prefix}holder-origin`);
                this.targetEl.setAttribute('drag', this.options.mode);
                document.body.style.cursor = 'grabbing';
                this.getDrops();
                if (this.options.mode === 'cut' && this.options.holder.from) {
                    this.updateHolderSize(this.dragHolder, this.targetEl);
                    this.targetEl.insertAdjacentElement('afterend', this.dragHolder);
                }
                if (this.options.mode === 'copy' && this.options.holder.from) {
                    this.targetEl.insertAdjacentElement('afterend', this.orgHolder);
                }
                super.listen({ name: 'dragStart', params: [{ ...data }] });
            },
            onTranslating: (data: T_obj) => {
                this.dropOver(data.orgEvt, data.evtTarget);
                transformTools.set({
                    el: this.targetEl,
                    data: {
                        translate: data.translate.value,
                    },
                });
                this.nowVal = data.translate.value;
                super.listen({ name: 'dragMove', params: [{ ...data, }] });
            },
            onTranslated: (data: T_obj) => {
                this.dropEnd(data.orgEvt, data.evtTarget).then((resp: boolean) => {
                    !resp && this.restore();
                });
                super.listen({ name: 'dragEnd', params: [{ ...data }] });
            },
            onCancel: (data: T_obj) => {
                this.toggleDropAttr();
                this.restore();
                super.listen({ name: 'dragCancel', params: [{ ...data }] });
            }
        });
    }
    updateHolderSize(target: T_el, relate: T_el, attr?: 'width' | 'height' | '') {
        if (!relate) return;
        let styleRelate = style(relate),
            styleTarget = style(target),
            width = styleRelate.width,
            height = styleRelate.height;
        if (attr === 'width') {
            target.style.width = width;
            target.style.height = styleTarget.height;
        } else if (attr === 'height') {
            target.style.height = height;
            target.style.width = styleTarget.width;
        } else {
            target.style.width = width;
            target.style.height = height;
        }
    }
    toggleDropAttr(exclude?: T_el, type?: 'ing' | 'ed') {
        if (exclude === this.dropHolder) return;
        exclude && exclude !== this.dropHolder && exclude.setAttribute('drop', type);
        for (let k of this.drops) {
            if (exclude === k) continue;
            k.removeAttribute('drop');
        }
    }
    toggleDropHolderAttr(type: 'set' | 'remove' = 'set', relate?: T_el) {
        if (type === 'remove') {
            this.dropHolder.removeAttribute('moving');
        } else {
            this.dropHolder.setAttribute('moving', '');
            relate && this.updateHolderSize(this.dropHolder, relate, this.holderAttr);
        }
    }
    clearTransform() {
        this.targetEl.style.cssText = this.targetEl.style.cssText.replace('transform', '');
    }
    getDrops() {
        this.drops = [this.dropHolder, ...elsSort(super.single2Els(this.options.drops, this.options.parent))];
    }
    goback(type: 'copy' | 'cut', state: 'succ' | 'fail') {
        if (state === 'succ') {
            if (type === 'copy') {
                this.targetEl.style.transitionDuration = `0ms`;
                transformTools.set({
                    el: this.targetEl,
                    data: {
                        translate: this.orgVal,
                    }
                });
                this.toggleDropAttr();
                this.restore();
            }
        } else if (state === 'fail') {
            ease({
                from: this.nowVal,
                to: this.orgVal,
                doing: (data: T_el) => {
                    transformTools.set({
                        el: this.targetEl,
                        data: {
                            translate: data.value,
                        }
                    });
                },
                done: () => {
                    this.restore();
                },
                duration: this.options.duration,
            });
            this.toggleDropAttr();
        }
    }
    restore() {
        this.targetEl.removeAttribute('drag');
        this.dropHolder.removeAttribute('moving');
        this.dropHolder.style.cssText = this.dropHolder.style.cssText.replace('width', '').replace('height', '');
        elState(this.dragHolder).isCalc && this.dragHolder.remove();
        elState(this.dropHolder).isCalc && this.dropHolder.remove();
        elState(this.orgHolder).isCalc && this.orgHolder.remove();
        elState(this.dropArrow).isCalc && this.dropArrow.remove();
        this.lastDrop = null;
        this.lastPoint = '';
        document.body.style.cssText = document.body.style.cssText.replace('cursor', '');
    }
    destroy(cb: any): any {
        if (this.destroyed) {
            return this;
        }
        this.restore();
        this.toggleDropAttr();
        this.gestureIns.destroy();
        this.destroyed = true;
        super.listen({ name: 'destroy', cb });
        return this;
    }
}
export default Drag;
