/**
 * @since Last modified: 2024-09-09 22:37:22
 * @class Hover
 * @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.Hover('#id',{});
 */
import getEl from '../utils/getEl';
import pipe from '../utils/pipe';
import PubBase from './PubBase';
import PubListen from './PubListen';
import optHover from '../options/optHover';
import PubCommon from './PubCommon';
import contains from '../utils/contains';
import throttle from '../utils/throttle';
class Hover extends pipe(PubListen, PubCommon)(PubBase) {
    constructor(elem, options, initial = true) {
        super();
        this.options = {};
        this.ready({
            options,
            maps: optHover,
            host: elem,
        });
        /**
        * @name Hover.targetEl
        * @description Target element.
        * @type getEl
        */
        /**
        * @name Hover.rawOpts
        * @description Raw Parameters of the instance from map.
        * @type object
        */
        /**
         * @name Hover.dftOpts
         * @description Default Parameters of the instance from merging.The reset method requires this variable.
         * @type object
         */
        /**
         * @name Hover.options
         * @description Parameters of the instance.
         * @type object
         */
        /**
         * @name Hover.destroyed
         * @description Destruction state.
         * @type boolean
         */
        /**
        * @name Hover.plans
        * @description Plans for listening.
        * @type object
        */
        /**
        * @name Hover.timestamp
        * @description Instance creation time.
        * @type number
        */
        /**
        * @name Hover.curX
        * @description The x-axis coordinate value of the current cursor.
        * @type number
        */
        /**
        * @name Hover.curY
        * @description The y-axis coordinate value of the current cursor.
        * @type number
        */
        this.curX;
        this.curY;
        /**
       * @name Hover.preX
       * @description The x-axis coordinate value of the last cursor.
       * @type number
       */
        /**
        * @name Hover.preY
        * @description The y-axis coordinate value of the last cursor.
        * @type number
        */
        this.preX;
        this.preY;
        this.enterTimer;
        this.leaveTimer;
        /**
        * @name Hover.isActive
        * @description Whether the target triggered the "enter" event.
        * @type boolean
        */
        this.isActive = false;
        /**
        * @name Hover.holdEl
        * @description Cursor moves to an element node that can still hold state.
        * @type HTMLElement
        */
        this.holdEl = getEl(this.options.hold);
        this.eventToggle = (type, elem, event) => {
            let obj = { event, target: elem };
            if (type === 'enter') {
                this.isActive = true;
                super.listen({ name: 'enter', params: [{ ...obj, inside: true, name: 'enter' }] });
            }
            else if (type === 'leave') {
                this.isActive = false;
                super.listen({ name: 'leave', params: [{ ...obj, inside: false, name: 'leave' }] });
            }
        };
        this.enterCompare = (elem, event) => {
            let distX = this.preX - this.curX, distY = this.preY - this.curY, distance = Math.sqrt(distX ** 2 + distY ** 2);
            if (distance < this.options.threshold) {
                if (!this.isActive) {
                    this.eventToggle('enter', elem, event);
                }
            }
            else {
                this.preX = this.curX;
                this.preY = this.curY;
                this.enterTimer = setTimeout(() => {
                    this.enterCompare(elem, event);
                }, this.options.intvl);
            }
        };
        this.globalEvent = throttle((event) => {
            let inTarget = contains(event.target, this.targetEl), inHold = this.holdEl ? contains(event.target, this.holdEl) : true, inside = inTarget && inHold;
            super.listen({ name: 'move', params: [{ inside, event, name: 'move', target: event.target }] });
        }, this.options.throttle);
        this.moveEvent = (event) => {
            this.curX = event.pageX;
            this.curY = event.pageY;
        };
        this.enterEvent = (event) => {
            clearTimeout(this.leaveTimer);
            this.preX = event.pageX;
            this.preY = event.pageY;
            this.enterTimer = setTimeout(() => {
                this.enterCompare(event.target, event);
            }, this.options.intvl);
        };
        this.leaveEvent = (event) => {
            clearTimeout(this.enterTimer);
            this.leaveTimer = setTimeout(() => {
                this.eventToggle('leave', event.target, event);
            }, this.options.timeout);
        };
        this.enterHold = () => {
            clearTimeout(this.leaveTimer);
        };
        this.leaveHold = (event) => {
            clearTimeout(this.enterTimer);
            this.leaveTimer = setTimeout(() => {
                this.eventToggle('leave', this.targetEl, event);
            }, this.options.timeout);
        };
        super.listen({ name: 'ready' });
        initial && this.init();
    }
    /**
    * @description Initialization instance.
    * @param {fn_cb_none} [cb] - Callback execution after initialization.
    * @returns {*} Return the instance itself.
    */
    async init(cb) {
        super.listen({ name: 'launch' });
        this.options.b4Init && await this.options.b4Init.call(this);
        document.body.removeEventListener('mousemove', this.globalEvent);
        this.targetEl.removeEventListener('mousemove', this.moveEvent);
        this.targetEl.removeEventListener('mouseenter', this.enterEvent);
        this.targetEl.removeEventListener('mouseleave', this.leaveEvent);
        document.body.addEventListener('mousemove', this.globalEvent);
        this.targetEl.addEventListener('mousemove', this.moveEvent);
        this.targetEl.addEventListener('mouseenter', this.enterEvent);
        this.targetEl.addEventListener('mouseleave', this.leaveEvent);
        if (this.holdEl) {
            this.holdEl.removeEventListener('mouseenter', this.enterHold);
            this.holdEl.removeEventListener('mouseleave', this.leaveHold);
            this.holdEl.addEventListener('mouseenter', this.enterHold);
            this.holdEl.addEventListener('mouseleave', this.leaveHold);
        }
        super.listen({ name: 'init', cb });
    }
    /**
     * @description Destroy instance and prohibit use.
     * @param {fn_cb_none} [cb] - Callback execution after destruction.
     * @returns {any} - Return the instance itself.
     */
    destroy(cb) {
        if (this.destroyed) {
            return this;
        }
        document.body.removeEventListener('mousemove', this.globalEvent);
        this.targetEl.removeEventListener('mousemove', this.moveEvent);
        this.targetEl.removeEventListener('mouseenter', this.enterEvent);
        this.targetEl.removeEventListener('mouseleave', this.leaveEvent);
        if (this.holdEl) {
            this.holdEl.removeEventListener('mouseenter', this.enterHold);
            this.holdEl.removeEventListener('mouseleave', this.leaveHold);
        }
        this.destroyed = true;
        super.listen({ name: 'destroy', cb });
        return this;
    }
}
Hover.hostType = 'node';
export default Hover;
