var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { MapToComponentField } from 'vega-slimmer/core';
import { BasePositionCalculationStrategy } from './base-position-calculation-strategy';
import { ScreenScrollPositionUpdate } from './screen-scroll-position-update';
import { tryGetDocument } from '../../../utils/try-get-document';
import { FeatureFlag } from '../../feature-control/feature-flag-controller';
import { isNonNullable } from '../../../types/type-guard';
/**
 * calculate position by screen container
 */
export class ScreenPositionCalculationStrategy extends BasePositionCalculationStrategy {
  constructor() {
    super(...arguments);
    this.scrollFrameRequestId = null;
    this.usePreviousPlacement = false;
    this.scrollPositionUpdateStrategy = new ScreenScrollPositionUpdate();
  }
  /**
   * For screen-positioned appendable we may get a lot of scroll events.
   * This hook throttles the check with requestAnimationFrame and only allows an adjust when
   * the strategy determines we should update.
   *
   * @returns {boolean} True when controller should perform a position adjustment.
   */
  shouldAdjustPosition() {
    if (this.scrollFrameRequestId !== null) {
      return false;
    }
    if (!FeatureFlag.isEnabled('VEGA_ELEMENT_APPENDER.AUTO_PLACEMENT')) {
      this.usePreviousPlacement = true;
      return true;
    }
    if (!this.previousTargetRect) {
      this.previousTargetRect = this.showConfig.target.getBoundingClientRect();
      this.usePreviousPlacement = false;
      return true;
    }
    /**
     * This avoids doing expensive DOM reads on every scroll event, while still
     * updating placement/alignment when the trigger moves and the appendable
     * would be clipped by the viewport (auto placement).
     *
     * The below method is e2e-test covered in @see{module:the-screen-position-scroll-RAFCallback-trigger-as-expected}
     */
    /* istanbul ignore next */
    this.scrollFrameRequestId = requestAnimationFrame(() => {
      var _a;
      this.scrollFrameRequestId = null;
      let viewportWidth = window.innerWidth;
      let viewportHeight = window.innerHeight;
      if (this.showConfig.positionRelativeTo) {
        const containerDom = (_a = tryGetDocument()) === null || _a === void 0 ? void 0 : _a.querySelector(this.showConfig.positionRelativeTo);
        if (containerDom) {
          viewportWidth = containerDom.getBoundingClientRect().width;
          viewportHeight = containerDom.getBoundingClientRect().height;
        }
      }
      const currentTargetRect = this.showConfig.target.getBoundingClientRect();
      const appendableRect = this.showConfig.appendableElement.getBoundingClientRect();
      const { shouldUpdate } = this.scrollPositionUpdateStrategy.shouldUpdatePosition({
        currentTargetRect,
        previousTargetRect: this.previousTargetRect,
        appendableRect,
        viewportWidth,
        viewportHeight,
      });
      this.usePreviousPlacement = !shouldUpdate;
      this.previousTargetRect = currentTargetRect;
    });
    return true;
  }
  /**
   * Clears any pending scroll animation frame callback.
   * Call this when popover is hidden/destroyed to avoid leaks and stale callbacks.
   */
  clearScrollFrame() {
    if (this.scrollFrameRequestId !== null) {
      cancelAnimationFrame(this.scrollFrameRequestId);
      this.scrollFrameRequestId = null;
    }
  }
  /**
   * Get the position of the element in the container according to the target element and the placement
   * If give a translocation, it will apply the translocation to the result.
   *
   * @param {boolean} usePreviousPlacement - Should keep use previous placement and alignment.
   * @returns {ElementPositionBox} A function that takes in a container, element, target, placement, and translocation and
   * returns an ElementPositionBox.
   */
  getPosition(usePreviousPlacement) {
    const stayPlacement = usePreviousPlacement ? this.usePreviousPlacement : false;
    let positionBox;
    if (stayPlacement) {
      positionBox = this.getPositionBox([this.previousPositionBox.placement].filter(isNonNullable), [this.previousPositionBox.alignment].filter(isNonNullable));
      if (!FeatureFlag.isEnabled('VEGA_ELEMENT_APPENDER.AUTO_PLACEMENT')) {
        positionBox.placement = this.previousPositionBox.placement;
        positionBox.alignment = this.previousPositionBox.alignment;
      }
    }
    else {
      positionBox = this.getPositionBox();
    }
    this.previousPositionBox = positionBox;
    return positionBox;
  }
  /**
   * It returns the position of an element relative to its container and also includes the scroll height of container.
   *
   * @returns {ElementBoundBox} An object with the properties width, height, top, and left.
   */
  getTargetBoundBox() {
    const targetRect = this.showConfig.target.getBoundingClientRect();
    return {
      width: targetRect.width,
      height: targetRect.height,
      top: targetRect.top,
      left: targetRect.left,
    };
  }
  /**
   * "Get the width and height of an element."
   *
   * The function takes a single parameter, a container element, and returns an object with two
   * properties: width and height
   *
   * @returns {ElementScrollDimension} An object with two properties: width and height.
   */
  getElementScrollDimension() {
    return {
      width: window.innerWidth,
      height: window.innerHeight,
    };
  }
  /**
   * Whether the alignment space is enough
   *
   * @param {ElementScrollDimension} containerBox - The bounding box of the container element.
   * @param {ElementBoundBox} targetBox - The position of the target element.
   * @param {ElementBoundBox} elementBox - The box of the element that we're trying to position.
   * @param {PlacementType} placement - The placement of the element relative to the target.
   * @param {PlacementType} alignment - The alignment of the element relative to the target.
   * @returns {boolean} return true if it has enough space, otherwise false
   */
  hasEnoughAlignmentSpace(containerBox, targetBox, elementBox, placement, alignment) {
    const left = this.calculateLeft(elementBox, targetBox, placement, alignment);
    const top = this.calculateTop(elementBox, targetBox, placement, alignment);
    return (left >= 0 &&
      top >= 0 &&
      left + elementBox.width <= containerBox.width &&
      top + elementBox.height <= containerBox.height);
  }
  /**
   * Screen-specific suitable placement selection.
   * Tries to find a placement that has enough alignment space within the viewport first,
   * otherwise falls back to the base strategy.
   *
   * @param {ElementScrollDimension} containerBox - The bounding box of the container.
   * @param {ElementBoundBox} elementBox - The box of the element to place.
   * @param {ElementBoundBox} targetBox - The box of the target element.
   * @param {PlacementType[]} placements - Ordered list of placements to try.
   * @returns {Nullable<PlacementType>} The chosen placement.
   */
  getSuitablePlacement(containerBox, elementBox, targetBox, placements) {
    const alignments = this.getAlignments();
    const suitablePlacement = placements.find((placement) => alignments.some((alignment) => this.hasEnoughAlignmentSpace(containerBox, targetBox, elementBox, placement, alignment)));
    if (suitablePlacement) {
      return suitablePlacement;
    }
    return super.getSuitablePlacement(containerBox, elementBox, targetBox, placements);
  }
}
__decorate([
  MapToComponentField()
], ScreenPositionCalculationStrategy.prototype, "showConfig", void 0);
