/**  
 * @since Last modified: 2024-09-09 22:37:22
 * @class Gesture
 * @description Basic class, you can extend any subclasses.
 * @param {getEl} elem - The target node.The writing is the same as getEl.
 * @param {Object} [options] - The parameters 
 * @see {@link https://codepen.io/axui/pen/jOREowx|demo @ codepen}
 * @example
 * ax.Gesture('#id');
 */
import { T_el, T_fn, T_fn_cb, T_fn_none, T_obj, T_opt_base, T_required } from "../../../types/ax";
import createEl from "../utils/createEl";
import getEl from "../utils/getEl";
import instance from "../utils/instance";
import pipe from "../utils/pipe";
import PubBase from "./PubBase";
import PubListen from "./PubListen";
import ax from "../ax";
import PubCommon from "./PubCommon";
import optGesture from "../options/optGesture";
import PubUpdate from "./PubUpdate";
import { T_gesture } from "../../../types/gesture";
import isMobi from "../utils/isMobi";
import extend from "../utils/extend";
import scrollObj from "../utils/scrollObj";
import deepClone from "../utils/deepClone";
import delay from "../utils/delay";
import transformTools from "../utils/transformTools";
import style from "../utils/style";
import isScrollUp from "../utils/isScrollUp";
import eventMap from "../utils/eventMap";
import clampVal from "../utils/clampVal";
import toNumber from "../utils/toNumber";
import prefix from "../global/prefix";
import getEls from "../utils/getEls";
import contains from "../utils/contains";
import isOutside from "../utils/isOutside";
import ease from "../utils/ease";
import curveVars from "../utils/curveVars";
import getEvtTarget from "../utils/getEvtTarget";
import throttle from "../utils/throttle";
import Spy from "./Spy";
class Gesture extends pipe(PubListen, PubCommon)(PubBase) {
    options: T_required<T_gesture<0>> | T_obj = {};
    static hostType = 'node';
    constructor(elem: any, options?: T_gesture, initial: boolean = true) {
        super();
        this.ready({
            options,
            host: elem,
            maps: optGesture,
            spread: ['drift', 'scale', 'swipe', 'rotate', 'viewport', 'keyboard', 'spy']
        });
        /**
        * @name Gesture.targetEl
        * @description Target element.
        * @type getEl
        */
        /**
        * @name Gesture.rawOpts
        * @description Raw Parameters of the instance from map.
        * @type object
        */
        /**
         * @name Gesture.dftOpts
         * @description Default Parameters of the instance from merging.The reset method requires this variable.
         * @type object
         */
        /**
         * @name Gesture.options
         * @description Parameters of the instance.
         * @type object
         */
        /**
         * @name Gesture.destroyed
         * @description Destruction state.
         * @type boolean
         */
        /**
        * @name Gesture.plans
        * @description Plans for listening.
        * @type object
        */
        /**
        * @name Gesture.storKeys
        * @description The set of parameter fields to save.
        * @type array
        */
        /**
         * @name Gesture.storTmp
         * @description Cache object.The "updateCache" method will update the variable.
         * @type object
         */
        /**
        * @name Gesture.storObs
        * @description Cache listener(proxy) that listens for "storTmp" changes and automatically updates the local storage.
        * @type any
        */
        /**
        * @name Gesture.timestamp
        * @description Instance creation time.
        * @type number
        */
        /**
        * @function Gesture.save
        * @description Storing settings locally.
        * @param {fn_cb_options} [cb] - Callback execution after saving local storage.
        * @returns {any} Return the instance itself
        */
        /**
        * @function Gesture.reset
        * @description Re-initialization of old parameters.
        * @param {fn_cb_none} [cb] - Call back after reset settings.
        * @returns {*} Return the instance itself
        */
        let _this = this;
        this.started = false;
        this.eventState = 'end';
        this.scrollObj = scrollObj;
        this.evtTarget = null;
        this.isMobi = isMobi;
        this.startTime = 0;
        this.diffTime = 0;
        this.jitterClick = _this.options.jitterClick + (this.isMobi ? 10 : 0);
        this.jitterTrans = _this.options.jitterTrans + (this.isMobi ? 4 : 0);
        this.clickCount = 0;
        this.preventEase = false;
        this.holdHandle = null;
        this.dragEls = getEls(this.options.translate.target);
        this.canTrans = true;
        this.transType = 'translate';
        this.getStepVal();
        this.moveVals = {
            h: 0,
            x: 0,
            y: 0,
            c: { id: 0, x: 0, y: 0 },
            d: { x: '', y: '' }
        }
        this.diffVals = { scale: 0, rotate: 0, translate: { x: 0, y: 0, h: 0 } };
        this.startCoord = { x: 0, y: 0 };
        this.triangleVals = { start: { x: 0, y: 0, h: 0 }, last: { x: 0, y: 0, h: 0 }, now: { x: 0, y: 0, h: 0 } };
        this.moveCount = 0;
        this.touchesMoveCount = 0;
        this.paramsFormat = {
            orgEvt: null,
            pointer: this.isMobi ? 'finger' : 'mouse',
            scale: {
                direction: 0,
                diff: 0,
                value: { x: 1, y: 1 },
                translate: { x: 0, y: 0 }
            },
            rotate: {
                direction: 0,
                diff: 0,
                value: 0
            },
            translate: {
                direction: { x: 0, y: 0 },
                diff: { x: 0, y: 0, h: 0 },
                value: { x: 0, y: 0 },
                canDrift: false,
                canSwipe: false,
            },
            drift: {
                duration: 0,
                direction: { x: 0, y: 0 },
                diff: { x: 0, y: 0, h: 0 },
                value: { x: 0, y: 0 }
            },
            swipe: {
                duration: 0,
                direction: { x: 0, y: 0 },
                diff: { x: 0, y: 0, h: 0 },
                value: { x: 0, y: 0 }
            },
            step: {
                direction: { x: 0, y: 0 },
            },
            coord: { x: 0, y: 0 },
            target: this.targetEl,
            name: '',
            moveStart: false,
            touchesStart: false,
            moveEnd: false,
            moveTime: 0,
        }
        this.params = null;
        this.endParams = {
            moveStart: false,
            touchesStart: false,
            moveEnd: true,
        }
        this.startFn = function (e: any) {
            if (contains(e.target, _this.targetEl)) {
                e.stopPropagation();
                e.cancelable && e.preventDefault();
            }
            _this.evtTarget = getEvtTarget(e);
            _this.params = { ...deepClone(_this.paramsFormat), evtTarget: _this.evtTarget };
            _this.params.orgEvt = e;
            if ((_this.isMobi && e.targetTouches.length > 2 && e.changedTouches.length === 0) || !contains(e.target, _this.targetEl)) {
                _this.started = false;
                return;
            }
            _this.getStartVals();
            _this.targetEl.setAttribute('gesture', 'start');
            let changedVals = _this.getTouchCoords(e, 'targetTouches'),
                targetVals = _this.getTouchCoords(e, 'targetTouches'),
                paramsHold = { ..._this.params },
                paramsTranslate = { ..._this.params },
                paramsScale = { ..._this.params },
                paramsRotate = { ..._this.params },
                paramsStart = { ..._this.params };
            _this.startCoord = _this.getCenterCoord(changedVals);
            _this.startTime = Date.now();
            if (!_this.isMobi || e.targetTouches.length === 1) {
                _this.holdHandle = setTimeout(() => {
                    paramsHold.coord = { ..._this.startCoord };
                    paramsHold.name = 'hold';
                    _this.listen({ name: 'hold', params: [paramsHold] });
                    clearTimeout(_this.holdHandle);
                }, _this.options.click.holdDelay);
            } else {
                _this.holdHandle && clearTimeout(_this.holdHandle);
            }
            if (_this.isMobi && e.targetTouches.length > 1) {
                _this.startCoord = _this.getCenterCoord(targetVals);
                paramsScale.coord = { ..._this.startCoord };
                paramsScale.scale.value = _this.lastVals.scale;
                paramsScale.name = 'scale';
                _this.listen({ name: 'scale', params: [paramsScale] });
                paramsRotate.coord = { ..._this.startCoord };
                paramsRotate.rotate.value = _this.lastVals.rotate;
                paramsRotate.name = 'rotate';
                _this.listen({ name: 'rotate', params: [paramsRotate] });
            }
            paramsTranslate.coord = { ..._this.startCoord };
            paramsTranslate.translate.value = { ..._this.lastVals.translate };
            paramsTranslate.name = 'translate';
            if (_this.dragEls.length === 0 || (_this.dragEls.length > 0 && _this.dragEls.find((k: any) => contains(e.target, k)))) {
                _this.listen({ name: 'translate', params: [paramsTranslate] });
            } else {
                _this.canTrans = false;
            }
            paramsStart.coord = { ..._this.startCoord };
            paramsStart.scale.value = _this.lastVals.scale;
            paramsStart.rotate.value = _this.lastVals.rotate;
            paramsStart.translate.value = { ..._this.lastVals.translate };
            paramsStart.totalTrans = _this.totalTrans;
            paramsStart.name = 'start';
            _this.listen({ name: 'start', params: [paramsStart] });
            _this.options.viewport.enable && _this.getSizeDiff(_this.lastVals.translate);
            _this.started = true;
            _this.eventState = 'start';
        }
        this.moveFn = function (e: any) {
            if (contains(e.target, _this.targetEl)) {
                e.stopPropagation();
                e.cancelable && e.preventDefault();
            }
            if (!_this.started) return;
            /* if (_this.driftListen) {
                _this.driftListen.then((data: number) => {
                    cancelAnimationFrame(data);
                });
            }; */
            _this.driftListen && cancelAnimationFrame(_this.driftListen.frame);
            _this.diffTime = Date.now() - _this.startTime;
            _this.moveCount++;
            _this.params.orgEvt = e;
            let targetVals = _this.getTouchCoords(e, 'targetTouches'),
                moveStart = _this.moveCount === 1 ? true : false;
            _this.moveVals = _this.getMoveVals(targetVals, _this.startCoord);
            if (_this.moveVals.h <= _this.jitterTrans) {
                return;
            } else {
                _this.targetEl.style.transitionDuration = `0ms`;
            }
            _this.evtTarget = getEvtTarget(e);
            let paramsTouches = { ..._this.params, coord: _this.moveVals.c, moveStart, evtTarget: _this.evtTarget },
                paramsTranslate = { ...paramsTouches },
                paramsScale = { ...paramsTouches },
                paramsRotate = { ...paramsTouches },
                paramsMove = { ...paramsTouches };
            _this.diffVals.translate = { x: ~~_this.moveVals.x, y: ~~_this.moveVals.y, h: ~~_this.moveVals.h };
            _this.updateNowTrans();
            _this.options.viewport.enable && (_this.nowVals.translate = _this.getBounceVals(_this.nowVals.translate));
            _this.moveVals.h > _this.jitterClick && _this.holdHandle && clearTimeout(_this.holdHandle);
            paramsTranslate = extend({
                target: paramsTouches,
                source: {
                    translate: {
                        diff: { ..._this.diffVals.translate },
                        value: { ..._this.nowVals.translate },
                        direction: _this.moveVals.d
                    },
                    name: 'translating'
                }
            });
            _this.totalTrans = _this.nowVals.translate;
            if (_this.parentEl && isOutside(e, _this.parentEl)) _this.canTrans = false;
            if (_this.canTrans) {
                if (_this.options.swipe.enable && _this.diffTime < _this.options.swipe.timeThr && _this.diffVals.translate.h > _this.options.swipe.distThr) {
                    _this.transType = 'swipe';
                } else {
                    _this.transType = 'translate';
                    _this.targetEl.setAttribute('gesture', 'move');
                    _this.listen({ name: 'translating', params: [paramsTranslate] });
                }
            }
            if (_this.isMobi && e.targetTouches.length > 1) {
                _this.touchesMoveCount++;
                _this.triangleVals.now = _this.getTriangleVals(targetVals[0], targetVals[1]);
                if (_this.touchesMoveCount === 1) {
                    _this.triangleVals.start = { ..._this.triangleVals.now };
                    _this.diffVals.scale = 0;
                    _this.diffVals.rotate = 0;
                    paramsTouches.touchesStart = true;
                } else {
                    _this.diffVals.scale = (_this.triangleVals.now.h / _this.triangleVals.last.h) - 1;
                    _this.diffVals.rotate = _this.triangleVals.now.a - _this.triangleVals.last.a;
                    _this.correctDiffRotate();
                }
                _this.correctRangeScale();
                _this.correctRangeRotate();
                paramsScale = extend({
                    target: paramsTouches,
                    source: {
                        scale: {
                            diff: toNumber(_this.diffVals.scale),
                            value: { x: toNumber(_this.nowVals.scale.x), y: toNumber(_this.nowVals.scale.y) },
                            direction: _this.diffVals.scale > 0 ? 1 : _this.diffVals.scale < 0 ? -1 : 0
                        },
                        name: 'scaling'
                    }
                });
                if (!_this.options.scale.centered) {
                    paramsScale.scale.translate = _this.getScaleTrans({
                        coord: paramsScale.coord,
                        diff: paramsScale.scale.diff,
                    });
                    _this.totalTrans = _this.getScaleTrans({
                        initVal: _this.totalTrans,
                        coord: paramsScale.coord,
                        diff: paramsScale.scale.diff,
                    });
                }
                _this.listen({ name: 'scaling', params: [paramsScale] });
                paramsRotate = extend({
                    target: paramsTouches,
                    source: {
                        rotate: {
                            diff: toNumber(_this.diffVals.rotate),
                            value: toNumber(_this.nowVals.rotate),
                            direction: _this.diffVals.rotate > 0 ? 1 : _this.diffVals.rotate < 0 ? -1 : 0
                        },
                        name: 'rotating'
                    }
                });
                _this.listen({ name: 'rotating', params: [paramsRotate] });
            }
            paramsMove = extend({
                target: paramsTouches,
                source: {
                    rotate: paramsRotate.rotate,
                    scale: paramsScale.scale,
                    translate: paramsTranslate.translate,
                    totalTrans: _this.totalTrans,
                    name: 'move'
                }
            });
            _this.listen({ name: 'move', params: [paramsMove] });
            _this.triangleVals.last = { ..._this.triangleVals.now };
            _this.eventState = 'move';
        }
        this.endFn = function (e: any) {
            if (contains(e.target, _this.targetEl)) {
                e.stopPropagation();
                e.cancelable && e.preventDefault();
            }
            if (!_this.started) return;
            _this.moveCount = _this.touchesMoveCount = 0;
            _this.params.orgEvt = e;
            _this.params.evtTarget = _this.evtTarget;
            _this.params.moveTime = _this.diffTime = Date.now() - _this.startTime;
            _this.holdHandle && clearTimeout(_this.holdHandle);
            let paramsTranslate = {
                ..._this.params,
                coord: { ..._this.moveVals.c },
                translate: {
                    diff: { ..._this.diffVals.translate },
                    value: { ..._this.nowVals.translate },
                    direction: { ..._this.moveVals.d },
                },
                ..._this.endParams,
            },
                paramsClick = { ..._this.params, ..._this.endParams },
                paramsScale = { ...paramsClick },
                paramsRotate = { ...paramsClick },
                paramsEnd = { ...paramsClick };
            if (_this.diffVals.translate.h < _this.jitterClick) {
                if (_this.diffTime < _this.options.click.timeThr && (!_this.isMobi && e.button === 0 || _this.isMobi)) {
                    let coords = _this.getTouchCoords(e, 'changedTouches'),
                        endCoord = _this.getCenterCoord(coords);
                    paramsClick.coord = endCoord;
                    paramsClick.name = 'click';
                    if (_this.options.click.dblclickable) {
                        _this.clickCount++;
                        if (_this.clickCount === 1) {
                            setTimeout(() => {
                                if (_this.clickCount === 1) {
                                    _this.listen({ name: 'click', params: [paramsClick] });
                                } else if (_this.clickCount > 1) {
                                    paramsClick.name = 'dblclick';
                                    _this.listen({ name: 'dblclick', params: [paramsClick] });
                                }
                                _this.clickCount = 0;
                            }, _this.options.click.delay);
                        }
                    } else {
                        _this.listen({ name: 'click', params: [paramsClick] });
                    }
                }
            }
            if (_this.canTrans) {
                let canDrift = _this.diffVals.translate.h > (_this.options.drift.distThr * (_this.isMobi ? 2 : 1)) && _this.diffTime < (_this.options.drift.timeThr / (_this.isMobi ? 2 : 1));
                paramsTranslate.translate.canSwipe = _this.transType === 'swipe' ? true : false;
                paramsTranslate.translate.canDrift = paramsTranslate.translate.canSwipe ? false : canDrift;
                paramsTranslate.name = 'translated';
                _this.listen({ name: 'translated', params: [paramsTranslate] });
                if (!_this.preventEase) {
                    if (_this.transType === 'swipe') {
                        _this.updateEaseParamsListen(paramsTranslate, 'swipe');
                    } else {
                        (_this.options.drift.enable && canDrift) && _this.updateEaseParamsListen(paramsTranslate);
                    }
                }
            }
            if (_this.isMobi) {
                paramsScale.scale.diff = toNumber(_this.diffVals.scale);
                paramsScale.scale.value = { x: toNumber(_this.nowVals.scale.x), y: toNumber(_this.nowVals.scale.y) };
                paramsScale.name = 'scaled';
                _this.listen({ name: 'scaled', params: [paramsScale] });
                paramsRotate.rotate.diff = toNumber(_this.diffVals.rotate);
                paramsRotate.rotate.value = toNumber(_this.nowVals.rotate);
                paramsRotate.name = 'rotated';
                _this.listen({ name: 'rotated', params: [paramsRotate] });
                _this.options.viewport.enable && _this.moveVals.h > 1 && _this.getSizeDiff();
                _this.lastVals.rotate = _this.nowVals.rotate;
                _this.lastVals.scale = { ..._this.nowVals.scale };
            }
            paramsEnd.translate = paramsTranslate.translate;
            paramsEnd.drift = paramsTranslate.drift;
            paramsEnd.scale = paramsScale.scale;
            paramsEnd.rotate = paramsRotate.rotate;
            paramsEnd.totalTrans = _this.totalTrans;
            paramsEnd.name = 'end';
            _this.listen({ name: 'end', params: [paramsEnd] });
            _this.options.viewport.enable && _this.moveVals.h > 1 && _this.rebound();
            _this.lastVals.translate = { ..._this.nowVals.translate };
            _this.setCompleted();
            _this.removeEvents();
        }
        this.stepFn = (e: any, dirValue: number, type: 'wheel' | 'keyboard' = 'wheel') => {
            _this.options.viewport && _this.getSizeDiff();
            _this.getStartVals();
            _this.options.step.duration ? _this.targetEl.style.transitionDuration = `${_this.options.step.duration}ms` : null;
            _this.targetEl.style.transitionTimingFunction = curveVars[_this.options.step.curve];
            _this.params = { ...deepClone(_this.paramsFormat), evtTarget: (type === 'keyboard' ? this.targetEl : getEvtTarget(e)) };
            _this.params = extend({
                target: _this.params,
                source: {
                    pointer: type,
                    orgEvt: e,
                    coord: type === 'keyboard' ? { x: 0, y: 0 } : { x: e.clientX, y: e.clientY },
                    step: {
                        direction: { x: dirValue, y: dirValue }
                    },
                    translate: { value: { ..._this.nowVals.translate } },
                    scale: {
                        value: { ..._this.nowVals.scale },
                        direction: dirValue
                    },
                    rotate: {
                        value: _this.nowVals.rotate,
                        direction: dirValue
                    },
                    moveTime: parseFloat(style(_this.targetEl).transitionDuration) * 1000
                }
            });
            _this.listen({ name: 'step', params: [{ ..._this.params, name: 'step' }] });
            let paramsAction = { ..._this.params };
            if (_this.options.step.mode === 'scale') {
                _this.diffVals.scale = (dirValue === 1 ? _this.stepVal : -_this.stepVal);
                _this.correctRangeScale();
                paramsAction.scale.diff = toNumber(_this.diffVals.scale);
                paramsAction.scale.value = { x: toNumber(_this.nowVals.scale.x), y: toNumber(_this.nowVals.scale.y) };
                if (!_this.options.scale.centered) {
                    paramsAction.scale.translate = _this.getScaleTrans({
                        coord: paramsAction.coord,
                        diff: paramsAction.scale.diff,
                    });
                    _this.totalTrans = _this.getScaleTrans({
                        initVal: _this.totalTrans,
                        coord: paramsAction.coord,
                        diff: paramsAction.scale.diff,
                    });
                }
                _this.options.step.linkage && _this.listen({ name: 'scaling', params: [{ ...paramsAction, name: 'scaling' }] });
            } else if (_this.options.step.mode === 'rotate') {
                _this.diffVals.rotate = (dirValue === 1 ? _this.stepVal : -_this.stepVal);
                _this.correctRangeRotate();
                paramsAction.rotate.diff = toNumber(_this.diffVals.rotate);
                paramsAction.rotate.value = toNumber(_this.nowVals.rotate);
                _this.options.step.linkage && _this.listen({ name: 'rotating', params: [{ ...paramsAction, name: 'rotating' }] });
            } else if (_this.options.step.mode === 'translate') {
                let direction = _this.options.step.axis === 'y' ? { x: 0, y: -dirValue, } : { x: -dirValue, y: 0 },
                    axisDist = -dirValue * _this.stepVal,
                    x = _this.options.step.axis === 'y' ? 0 : axisDist,
                    y = _this.options.step.axis === 'x' ? 0 : axisDist;
                _this.diffVals.translate = { x, y, h: _this.stepVal };
                _this.updateNowTrans();
                _this.options.viewport.enable && (_this.nowVals.translate = _this.getViewLimitVals(_this.nowVals.translate));
                paramsAction.translate.diff = _this.diffVals.translate;
                paramsAction.translate.value = _this.nowVals.translate;
                paramsAction.translate.direction = direction;
                _this.options.step.linkage && _this.listen({ name: 'translating', params: [{ ...paramsAction, name: 'translating' }] });
            }
            _this.listen({ name: 'stepped', params: [{ ...paramsAction, name: 'stepped' }] });
            _this.listen({ name: 'move', params: [{ ...paramsAction, totalTrans: _this.totalTrans, name: 'move' }] });
            _this.lastVals[_this.options.step.mode] = { ..._this.nowVals[_this.options.step.mode] };
        }
        this.wheelFn = throttle(function (e: any) {
            e.stopPropagation();
            e.preventDefault();
            let isUp = _this.correctWheelDirection(isScrollUp(e, _this.scrollObj.detail)),
                dirValue = isUp ? 1 : -1;
            _this.stepFn(e, dirValue, 'wheel');
        }, { intvl: _this.options.step.intvl, prevent: true });
        this.keyboardFn = throttle((e: any) => {
            e.stopPropagation();
            e.preventDefault();
            let dirValue = (e.code === this.options.keyboard.prev) ? -1 : (e.code === this.options.keyboard.next) ? 1 : 0;
            if (!dirValue) return;
            _this.stepFn(e, dirValue, 'keyboard');
        }, { intvl: _this.options.step.intvl, prevent: true });
        this.cancelFn = function (e: any) {
            _this.setCompleted();
            _this.removeEvents();
            let paramsCancel = Object.assign(deepClone(_this.paramsFormat), {
                orgEvt: e,
                evtTarget: getEvtTarget(e),
                coord: { x: e.clientX, y: e.clientY },
                ..._this.endParams,
                name: 'cancel'
            });
            _this.listen({ name: 'cancel', params: [paramsCancel] });
        }
        this.menuFn = function (e: any) {
            if (_this.isUnbound(e.target)) return;
            e.stopPropagation();
            e.preventDefault();
            let paramsClick = Object.assign(deepClone(_this.paramsFormat), {
                orgEvt: e,
                evtTarget: getEvtTarget(e),
                coord: { x: e.clientX, y: e.clientY },
                ..._this.endParams,
                name: 'hold'
            });
            _this.listen({ name: 'hold', params: [paramsClick] });
        }
        this.triggerFn = (e: any) => {
            if (this.isUnbound(e.target)) return;
            e.stopPropagation();
            this.addEvents();
        }
        this.transitionendFn = (e: any) => {
            e.stopPropagation();
            if (this.options.viewport.enable) {
                this.getSizeDiff(this.nowVals.translate);
                this.rebound(this.nowVals.translate);
            }
        }
        this.preventDft = (e: any) => {
            e?.preventDefault();
        }
        super.listen({ name: 'ready' });
        initial && this.init() ;
    }
    getStartVals() {
        this.startVals = transformTools.get(this.targetEl, ['translate', 'scale', 'rotate'], this.options.translate.instead);
        this.totalTrans = { ...this.startVals.translate };
        this.nowVals = {
            translate: {
                ...this.startVals.translate,
                h: ~~this.calcDist(this.startVals.translate.x, this.startVals.translate.y)
            },
            scale: { ...this.startVals.scale },
            rotate: this.startVals.rotate,
        }
        this.lastVals = deepClone(this.nowVals);
    }
    /**
    * @description Initialization instance.
    * @param {fn_cb_none} [cb] - Callback execution after initialization.
    * @returns {Promise<any>} - Returns a proimise whose argument is the instance itself.
    */
    async init(cb?: T_fn): Promise<any> {
        super.listen({ name: 'launch' });
        this.options.b4Init && await this.options.b4Init.call(this);
        this.setEmpty();
        this.parentEl = getEl(this.options.parent);
        this.viewportEl = getEl(this.options.viewport.selector);
        this.options.initialVal ? transformTools.set({ el: this.targetEl, data: this.options.initialVal, instead: this.options.translate.instead }) : null;
        this.initialVals = this.options.translate.instead ?
            transformTools.get(this.targetEl, ['translate', 'scale', 'rotate', 'skew'], this.options.translate.instead) :
            this.targetEl.style.transform;
        this.targetEl.addEventListener('pointerdown', this.triggerFn, { passive: false });
        this.isMobi && this.targetEl.addEventListener('touchstart', this.preventDft, { passive: false });
        this.targetEl.addEventListener("transitionend", this.transitionendFn, { passive: false });
        if (!this.isMobi) {
            if (this.options.wheel) {
                this.targetEl.removeEventListener(this.scrollObj.event, this.wheelFn)
                this.targetEl.addEventListener(this.scrollObj.event, this.wheelFn, { passive: false })
            }
            if (this.options.click.hold2Menu) {
                this.targetEl.removeEventListener('contextmenu', this.menuFn);
                this.targetEl.addEventListener('contextmenu', this.menuFn);
            }
        }
        this.setSpy();
        this.setCompleted();
        super.listen({ name: 'init', cb });
        return this;
    }
    setEmpty() {
    }
    setSpy() {
        if (!this.options.spy.enable) return;
        let opts = extend({
            target: {
                onIn: () => {
                    super.listen({ name: 'showing'});
                },
                onOut: () => {
                    this.options.keyboard.enable && !this.isMobi && window.removeEventListener('keydown', this.keyboardFn);
                    super.listen({ name: 'hidden'});
                },
                onShown: () => {
                    this.options.keyboard.enable && !this.isMobi && window.addEventListener('keydown', this.keyboardFn, { passive: false });
                    super.listen({ name: 'shown'});
                }
            },
            source: this.options.spy,
        });
        this.spyIns = new Spy(getEl(this.options.keyboard.target) || this.targetEl, opts);
    }
    isUnbound(target: T_el) {
        let unbounds = getEls(this.options.unbound, this.targetEl);
        return unbounds.find((k: any) => contains(target, k));
    }
    updateEaseParamsListen(params: T_obj, type: 'drift' | 'swipe' = 'drift') {
        let distH = ~~((this.options[type].timeThr - this.diffTime) * this.options[type].coef) * (type === 'swipe' ? (this.isMobi ? 8 : 12) : 1),
            distX = ~~(distH * (this.diffVals.translate.x / this.diffVals.translate.h)),
            distY = ~~(distH * (this.diffVals.translate.y / this.diffVals.translate.h)),
            driftX = this.clampVals('translate', params.translate.value.x + distX),
            driftY = this.clampVals('translate', params.translate.value.y + distY);
        params[type].direction = params.translate.direction;
        params[type].diff = { x: distX, y: distY, h: distH };
        params[type].duration = this.options[type].duration;
        params[type].value = this.options.viewport.enable ? this.getViewLimitVals({ x: driftX, y: driftY }) : { x: driftX, y: driftY };
        params.name = type;
        this.listen({ name: type, params: [params] });
        if (this.options[type].auto) {
            this.targetEl.style.transitionDuration = `0ms`;
            this.driftListen = ease({
                from: params.translate.value,
                to: params[type].value,
                curve: this.options[type].curve,
                duration: this.options[type].duration,
                doing: (e: T_obj) => {
                    params.translate.value = { x: Math.floor(e.value.x), y: Math.floor(e.value.y) };
                    this.listen({ name: (type === 'swipe' ? 'swiping' : 'drifting'), params: [params] });
                }, done: () => {
                    let translateVals = transformTools.get(this.targetEl, ['translate'], this.options.translate.instead).translate;
                    if ((params[type].value.x === translateVals.x) && (params[type].value.y === translateVals.y)) {
                        this.listen({ name: (type === 'swipe' ? 'swiped' : 'drifted'), params: [params] });
                    }
                }
            });
        }
    }
    updateNowTrans() {
        let x = this.lastVals.translate.x + this.diffVals.translate.x,
            y = this.lastVals.translate.y + this.diffVals.translate.y;
        this.nowVals.translate = { x: ~~x, y: ~~y };
        this.correctRangeTrans();
    }
    getMoveVals(vals: T_obj, startCoord: T_obj) {
        let c = this.getCenterCoord(vals),
            diff = this.getTriangleVals(c, startCoord),
            h = ~~diff.h,
            x = ~~diff.x,
            y = ~~diff.y,
            d = { x: x > 0 ? 1 : x < 0 ? -1 : 0, y: y > 0 ? 1 : y < 0 ? -1 : 0 };
        return { c, h, x, y, d };
    }
    removeEvents() {
        document.removeEventListener(eventMap[0], this.startFn);
        document.removeEventListener(eventMap[1], this.moveFn);
        document.removeEventListener(eventMap[2], this.endFn);
        document.removeEventListener(eventMap[3], this.cancelFn);
    }
    addEvents() {
        document.addEventListener(eventMap[0], this.startFn, { passive: false });
        document.addEventListener(eventMap[1], this.moveFn, { passive: false });
        document.addEventListener(eventMap[2], this.endFn, { passive: false });
        document.addEventListener(eventMap[3], this.cancelFn, { passive: false });
    }
    setCompleted() {
        this.started = false;
        this.moveCount = this.touchesMoveCount = 0;
        this.diffVals = { scale: 0, rotate: 0, translate: { x: 0, y: 0, h: 0 } };
        this.holdHandle && clearTimeout(this.holdHandle);
        this.canTrans = true;
        this.preventEase = false;
        this.targetEl.style.cssText = this.targetEl.style.cssText.replace('transition-duration: 0ms', '');
        this.targetEl.removeAttribute('gesture');
    }
    getOrign(coord: T_obj, rect: T_obj) {
        let x = coord.x - rect.left,
            y = coord.y - rect.top;
        x < 0 ? x = 0 : x > rect.width ? x = rect.width : null;
        y < 0 ? y = 0 : y > rect.height ? y = rect.height : null;
        return { x, y }
    }
    getScaleTrans(options: T_obj) {
        let styles = style(this.targetEl),
            width = parseInt(styles.width),
            height = parseInt(styles.height),
            origin = this.options.origin || { x: width / 2, y: height / 2 },
            rect = this.targetEl.getBoundingClientRect(),
            newOrigin: T_obj = this.getOrign(options.coord, rect),
            transform = transformTools.get(this.targetEl, ['translate'], this.options.translate.instead),
            percent = (attr: string) => {
                let value = newOrigin[attr] / rect[attr === 'x' ? 'width' : 'height'];
                value < 0 ? value = 0 : value > 1 ? value = 1 : null;
                return value;
            },
            orginDiff = (attr: string) => {
                return (origin[attr] - (attr === 'x' ? width : height) * percent(attr)) * options.diff;
            },
            initVal: T_obj = options.initVal || { x: transform.translate.x, y: transform.translate.y },
            x = initVal.x + orginDiff('x'),
            y = initVal.y + orginDiff('y');
        return { x: ~~x, y: ~~y };
    }
    getTouchCoords(event: T_obj, target = 'targetTouches') {
        let touches = [];
        if (this.isMobi) {
            for (let k of event[target]) {
                touches.push({
                    x: ~~k.clientX,
                    y: ~~k.clientY
                });
            }
        } else {
            touches.push({
                x: ~~event.clientX,
                y: ~~event.clientY
            });
        }
        return touches;
    }
    getCenterCoord(data: T_obj) {
        let result,
            avg = (n: number, axis: string) => {
                let value = 0;
                for (let k = 0; k < n; k++) {
                    value += data[k][axis];
                }
                return value / n;
            };
        if (data.length > 1) {
            result = { x: ~~avg(2, 'x'), y: ~~avg(2, 'y') };
        } else if (data.length === 1) {
            result = data[0];
        } else {
            result = { x: 0, y: 0 };
        }
        return result;
    }
    calcDist(x: number, y: number) {
        return Math.sqrt(x * x + y * y)
    }
    getTriangleVals(now: T_obj, last: T_obj) {
        let x = now.x - last.x,
            y = now.y - last.y,
            h = this.calcDist(x, y),
            a = Math.atan2(y, x) * 180 / Math.PI;
        return { x, y, h, a };
    }
    correctRangeTrans() {
        let x = this.clampVals('translate', this.nowVals.translate.x),
            y = this.clampVals('translate', this.nowVals.translate.y),
            tmpX,
            tmpY,
            diffX,
            diffY,
            diffH;
        if (x !== this.nowVals.translate.x) {
            tmpX = x;
            diffX = x - this.lastVals.translate.x;
        } else {
            tmpX = this.nowVals.translate.x;
            diffX = this.diffVals.translate.x;
        }
        if (y !== this.nowVals.translate.y) {
            tmpY = y;
            diffY = y - this.lastVals.translate.y;
        } else {
            tmpY = this.nowVals.translate.y;
            diffY = this.diffVals.translate.y;
        }
        diffH = this.calcDist(diffX, diffY);
        this.diffVals.translate = { x: diffX, y: diffY, h: diffH }
        this.nowVals.translate = { x: tmpX, y: tmpY }
    }
    correctRangeRotate() {
        this.nowVals.rotate = this.clampVals('rotate', this.nowVals.rotate);
        let tmp = this.nowVals.rotate + this.diffVals.rotate;
        if (tmp >= this.options.rotate.max) {
            this.diffVals.rotate = this.options.rotate.max - this.nowVals.rotate;
        } else if (tmp <= this.options.rotate.min) {
            this.diffVals.rotate = this.options.rotate.min - this.nowVals.rotate;
        }
        this.nowVals.rotate += this.diffVals.rotate;
    }
    correctDiffRotate() {
        if (this.triangleVals.now.a > 0 && this.triangleVals.last.a < 0) {
            if (this.triangleVals.now.a > 90 && this.triangleVals.last.a < -90) {
                this.diffVals.rotate = 360 - this.diffVals.rotate;
            } else if (this.triangleVals.now.a < 90 && this.triangleVals.last.a > -90) {
                this.diffVals.rotate = - this.diffVals.rotate;
            }
        } else if (this.triangleVals.now.a < 0 && this.triangleVals.last.a > 0) {
            if (this.triangleVals.now.a > -90 && this.triangleVals.last.a < 90) {
                this.diffVals.rotate = - this.diffVals.rotate;
            } else if (this.triangleVals.now.a < -90 && this.triangleVals.last.a > 90) {
                this.diffVals.rotate = -(360 + this.diffVals.rotate);
            }
        }
    }
    correctRangeScale() {
        this.nowVals.scale.x = this.clampVals('scale', this.nowVals.scale.x);
        this.nowVals.scale.y = this.clampVals('scale', this.nowVals.scale.y);
        let tmpX = this.nowVals.scale.x + this.diffVals.scale,
            tmpY = this.nowVals.scale.y + this.diffVals.scale;
        if (tmpX >= this.options.scale.max || tmpY >= this.options.scale.max) {
            this.diffVals.scale = Math.min((this.options.scale.max - this.nowVals.scale.x), (this.options.scale.max - this.nowVals.scale.y));
        } else if (tmpX <= this.options.scale.min || tmpY <= this.options.scale.min) {
            this.diffVals.scale = Math.min((this.options.scale.min - this.nowVals.scale.x), (this.options.scale.min - this.nowVals.scale.y));
        }
        this.nowVals.scale.x += this.diffVals.scale;
        this.nowVals.scale.y += this.diffVals.scale;
    }
    clampVals(prop: 'scale' | 'rotate' | 'translate', value: number) {
        return clampVal({ val: value, min: this.options[prop].min, max: this.options[prop].max });
    }
    correctWheelDirection(value: boolean) {
        return this.options.step.reverse ? !value : value;
    }
    getStepVal() {
        if (!this.options.step.value) {
            this.stepVal = this.options.step.mode === 'translate' ? 60 : this.options.step.mode === 'scale' ? 0.5 : this.options.step.mode === 'rotate' ? 10 : 0;
        } else {
            this.stepVal = this.options.step.value = 0;
        }
    }
    getSizeDiff(val?: T_obj) {
        this.sizes = {
            viewport: { width: 0, height: 0, left: 0, right: 0, top: 0, bottom: 0 },
            target: { width: 0, height: 0, left: 0, right: 0, top: 0, bottom: 0 },
            diff: { x: 0, y: 0 },
            offset: { left: 0, right: 0, top: 0, bottom: 0 }
        }
        if (!this.viewportEl) {
            this.sizes.viewport = {
                width: document.documentElement.clientWidth,
                height: document.documentElement.clientHeight,
                left: 0,
                top: 0,
                right: document.documentElement.clientWidth,
                bottom: document.documentElement.clientHeight
            }
        } else {
            let tmp = this.viewportEl.getBoundingClientRect();
            this.sizes.viewport = {
                width: tmp.width,
                height: tmp.height,
                left: tmp.left,
                top: tmp.top,
                right: tmp.right,
                bottom: tmp.bottom,
            }
        }
        let rect = this.targetEl.getBoundingClientRect();
        this.sizes.target = {
            width: rect.width,
            height: rect.height,
            left: rect.left,
            top: rect.top,
            right: rect.right,
            bottom: rect.bottom,
        }
        this.sizes.diff = { x: this.sizes.viewport.width - this.sizes.target.width, y: this.sizes.viewport.height - this.sizes.target.height }
        this.sizes.offset = {
            left: this.sizes.viewport.left - this.sizes.target.left,
            right: this.sizes.viewport.right - this.sizes.target.right,
            top: this.sizes.viewport.top - this.sizes.target.top,
            bottom: this.sizes.viewport.bottom - this.sizes.target.bottom,
        }
        /* this.crossShape = (this.sizes.diff.x > 0 && this.sizes.diff.y > 0) ? 'baby' :
            (this.sizes.diff.y > 0 && this.sizes.diff.x <= 0) ? 'river' :
                (this.sizes.diff.x > 0 && this.sizes.diff.y <= 0) ? 'chimney' : 'cover'; */
        this.getViewRange(val);
    }
    getViewRange(val?: T_obj) {
        let value = val || transformTools.get(this.targetEl, ['translate'])['translate'];
        this.viewRange = {
            x: [value.x + this.sizes.offset.left, value.x + this.sizes.offset.right].sort(),
            y: [value.y + this.sizes.offset.top, value.y + this.sizes.offset.bottom].sort(),
        }
    }
    getDmpVals(value: number) {
        let dmp = this.options.viewport.dmpRatio ** 2;
        return this.options.viewport.bouncy ? value * dmp : value;
    }
    getBounceVals(data: T_obj) {
        let x = data.x, y = data.y;
        if (x < this.viewRange.x[0]) {
            x += this.getDmpVals(this.viewRange.x[0] - x);
        } else if (x > this.viewRange.x[1]) {
            x += this.getDmpVals(this.viewRange.x[1] - x);
        }
        if (y < this.viewRange.y[0]) {
            y += this.getDmpVals(this.viewRange.y[0] - y);
        } else if (y > this.viewRange.y[1]) {
            y += this.getDmpVals(this.viewRange.y[1] - y);
        }
        return { x: ~~x, y: ~~y };
    }
    getViewLimitVals(data: T_obj) {
        let x = clampVal({ val: data.x, min: this.viewRange.x[0], max: this.viewRange.x[1] }),
            y = clampVal({ val: data.y, min: this.viewRange.y[0], max: this.viewRange.y[1] });
        return { x, y }
    }
    getAutoDur(val1: number, val2: number) {
        return Math.abs(val1 - val2) / 3 + 200;
    }
    rebound(val?: T_obj) {
        let tmp = val || this.nowVals.translate,
            dur;
        if (tmp.x < this.viewRange.x[0]) {
            dur = this.getAutoDur(this.viewRange.x[0], tmp.x);
            tmp.x = this.viewRange.x[0];
        } else if (tmp.x > this.viewRange.x[1]) {
            dur = this.getAutoDur(this.viewRange.x[1], tmp.x);
            tmp.x = this.viewRange.x[1];
        }
        if (tmp.y < this.viewRange.y[0]) {
            dur = this.getAutoDur(this.viewRange.y[0], tmp.y);
            tmp.y = this.viewRange.y[0];
        } else if (tmp.y > this.viewRange.y[1]) {
            dur = this.getAutoDur(this.viewRange.y[1], tmp.y);
            tmp.y = this.viewRange.y[1];
        }
        this.targetEl.style.transitionDuration = `${this.options.viewport.duration || dur}ms`;
        transformTools.set({
            el: this.targetEl,
            data: { translate: { x: tmp.x, y: tmp.y } }
        });
    }
    reset(cb?: T_fn_none) {
        this.targetEl.style.transform = this.initialVals;
        super.listen({ name: 'reset', cb });
        return this;
    }
    destroy(cb?: T_fn_none) {
        if (this.destroyed) {
            return this;
        }
        this.setCompleted();
        this.targetEl.removeEventListener('pointerdown', this.triggerFn);
        this.targetEl.removeEventListener("transitionend", this.transitionendFn);
        this.isMobi && this.targetEl.removeEventListener('touchstart', this.preventDft);
        this.removeEvents();
        if (!this.isMobi) {
            this.options.wheel && this.targetEl.removeEventListener(this.scrollObj.event, this.wheelFn);
            this.options.keyboard.enable && window.removeEventListener('keydown', this.keyboardFn);
            this.options.click.hold2Menu && this.targetEl.removeEventListener('contextmenu', this.menuFn);
            this.spyIns && this.spyIns.destroy();
        }
        this.destroyed = true;
        super.listen({ name: 'destroy', cb });
        return this;
    }
}
export default Gesture;
