/**  
 * @since Last modified: 2024-09-17 22:27:27
 * @class Position
 * @description Relative positioning of two nodes.
 * @param {getEl} target - Host button element. 
 * @param {getEl} bubble - Guest bubble element. 
 * @param {object} [options] - Parameters 
 * @augments Common
 * @see {@link https://codepen.io/axui/pen/ZEwmEdb|demo @ codepen}
 * @example
 * ax.Position('#target','#bubble');
 */
import getEl from '../utils/getEl';
import extend from '../utils/extend';
import offset from '../utils/offset';
import createEl from '../utils/createEl';
import elState from '../utils/elState';
import toNumber from '../utils/toNumber';
import toPixel from '../utils/toPixel';
import { T_obj, T_el, T_sel, T_required, T_fn } from '../../../types/ax';
import PubBase from './PubBase';
import PubListen from './PubListen';
import pipe from '../utils/pipe';
import { T_position } from '../../../types/position';
import style from '../utils/style';
import spreadBool from '../utils/spreadBool';
import instance from '../utils/instance';
import optPosition from '../options/optPosition';
import PubUpdate from './PubUpdate';
import PubCommon from './PubCommon';
import Message from './Message';
import Hover from './Hover';
import Observe from './Observe';
import ax from '../ax';
import config from '../global/config';
import alias from '../global/alias';
class Position extends pipe(PubListen, PubCommon)(PubBase) {
    targetEl: any;
    options: T_required<T_position<0>> | T_obj = {};
    destroyed: any;
    plans: any;
    bubbleEl: any;
    arrowEl: any;
    gap: any;
    parent: any;
    placement: any;
    observerCount: any;
    completeCount: any;
    trigger: any;
    targetAttrsObserver: any;
    targetSizeObserver: any;
    bubbleAttrsObserver: any;
    bubbleSizeObserver: any;
    parentObserver: any;
    targetData: any;
    bubbleData: any;
    browserData: any;
    static hostType = 'none';
    constructor(target: T_sel, bubble: T_sel, options?: T_position, initial: boolean = config.initial) {
        super();
        this.ready({
            options,
            host: target,
            maps: optPosition,
            spread: ['arrow', 'parentMutation', 'targetObserve', 'bubbleObserve']
        });
        this.timestamp = Date.now();
        /**
       * @name Position.bubbleEl
       * @description BOX node,is guest node,used to store content.
       * @type HTMLElement
       */
        this.bubbleEl = getEl(bubble);
        if (!this.bubbleEl) {
            throw new Error(`The host element and guest element must be exist!`);
        }
        /**
        * @name Position.gap
        * @description The gap between BTN and BOX.
        * @type number
        */
        this.gap = toPixel(this.options.arrow.gap);
        /**
        * @name Position.parent
        * @description BTN's parent node, the change of HTML of the parent node will affect the relative position of BTN.
        * @type HTMLElement
        */
        this.parent = this.options.parentMutation.enable && getEl(this.options.parentMutation.selector) ? getEl(this.options.parentMutation.selector) : null;
        this.regularPlaces = [
            'left-start', 'left-center', 'left-end',
            'right-start', 'right-center', 'right-end',
            'top-start', 'top-center', 'top-end',
            'bottom-start', 'bottom-center', 'bottom-end',
        ]
        this.specialPlaces = ['left-max', 'right-max', 'top-max', 'bottom-max', 'center', 'max',]
        this.places = [...this.regularPlaces, ...this.specialPlaces]
        this.observerCount = 0;
        this.completeCount = 0;
        this.timer = 0;
        this.trigger = () => {
            if(elState(this.bubbleEl).isUncalc){return;};
            this.observerCount++;
            this.complete(this.observerCount);
        }
        this.targetAttrsObserver = new MutationObserver((mutations) => {
            for (let mutation of mutations) {
                if (this.options.targetObserve.attrs.includes(mutation.attributeName as string)) {
                    this.trigger();
                }
            }
        });
        this.targetSizeObserver = new ResizeObserver(() => {
            this.trigger();
        });
        this.bubbleAttrsObserver = new MutationObserver((mutations) => {
            for (let mutation of mutations) {
                if (this.options.bubbleObserve.attrs.includes(mutation.attributeName as string)) {
                    this.trigger();
                }
            }
        });
        this.bubbleSizeObserver = new ResizeObserver(() => {
            this.trigger();
        });
        this.parentObserver = new MutationObserver(() => {
            if (!this.unsettable('target') && this.targetData) {
                if (offset(this.targetEl).left !== this.targetData.left || offset(this.targetEl).top !== this.targetData.top) {
                   this.trigger();
                }
            }
        });
        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?: T_fn): Promise<any> {
        super.listen({ name: 'launch' });
        this.options.b4Init && await this.options.b4Init.call(this);
        /**
        * @name Position.placement
        * @description Box's position relative to BTN.
        * @type string
        */
        this.placement = this.correctPlace(this.options.placement);
        if (!this.places.includes(this.placement)) {
            return this;
        }
        this.options.placement = this.placement;
        this.fullGap = getComputedStyle(document.documentElement).getPropertyValue('--_g-full').trim();
        if (this.placement !== 'center' && this.placement !== 'max') {
            this.createArrow();
        }
        this.setAttrs();
        this.resetPlacement();
        this.options.enable ? this.setObservers() : null;
        super.listen({ name: 'init', cb });
        return this;
    }
    setObservers() {
        window.addEventListener("resize", this.trigger);
        window.addEventListener('scroll', this.trigger);
        if (this.options.targetObserve.enable) {
            if (this.options.targetObserve.attrs.length > 0) {
                this.targetAttrsObserver.observe(this.targetEl, {
                    attributes: true,
                });
            }
            this.options.targetObserve.size ? this.targetSizeObserver.observe(this.targetEl) : null;
        }
        if (this.options.bubbleObserve.enable) {
            if (this.options.bubbleObserve.attrs.length > 0) {
                this.bubbleAttrsObserver.observe(this.bubbleEl, {
                    attributes: true,
                });
            }
            this.options.bubbleObserve.size ? this.bubbleSizeObserver.observe(this.bubbleEl) : null;
        }
        if (this.parent) {
            this.parentObserver.observe(this.parent, this.options.parentMutation.options);
        }
    }
    removeObservers() {
        if (this.options.targetObserve.enable) {
            this.options.targetObserve.attrs.length > 0 ? this.targetAttrsObserver.disconnect() : null;
            this.options.targetObserve.size ? this.targetSizeObserver.unobserve(this.targetEl) : null;
        }
        if (this.options.bubbleObserve.enable) {
            this.options.bubbleObserve.attrs.length > 0 ? this.bubbleAttrsObserver.disconnect() : null;
            this.options.bubbleObserve.size ? this.bubbleSizeObserver.unobserve(this.bubbleEl) : null;
        }
        this.parent ? this.parentObserver.disconnect() : null;
        window.removeEventListener('resize', this.trigger);
        window.removeEventListener('scorll', this.trigger);
    }
    correctPlace(value: string) {
        return (value === 'left') ? 'left-center' :
            (value === 'right') ? 'right-center' :
                (value === 'top') ? 'top-center' :
                    (value === 'bottom') ? 'bottom-center' : value;
    }
    complete(count: number) {
        /*         setTimeout(() => {
                    if (this.observerCount === count) {
                        this.resetPlacement();
                        this.observerCount = 0;
                    }
                }, this.options.delay);
         */
        clearInterval(this.timer);
        this.timer = setInterval(() => {
            if (this.observerCount === count) {
                this.resetPlacement();
                this.observerCount = 0;
                clearInterval(this.timer);
            }
        }, this.options.delay);
    }
    /**
    * @description Set attributes and styles to the target node after initialization.
    * @returns null
    */
    setAttrs() {
        this.bubbleEl.style.position = (['max', 'center'].includes(this.placement)) ? 'fixed' : 'absolute';
        this.setPlacementAttr();
    }
    setPlacementAttr() {
        this.bubbleEl.setAttribute('placement', this.placement);
    }
    createArrow() {
        if (this.options.arrow.enable) {
            this.arrowEl = createEl('i', { style: 'position:absolute', [alias]: 'arrow' });
            this.bubbleEl.appendChild(this.arrowEl);
        }
    }
    setArrow(placement: string) {
        let [left, right, top, bottom] = ['auto', 'auto', 'auto', 'auto'],
            exceedWidth = this.targetData.width > this.bubbleData.width / 2,
            exceedHeight = this.targetData.height > this.bubbleData.height / 2,
            arrowHalf = toPixel(getComputedStyle(this.arrowEl).width) / 2,
            arrowOffset = toPixel(this.options.arrow.offset),
            arrowRotate = '';
        if (placement.includes('top') || placement.includes('bottom')) {
            if (placement.includes('max')) {
                let targetLeft = this.targetData.offsetLeft + this.targetData.width / 2,
                    exceedStart = targetLeft < this.bubbleData.offsetLeft + arrowOffset - arrowHalf,
                    exceedEnd = targetLeft > this.bubbleData.offsetRight - arrowOffset + arrowHalf;
                left = (exceedEnd || exceedStart) ? '-10000px' : (this.targetData.offsetLeft - this.bubbleData.offsetLeft) + this.targetData.width / 2 - arrowHalf + 'px';
            } else if (placement.includes('start')) {
                left = !exceedWidth ? Math.floor(this.targetData.width / 2 - arrowHalf) + 'px' : this.options.arrow.offset;
            } else if (placement.includes('end')) {
                right = !exceedWidth ? Math.floor(this.targetData.width / 2 - arrowHalf) + 'px' : this.options.arrow.offset;
            } else {
                left = this.bubbleData.width / 2 - 1 - arrowHalf + 'px';
            }
        } else if (placement.includes('left') || placement.includes('right')) {
            if (placement.includes('max')) {
                let targetTop = this.targetData.offsetTop + this.targetData.height / 2,
                    exceedStart = targetTop < this.bubbleData.offsetTop + arrowOffset - arrowHalf,
                    exceedEnd = targetTop > this.bubbleData.offsetBottom - arrowOffset + arrowHalf;
                top = (exceedEnd || exceedStart) ? '-10000px' : (this.targetData.offsetTop - this.bubbleData.offsetTop) + this.targetData.height / 2 - arrowHalf + 'px';
            } else if (placement.includes('start')) {
                top = !exceedHeight ? Math.floor(this.targetData.height / 2 - arrowHalf) + 'px' : this.options.arrow.offset;
            } else if (placement.includes('end')) {
                bottom = !exceedHeight ? Math.floor(this.targetData.height / 2 - arrowHalf) + 'px' : this.options.arrow.offset;
            } else {
                top = this.bubbleData.height / 2 - 1 - arrowHalf + 'px';
            }
        } else {
            left = '-10000px';
            top = '-10000px';
        }
        if (placement.includes('top')) {
            bottom = '-' + this.options.arrow.size;
            arrowRotate = '45';
        } else if (placement.includes('bottom')) {
            top = '-' + this.options.arrow.size;
            arrowRotate = '-135';
        } else if (placement.includes('left')) {
            right = '-' + this.options.arrow.size;
            arrowRotate = '-45';
        } else if (placement.includes('right')) {
            left = '-' + this.options.arrow.size;
            arrowRotate = '135';
        }
        this.arrowEl.style.left = left;
        this.arrowEl.style.right = right;
        this.arrowEl.style.top = top;
        this.arrowEl.style.bottom = bottom;
        this.arrowEl.style.transform = `rotate(${arrowRotate}deg)`;
    }
    getRectsData() {
        this.bodyData = {
            width: document.body.offsetWidth,
            height: document.body.offsetHeight,
        }
        this.browserData = {
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight,
            scrollTop: document.documentElement.scrollTop || document.body.scrollTop,
            scrollLeft: document.documentElement.scrollLeft || document.body.scrollLeft
        }
        let targetRect = this.targetEl.getBoundingClientRect();
        this.targetData = {
            width: targetRect.width,
            height: targetRect.height,
            offsetLeft: targetRect.left,
            offsetRight: targetRect.right,
            offsetTop: targetRect.top,
            offsetBottom: targetRect.bottom,
            gapRight: this.browserData.width - targetRect.right,
            gapBottom: this.browserData.height - targetRect.bottom,
            ...offset(this.targetEl)
        };
        this.getBubbleData();
    }
    getBubbleData() {
        let bubbleRect = this.bubbleEl.getBoundingClientRect();
        this.bubbleData = {
            width: bubbleRect.width,
            height: bubbleRect.height,
            offsetLeft: bubbleRect.left,
            offsetRight: bubbleRect.right,
            offsetTop: bubbleRect.top,
            offsetBottom: bubbleRect.bottom,
            gapRight: this.browserData.width - bubbleRect.right,
            gapBottom: this.browserData.height - bubbleRect.bottom
        };
    }
    /**
    * @description Set the position of BOX according to placement.
    * @param {string} placement - Same as "options.placement".
    * @returns null
    */
    setPlacement(placement: string) {
        let [left, right, top, bottom] = ['auto', 'auto', 'auto', 'auto'];
        if (placement.includes('bottom') || placement.includes('top')) {
            if (placement.includes('bottom')) {
                left = placement === 'bottom' || placement === 'bottom-center' ? (this.targetData.left - (this.bubbleData.width - this.targetData.width) / 2) :
                    placement === 'bottom-start' ? this.targetData.left :
                        placement === 'bottom-end' ? this.targetData.left + this.targetData.width - this.bubbleData.width : 0;
                top = `${toNumber(this.targetData.top + this.targetData.height + this.gap, { mode: 'ceil' })}px`;
                left = `${toNumber(left, { mode: 'ceil' })}px`;
            } else if (placement.includes('top')) {
                left = placement === 'top' || placement === 'top-center' ? (this.targetData.left - (this.bubbleData.width - this.targetData.width) / 2) :
                    placement === 'top-start' ? this.targetData.left :
                        placement === 'top-end' ? (this.targetData.left + this.targetData.width - this.bubbleData.width) : 0;
                bottom = `${toNumber((this.bodyData.height - this.targetData.top + this.gap), { mode: 'ceil' })}px`;
                left = `${toNumber(left, { mode: 'ceil' })}px`;
            }
        } else if (placement.includes('left') || placement.includes('right')) {
            if (placement.includes('left')) {
                if (placement === 'left' || placement === 'left-center') {
                    top = `${toNumber(this.targetData.top - (this.bubbleData.height - this.targetData.height) / 2, { mode: 'ceil' })}px`;
                } else if (placement === 'left-start') {
                    top = `${toNumber(this.targetData.top, { mode: 'ceil' })}px`;
                } else if (placement === 'left-end') {
                    bottom = `${toNumber(this.bodyData.height - this.targetData.top - this.targetData.height, { mode: 'ceil' })}px`;
                }
                right = `${toNumber(this.browserData.width - this.targetData.left + this.gap, { mode: 'ceil' })}px`;
            } else if (placement.includes('right')) {
                if (placement === 'right' || placement === 'right-center') {
                    top = `${toNumber(this.targetData.top - (this.bubbleData.height - this.targetData.height) / 2, { mode: 'ceil' })}px`;
                } else if (placement === 'right-start') {
                    top = `${toNumber(this.targetData.top, { mode: 'ceil' })}px`;
                } else if (placement === 'right-end') {
                    bottom = `${toNumber(this.bodyData.height - this.targetData.top - this.targetData.height, { mode: 'ceil' })}px`;
                }
                left = `${toNumber(this.targetData.left + this.targetData.width + this.gap, { mode: 'ceil' })}px`;
            }
        }
        if (this.placement === 'max') {
            this.bubbleEl.style.width = `calc(100vw - ${this.fullGap}*2)`;
            this.bubbleEl.style.height = `calc(100vh - ${this.fullGap}*2)`;
            left = this.fullGap;
            top = this.fullGap;
        } else if (this.placement === 'center') {
            this.bubbleEl.style.marginLeft = `-${toNumber(this.bubbleEl.getBoundingClientRect().width / 2, { mode: 'ceil' })}px`;
            this.bubbleEl.style.marginTop = `-${toNumber(this.bubbleEl.getBoundingClientRect().height / 2, { mode: 'ceil' })}px`;
            left = '50%';
            top = '50%';
        } else if (this.placement === 'top-max' || this.placement === 'bottom-max') {
            left = `${toNumber(this.browserData.scrollLeft + toPixel(this.fullGap), { mode: 'ceil' })}px`;
            this.bubbleEl.style.width = `calc(100vw - ${this.fullGap}*2)`;
        } else if (this.placement === 'left-max' || this.placement === 'right-max') {
            top = `${toNumber(this.browserData.scrollTop + toPixel(this.fullGap), { mode: 'ceil' })}px`;
            this.bubbleEl.style.height = `calc(100vh - ${this.fullGap}*2)`;
        }
        this.bubbleEl.style.left = left;
        this.bubbleEl.style.right = right;
        this.bubbleEl.style.top = top;
        this.bubbleEl.style.bottom = bottom;
        this.getBubbleData();
        this.arrowEl ? this.setArrow(placement) : null;
    }
    resetBubbleAttrs() {
        this.bubbleEl.style.cssText = this.bubbleEl.style.cssText.replace('margin-left:', '').replace('margin-top:', '').replace('width:', '').replace('height:', '');
        this.bubbleEl.style.left = 'auto';
        this.bubbleEl.style.right = 'auto';
        this.bubbleEl.style.top = 'auto';
        this.bubbleEl.style.bottom = 'auto';
        this.getBubbleData();
    }
    getSurroundGap() {
        let left = this.targetData.offsetLeft - this.bubbleData.width - this.gap,
            top = this.targetData.offsetTop - this.bubbleData.height - this.gap,
            right = this.browserData.width - this.targetData.offsetRight - this.bubbleData.width - this.gap,
            bottom = this.browserData.height - this.targetData.offsetBottom - this.bubbleData.height - this.gap,
            startTop = this.targetData.offsetTop,
            startBottom = this.browserData.height - (startTop + this.bubbleData.height),
            centerTop = this.targetData.offsetTop - (this.bubbleData.height - this.targetData.height) / 2,
            centerBottom = this.browserData.height - (centerTop + this.bubbleData.height),
            endTop = this.targetData.offsetBottom - this.bubbleData.height,
            endBottom = this.browserData.height - this.targetData.offsetBottom,
            startLeft = this.targetData.offsetLeft,
            startRight = this.browserData.width - (startLeft + this.bubbleData.width),
            centerLeft = this.targetData.offsetLeft - (this.bubbleData.width - this.targetData.width) / 2,
            centerRight = this.browserData.width - (centerLeft + this.bubbleData.width),
            endLeft = this.targetData.offsetRight - this.bubbleData.width,
            endRight = this.browserData.width - this.targetData.offsetRight;
        return {
            left, right, top, bottom,
            start: { left: startLeft, right: startRight, top: startTop, bottom: startBottom },
            center: { left: centerLeft, right: centerRight, top: centerTop, bottom: centerBottom },
            end: { left: endLeft, right: endRight, top: endTop, bottom: endBottom }
        };
    }
    fixPlacement() {
        if (this.targetData.gapRight > this.browserData.width || this.targetData.offsetLeft > this.browserData.width || ['max', 'center'].includes(this.placement)) {
            return this;
        }
        let gaps = this.getSurroundGap();
        if (this.placement.includes('top') && (gaps.top < 0 && gaps.bottom >= 0)) {
            this.placement = this.placement.replace('top', 'bottom');
        } else if (this.placement.includes('bottom') && (gaps.bottom < 0 && gaps.top >= 0)) {
            this.placement = this.placement.replace('bottom', 'top');
        } else if (this.placement.includes('left') && (gaps.left < 0 && gaps.right >= 0)) {
            this.placement = this.placement.replace('left', 'right');
        } else if (this.placement.includes('right') && (gaps.right < 0 && gaps.left >= 0)) {
            this.placement = this.placement.replace('right', 'left');
        }
        if (!this.placement.includes('-max')) {
            if ((this.placement.includes('top') || this.placement.includes('bottom')) && this.bubbleData.width < this.browserData.width) {
                if (this.bubbleData.width > this.targetData.width) {
                    if (this.targetData.offsetLeft < 0 && this.targetData.gapRight < this.browserData.width) {
                        this.placement = this.placement.replace('end', 'start').replace('center', 'start');
                    } else if (this.targetData.gapRight < 0 && this.targetData.offsetLeft < this.browserData.width) {
                        this.placement = this.placement.replace('start', 'end').replace('center', 'end');
                    } else {
                        if (gaps.end.left < 0 && gaps.center.left < 0) {
                            this.placement = this.placement.replace('end', 'start').replace('center', 'start');
                        } else if (gaps.end.left < 0 && this.placement.includes('end')) {
                            this.placement = this.placement.replace('end', 'center');
                        } else if (gaps.end.left < 0 && gaps.center.left > 0 && this.placement.includes('start') && (this.options.placement.includes('end') || this.options.placement.includes('center'))) {
                            this.placement = this.placement.replace('start', 'center');
                        } else if (gaps.end.left > 0 && this.placement.includes('center') && (this.options.placement.includes('end'))) {
                            this.placement = this.placement.replace('center', 'end');
                        }
                        if (gaps.start.right < 0 && gaps.center.right < 0) {
                            this.placement = this.placement.replace('start', 'end').replace('center', 'end');
                        } else if (gaps.start.right < 0 && this.placement.includes('start')) {
                            this.placement = this.placement.replace('start', 'center');
                        } else if (gaps.start.right < 0 && gaps.center.right > 0 && this.placement.includes('end') && (this.options.placement.includes('start') || this.options.placement.includes('center'))) {
                            this.placement = this.placement.replace('end', 'center');
                        } else if (gaps.start.right > 0 && this.placement.includes('center') && (this.options.placement.includes('start'))) {
                            this.placement = this.placement.replace('center', 'start');
                        }
                    }
                } else {
                    if (gaps.start.left < 0 && gaps.center.left < 0) {
                        this.placement = this.placement.replace('start', 'end').replace('center', 'end');
                    } else if (gaps.start.left < 0 && this.placement.includes('start')) {
                        this.placement = this.placement.replace('start', 'center');
                    } else if (gaps.start.left < 0 && gaps.center.left > 0 && this.placement.includes('end') && (this.options.placement.includes('start') || this.options.placement.includes('center'))) {
                        this.placement = this.placement.replace('end', 'center');
                    } else if (gaps.start.left > 0 && this.placement.includes('center') && (this.options.placement.includes('start'))) {
                        this.placement = this.placement.replace('center', 'start');
                    }
                    if (gaps.end.right < 0 && gaps.center.right < 0) {
                        this.placement = this.placement.replace('end', 'start').replace('center', 'start');
                    } else if (gaps.end.right < 0 && this.placement.includes('end')) {
                        this.placement = this.placement.replace('end', 'center');
                    } else if (gaps.end.right < 0 && gaps.center.right > 0 && this.placement.includes('start') && (this.options.placement.includes('end') || this.options.placement.includes('center'))) {
                        this.placement = this.placement.replace('start', 'center');
                    } else if (gaps.end.right > 0 && this.placement.includes('center') && (this.options.placement.includes('end'))) {
                        this.placement = this.placement.replace('center', 'end');
                    }
                }
            }
            if ((this.placement.includes('left') || this.placement.includes('right')) && this.bubbleData.height < this.browserData.height) {
                if (this.bubbleData.height > this.targetData.height) {
                    if (this.targetData.offsetTop < 0 && this.targetData.gapBottom < this.browserData.height) {
                        this.placement = this.placement.replace('end', 'start').replace('center', 'start');
                    } else if (this.targetData.gapBottom < 0 && this.targetData.offsetTop < this.browserData.height) {
                        this.placement = this.placement.replace('start', 'end').replace('center', 'end');
                    } else {
                        if (gaps.end.top < 0 && gaps.center.top < 0) {
                            this.placement = this.placement.replace('end', 'start').replace('center', 'start');
                        } else if (gaps.end.top < 0 && this.placement.includes('end')) {
                            this.placement = this.placement.replace('end', 'center');
                        } else if (gaps.end.top < 0 && gaps.center.top > 0 && this.placement.includes('start') && (this.options.placement.includes('end') || this.options.placement.includes('center'))) {
                            this.placement = this.placement.replace('start', 'center');
                        } else if (gaps.end.top > 0 && this.placement.includes('center') && (this.options.placement.includes('end'))) {
                            this.placement = this.placement.replace('center', 'end');
                        }
                        if (gaps.start.bottom < 0 && gaps.center.bottom < 0) {
                            this.placement = this.placement.replace('start', 'end').replace('center', 'end');
                        } else if (gaps.start.bottom < 0 && this.placement.includes('start')) {
                            this.placement = this.placement.replace('start', 'center');
                        } else if (gaps.start.bottom < 0 && gaps.center.bottom > 0 && this.placement.includes('end') && (this.options.placement.includes('start') || this.options.placement.includes('center'))) {
                            this.placement = this.placement.replace('end', 'center');
                        } else if (gaps.start.bottom > 0 && this.placement.includes('center') && (this.options.placement.includes('start'))) {
                            this.placement = this.placement.replace('center', 'start');
                        }
                    }
                } else {
                    if (gaps.start.top < 0 && gaps.center.top < 0) {
                        this.placement = this.placement.replace('start', 'end').replace('center', 'end');
                    } else if (gaps.start.top < 0 && this.placement.includes('start')) {
                        this.placement = this.placement.replace('start', 'center');
                    } else if (gaps.start.top < 0 && gaps.center.top > 0 && this.placement.includes('end') && (this.options.placement.includes('start') || this.options.placement.includes('center'))) {
                        this.placement = this.placement.replace('end', 'center');
                    } else if (gaps.start.top > 0 && this.placement.includes('center') && (this.options.placement.includes('start'))) {
                        this.placement = this.placement.replace('center', 'start');
                    }
                    if (gaps.end.bottom < 0 && gaps.center.bottom < 0) {
                        this.placement = this.placement.replace('end', 'start').replace('center', 'start');
                    } else if (gaps.end.bottom < 0 && this.placement.includes('end')) {
                        this.placement = this.placement.replace('end', 'center');
                    } else if (gaps.end.bottom < 0 && gaps.center.bottom > 0 && this.placement.includes('start') && (this.options.placement.includes('end') || this.options.placement.includes('center'))) {
                        this.placement = this.placement.replace('start', 'center');
                    } else if (gaps.end.bottom > 0 && this.placement.includes('center') && (this.options.placement.includes('end'))) {
                        this.placement = this.placement.replace('center', 'end');
                    }
                }
            }
        }
        this.fullyExpose(gaps);
        this.setPlacementAttr();
    }
    fullyExpose(gaps: any) {
        if (this.options.placement === this.placement || this.placement.includes('max')) {
            return this;
        }
        let placeArr = this.placement.split('-'), parentStr = placeArr[0], childStr = placeArr[1];
        parentStr = (this.options.placement.includes('top') && gaps.top >= 0) ? 'top' :
            (this.options.placement.includes('bottom') && gaps.bottom >= 0) ? 'bottom' :
                (this.options.placement.includes('left') && gaps.left >= 0) ? 'left' :
                    (this.options.placement.includes('right') && gaps.right >= 0) ? 'right' : placeArr[0];
        if (this.options.placement.includes('top') || this.options.placement.includes('bottom')) {
            if (this.options.placement.includes('start') && gaps.start[(this.bubbleData.width > this.targetData.width) ? 'right' : 'left'] >= 0) {
                childStr = 'start';
            } else if (this.options.placement.includes('end') && gaps.end[(this.bubbleData.width > this.targetData.width) ? 'left' : 'right'] >= 0) {
                childStr = 'end';
            } else if (this.options.placement.includes('center') && gaps.center.left >= 0 && gaps.center.right >= 0) {
                childStr = 'center';
            }
        } else if (this.options.placement.includes('left') || this.options.placement.includes('right')) {
            if (this.options.placement.includes('start') && gaps.start[(this.bubbleData.height > this.targetData.height) ? 'bottom' : 'top'] >= 0) {
                childStr = 'start';
            } else if (this.options.placement.includes('end') && gaps.end[(this.bubbleData.height > this.targetData.height) ? 'top' : 'bottom'] >= 0) {
                childStr = 'end';
            } else if (this.options.placement.includes('center') && gaps.center.top >= 0 && gaps.center.bottom >= 0) {
                childStr = 'center';
            }
        }
        this.placement = `${parentStr}-${childStr}`;
    }
    unsettable(type?: string) {
        let el = type === 'target' ? this.targetEl : this.bubbleEl;
        return elState(el).isUncalc;
    }
    /**
    * @description Re-positioning according to this.placement.
    * @param {fn_cb_placement} cb - Callback execution after resetting placement.
    * @returns null
    */
    resetPlacement(cb?: T_fn) {
        if (this.destroyed || !this.places.includes(this.placement) || this.unsettable()) {
            return this;
        }
        this.getRectsData();
        this.resetBubbleAttrs();
        this.fixPlacement();
        this.setPlacement(this.placement);
        super.listen({ name: 'change', cb, params: [this.placement] });
    }
    /**
    * @description Change-positioning according to this.placement.
    * @param {fn_cb_placement} cb - Callback execution after changing placement.
    * @returns {*} Return the instance itself
    */
    change(placement?: string, cb?: T_fn): any {
        if (this.destroyed) {
            return this;
        }
        placement ? this.placement = this.correctPlace(placement) : null;
        if (!this.places.includes(this.placement)) {
            return this;
        }
        this.options.placement = this.placement;
        this.setAttrs();
        this.getRectsData();
        this.resetBubbleAttrs();
        this.setPlacement(this.placement);
        super.listen({ name: 'change', cb, params: [this.placement] });
        return this;
    }
    /**
    * @description Transfer target element.
    * @param {getEl} elem - New target element,The writing is the same as getEl.
    * @param {fn_cb_node} cb - Callback execution after transfering target element.
    * @returns instance itself.
    */
    transfer(elem: HTMLElement | string, cb?: T_fn) {
        if (this.destroyed || !elem) {
            return this;
        }
        this.targetEl = getEl(elem);
        if (this.targetEl) {
            this.resetPlacement(cb);
            super.listen({ name: 'transfer', cb, params: [this.targetEl] });
        }
        return this;
    }
    /**
     * @description Destroy instance and prohibit use.
     * @param {fn_cb_none} [cb] - Callback execution after destruction.
     * @returns {any} - Return the instance itself.
     */
    destroy(cb?: T_fn): any {
        if (this.destroyed) {
            return this;
        }
        this.removeObservers();
        this.destroyed = true;
        super.listen({ name: 'destroy', cb, params: [this.placement] });
        return this;
    }
}
export default Position;
