import * as _vue from "vue";

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

import { inject } from 'vue';
import PropTypes from '../_util/vue-types';
import classNames from '../_util/classNames';
import omit from 'omit.js';
import ResizeObserver from '../vc-resize-observer';
import BaseMixin from '../_util/BaseMixin';
import throttleByAnimationFrame from '../_util/throttleByAnimationFrame';
import { ConfigConsumerProps } from '../config-provider';
import warning from '../_util/warning';
import { addObserveTarget, removeObserveTarget, getTargetRect, getFixedTop, getFixedBottom } from './utils';

function getDefaultTarget() {
  return typeof window !== 'undefined' ? window : null;
} // Affix


var AffixProps = {
  /**
   * 距离窗口顶部达到指定偏移量后触发
   */
  offsetTop: PropTypes.number,
  offset: PropTypes.number,

  /** 距离窗口底部达到指定偏移量后触发 */
  offsetBottom: PropTypes.number,

  /** 固定状态改变时触发的回调函数 */
  // onChange?: (affixed?: boolean) => void;

  /** 设置 Affix 需要监听其滚动事件的元素，值为一个返回对应 DOM 元素的函数 */
  target: PropTypes.func.def(getDefaultTarget),
  prefixCls: PropTypes.string,
  onChange: PropTypes.func,
  onTestUpdatePosition: PropTypes.func
};
var AffixStatus = {
  None: 'none',
  Prepare: 'Prepare'
};
var Affix = {
  name: 'AAffix',
  props: AffixProps,
  mixins: [BaseMixin],
  setup: function setup() {
    return {
      configProvider: inject('configProvider', ConfigConsumerProps)
    };
  },
  data: function data() {
    return {
      affixStyle: undefined,
      placeholderStyle: undefined,
      status: AffixStatus.None,
      lastAffix: false,
      prevTarget: null
    };
  },
  beforeMount: function beforeMount() {
    this.updatePosition = throttleByAnimationFrame(this.updatePosition);
    this.lazyUpdatePosition = throttleByAnimationFrame(this.lazyUpdatePosition);
  },
  mounted: function mounted() {
    var _this = this;

    var target = this.target;

    if (target) {
      // [Legacy] Wait for parent component ref has its value.
      // We should use target as directly element instead of function which makes element check hard.
      this.timeout = setTimeout(function () {
        addObserveTarget(target(), _this); // Mock Event object.

        _this.updatePosition();
      });
    }
  },
  updated: function updated() {
    this.measure();
  },
  watch: {
    target: function target(val) {
      var newTarget = null;

      if (val) {
        newTarget = val() || null;
      }

      if (this.prevTarget !== newTarget) {
        removeObserveTarget(this);

        if (newTarget) {
          addObserveTarget(newTarget, this); // Mock Event object.

          this.updatePosition();
        }

        this.prevTarget = newTarget;
      }
    },
    offsetTop: function offsetTop() {
      this.updatePosition();
    },
    offsetBottom: function offsetBottom() {
      this.updatePosition();
    }
  },
  beforeUnmount: function beforeUnmount() {
    clearTimeout(this.timeout);
    removeObserveTarget(this);
    this.updatePosition.cancel(); // https://github.com/ant-design/ant-design/issues/22683

    this.lazyUpdatePosition.cancel();
  },
  methods: {
    getOffsetTop: function getOffsetTop() {
      var offset = this.offset,
          offsetBottom = this.offsetBottom;
      var offsetTop = this.offsetTop;

      if (typeof offsetTop === 'undefined') {
        offsetTop = offset;
        warning(typeof offset === 'undefined', 'Affix', '`offset` is deprecated. Please use `offsetTop` instead.');
      }

      if (offsetBottom === undefined && offsetTop === undefined) {
        offsetTop = 0;
      }

      return offsetTop;
    },
    getOffsetBottom: function getOffsetBottom() {
      return this.offsetBottom;
    },
    // =================== Measure ===================
    measure: function measure() {
      var status = this.status,
          lastAffix = this.lastAffix;
      var target = this.target;

      if (status !== AffixStatus.Prepare || !this.$refs.fixedNode || !this.$refs.placeholderNode || !target) {
        return;
      }

      var offsetTop = this.getOffsetTop();
      var offsetBottom = this.getOffsetBottom();
      var targetNode = target();

      if (!targetNode) {
        return;
      }

      var newState = {
        status: AffixStatus.None
      };
      var targetRect = getTargetRect(targetNode);
      var placeholderReact = getTargetRect(this.$refs.placeholderNode);
      var fixedTop = getFixedTop(placeholderReact, targetRect, offsetTop);
      var fixedBottom = getFixedBottom(placeholderReact, targetRect, offsetBottom);

      if (fixedTop !== undefined) {
        newState.affixStyle = {
          position: 'fixed',
          top: fixedTop,
          width: placeholderReact.width + 'px',
          height: placeholderReact.height + 'px'
        };
        newState.placeholderStyle = {
          width: placeholderReact.width + 'px',
          height: placeholderReact.height + 'px'
        };
      } else if (fixedBottom !== undefined) {
        newState.affixStyle = {
          position: 'fixed',
          bottom: fixedBottom,
          width: placeholderReact.width + 'px',
          height: placeholderReact.height + 'px'
        };
        newState.placeholderStyle = {
          width: placeholderReact.width + 'px',
          height: placeholderReact.height + 'px'
        };
      }

      newState.lastAffix = !!newState.affixStyle;

      if (lastAffix !== newState.lastAffix) {
        this.$emit('change', newState.lastAffix);
      }

      this.setState(newState);
    },
    // @ts-ignore TS6133
    prepareMeasure: function prepareMeasure() {
      this.setState({
        status: AffixStatus.Prepare,
        affixStyle: undefined,
        placeholderStyle: undefined
      });
      this.$forceUpdate(); // Test if `updatePosition` called

      if (process.env.NODE_ENV === 'test') {
        this.$emit('testUpdatePosition');
      }
    },
    updatePosition: function updatePosition() {
      this.prepareMeasure();
    },
    lazyUpdatePosition: function lazyUpdatePosition() {
      var target = this.target;
      var affixStyle = this.affixStyle; // Check position change before measure to make Safari smooth

      if (target && affixStyle) {
        var offsetTop = this.getOffsetTop();
        var offsetBottom = this.getOffsetBottom();
        var targetNode = target();

        if (targetNode && this.$refs.placeholderNode) {
          var targetRect = getTargetRect(targetNode);
          var placeholderReact = getTargetRect(this.$refs.placeholderNode);
          var fixedTop = getFixedTop(placeholderReact, targetRect, offsetTop);
          var fixedBottom = getFixedBottom(placeholderReact, targetRect, offsetBottom);

          if (fixedTop !== undefined && affixStyle.top === fixedTop || fixedBottom !== undefined && affixStyle.bottom === fixedBottom) {
            return;
          }
        }
      } // Directly call prepare measure since it's already throttled.


      this.prepareMeasure();
    }
  },
  render: function render() {
    var _this2 = this;

    var prefixCls = this.prefixCls,
        affixStyle = this.affixStyle,
        placeholderStyle = this.placeholderStyle,
        $slots = this.$slots,
        $props = this.$props;
    var getPrefixCls = this.configProvider.getPrefixCls;
    var className = classNames(_defineProperty({}, getPrefixCls('affix', prefixCls), affixStyle));
    var props = omit($props, ['prefixCls', 'offsetTop', 'offsetBottom', 'target']);
    return _vue.createVNode(ResizeObserver, {
      "onResize": function onResize() {
        _this2.updatePosition();
      }
    }, {
      default: function _default() {
        return [_vue.createVNode("div", _vue.mergeProps(props, {
          "style": placeholderStyle,
          "ref": "placeholderNode"
        }), [_vue.createVNode("div", {
          "class": className,
          "ref": "fixedNode",
          "style": affixStyle
        }, [$slots.default && $slots.default()])])];
      }
    });
  }
};
/* istanbul ignore next */

Affix.install = function (app) {
  app.component(Affix.name, Affix);
};

export default Affix;