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 { isElementVisible } from '../../utils/ui';
import InternalVegaZIndexManager from '../zindex/internal-vega-z-index-manager';
import { LogUtility } from '../../utils/log';
import { InjectVegaSlimmer, createDynamicSlimmer, methodPlaceholder } from 'vega-slimmer/core';
import { PageResizeObserverSlimmer } from '../slimmers/page-resize-observer-slimmer';
import { ElementAppenderContainerController } from '../slimmers/element-appender/element-appender-container-controller';
import { PositionCalculationController } from '../slimmers/position-calculation/position-calculation-controller';
/**
 * This class is responsible for attaching the appendable element to the host element and adjusting its position
 *
 * @property {HTMLElement} showConfigCallback This is the callback function that will be
 * called when the user clicks on the "Configure" button.
 * @property {HTMLElement} showConfigCallbackTarget he component that will be used as the
 * target for the showConfigCallback.
 * @property {HTMLElement} showConfig the show config
 */
export class ElementAppender {
  /**
   * The `constructor` function is used to initialize the `showConfigCallback` and
   * `showConfigCallbackTarget` properties of the class.
   *
   * @param {ShowConfigCallbackType} showConfigCallback - This is the callback function that will be
   * called when the user clicks on the "Configure" button.
   * @param {ComponentInterface} [showConfigCallbackTarget] - The component that will be used as the
   * target for the showConfigCallback.
   */
  constructor(showConfigCallback, showConfigCallbackTarget) {
    var _a;
    this.resizeObserverSlimmer = createDynamicSlimmer(ElementAppender, PageResizeObserverSlimmer, [], [
      { mapFrom: 'connectResizeObserver', mapTo: 'connectResizeObserver' },
      { mapFrom: 'disconnectResizeObserver', mapTo: 'disconnectResizeObserver' },
    ], [
      {
        /**
         * The above code is defining a callback function. Inside the function,
         * it checks if both the `target` and `appendableElement` are visible using the `isElementVisible` function.
         * If both elements are visible, it calls three other functions: `adjustWidth`, `adjustHeight`, and `adjustPosition`.
         * These functions are responsible for adjusting the width, height, and position of the elements respectively.
         * The below method is e2e-test covered in @see{module:element-appender-helper-resize-callback-e2e }
         */
        callback: /* istanbul ignore next */ () => {
          // while target and appendableElement are both visible, we can adjust position.
          // https://gethired.atlassian.net/browse/VD-1087
          if (isElementVisible(this.showConfig.target) &&
            isElementVisible(this.showConfig.appendableElement)) {
            this.adjustPosition();
          }
        },
        // eslint-disable-next-line jsdoc/require-jsdoc, no-restricted-globals
        target: () => [document.body, this.showConfig.appendableElement, this.showConfig.target],
      },
    ]);
    this.elementAppenderContainerController = new ElementAppenderContainerController();
    this.positionCalculationController = new PositionCalculationController();
    this.showConfigCallback = showConfigCallback;
    this.showConfigCallbackTarget = showConfigCallbackTarget;
    const config = this.getShowConfig();
    this.hideElementAppender(config.appendableElement);
    (_a = config.appendableElement) === null || _a === void 0 ? void 0 : _a.classList.add('vega-element-appender-appendable');
  }
  /**
   * It takes an element and appends it to the DOM, then adjusts its position and size to match the
   * target element
   *
   * @returns {void} Nothing is being returned.
   */
  show() {
    this.positionCalculationController.clearScreenPositionScrollFrame();
    this.setShowConfig();
    if (!this.showConfig.target) {
      LogUtility.error(`No target element found, please add it at new elementAppender.`);
      return;
    }
    if (!this.showConfig.appendableElement) {
      LogUtility.error(`No appendable element found, please add it at new elementAppender.`);
      return;
    }
    /* A function that takes no parameters and returns nothing. It sets the position of the appendable
    element. */
    this.prepareShow();
    this.adjustPosition();
    this.setZIndex();
    this.toggleShow(true);
    this.connectResizeObserver();
  }
  /**
   * > If the appendable element exists, hide it
   */
  hide() {
    if (!this.showConfig)
      return;
    this.positionCalculationController.clearScreenPositionScrollFrame();
    this.toggleShow(false);
    this.resetContainerPosition();
    InternalVegaZIndexManager.removeZIndex(this.showConfig.appendableElement, 'base');
  }
  /**
   * Update position while window scroll.
   */
  onScroll() {
    if (this.showConfig.isScreenPosition) {
      this.adjustPosition(true);
    }
  }
  /* istanbul ignore next */
  /**
   * It removes the z-index from the appendable element, removes the appendable element from the DOM, and
   * disconnects the resize observer
   */
  destroy() {
    if (!this.showConfig)
      return;
    this.positionCalculationController.clearScreenPositionScrollFrame();
    this.removeVisibleAppendElement(this.showConfig.appendableElement);
    this.resetContainerPosition();
    InternalVegaZIndexManager.removeZIndex(this.showConfig.appendableElement, 'base');
    this.showConfig.appendableElement.remove();
    this.disconnectResizeObserver();
  }
  /**
   * The function "connectResizeObserver" is used to connect a resize observer.
   */
  connectResizeObserver() {
    methodPlaceholder();
  }
  /**
   * The function disconnects a resize observer.
   */
  disconnectResizeObserver() {
    methodPlaceholder();
  }
  /**
   * Toggle appendableElement show or hide
   *
   * @param {boolean} show remove hidden style if true, otherwise add it
   */
  toggleShow(show) {
    if (show) {
      this.showElementAppender(this.showConfig.appendableElement);
      this.saveVisibleAppendElement(this.showConfig.appendableElement);
    }
    else {
      this.hideElementAppender(this.showConfig.appendableElement);
      this.removeVisibleAppendElement(this.showConfig.appendableElement);
    }
  }
  /**
   * Show the appendable element by removing hidden styles
   * TODO: should directly control display style. https://gethired.atlassian.net/browse/VD-7884
   *
   * @param {HTMLElement} appendableElement - Appendable element controlled by ElementAppender
   */
  showElementAppender(appendableElement) {
    if (appendableElement.classList.contains('date-picker-content-box')) {
      appendableElement.style.display = 'block';
    }
    else {
      appendableElement.classList.remove('vega-force-hidden');
    }
  }
  /**
   * Hide the appendable element by adding hidden styles.
   * TODO: should directly control display style. https://gethired.atlassian.net/browse/VD-7884
   *
   * @param {HTMLElement} appendableElement - Appendable element controlled by ElementAppender
   */
  hideElementAppender(appendableElement) {
    if (!appendableElement)
      return;
    if (appendableElement.classList.contains('date-picker-content-box')) {
      appendableElement.style.display = 'none';
    }
    else {
      appendableElement.classList.add('vega-force-hidden');
    }
  }
  /**
   * Save visible AppendableElement from the container state
   *
   * @param {HTMLElement} appendableElement - Appendable element controlled by ElementAppender
   */
  saveVisibleAppendElement(appendableElement) {
    const containerState = ElementAppender.containerMap.get(this.container);
    if (containerState) {
      containerState.visibleAppendElements.add(appendableElement);
    }
  }
  /**
   * Remove the AppendableElement from the container state.
   * If the container not includes visible element, will remove the container Map.
   *
   * @param {HTMLElement} appendableElement - Appendable element controlled by ElementAppender
   */
  removeVisibleAppendElement(appendableElement) {
    const containerState = ElementAppender.containerMap.get(this.container);
    if (containerState) {
      containerState.visibleAppendElements.delete(appendableElement);
    }
  }
  /**
   * The below method is e2e-test covered in @see {module:should-not-reset-dropdown-append-container-position-style-to-origin-when-there-is-another-dropdown-need-to-show}
   */
  resetContainerPosition() {
    const containerState = ElementAppender.containerMap.get(this.container);
    if (containerState) {
      const { visibleAppendElements, originPosition = 'static' } = containerState;
      if (visibleAppendElements.size === 0) {
        this.container.style.position = originPosition;
        ElementAppender.containerMap.delete(this.container);
      }
    }
  }
  /**
   * If the showConfigCallback is a function, then call it and set the showConfig to the result.
   * Otherwise, set the showConfig to the showConfigCallback
   */
  setShowConfig() {
    this.showConfig = this.getShowConfig();
    this.showConfig = Object.assign({
      matchTargetWidth: false,
      matchContainerHeight: false,
    }, this.showConfig);
    if (this.showConfig.target && this.showConfig.appendableElement) {
      this.elementAppenderContainerController.setContainer();
    }
  }
  /**
   * It makes sure that the container element is positioned relatively, and that the appendable element
   * has box-sizing set to border-box
   *
   * @returns {void}
   */
  prepareShow() {
    // Add relative position to the container while the container is static,
    // https://developer.mozilla.org/en-US/docs/Web/CSS/position#types_of_positioning
    if (!this.showConfig.isScreenPosition) {
      this.initVisibleAppendElement();
    }
    this.showConfig.appendableElement.style.boxSizing = 'border-box';
    this.positionCalculationController.setCalculationStrategy();
  }
  /**
   * init visible append element.
   */
  initVisibleAppendElement() {
    const containerStyle = window.getComputedStyle(this.container);
    const originContainerPosition = containerStyle.position;
    if (!ElementAppender.containerMap.has(this.container)) {
      ElementAppender.containerMap.set(this.container, {
        visibleAppendElements: new Set(),
        originPosition: originContainerPosition,
      });
    }
    if (originContainerPosition === 'static') {
      this.container.style.position = 'relative';
    }
  }
  /**
   * A function that takes no parameters and returns nothing. It sets the position of the appendable element.
   *
   * @param {boolean} usePreviousPlacement - Should keep use previous placement and alignment.
   * @returns {void}
   */
  adjustPosition(usePreviousPlacement) {
    this.positionCalculationController.adjustPosition(usePreviousPlacement);
  }
  /**
   * Sets the z-index of the appendable element to 11000.
   *
   * @returns {void}
   */
  setZIndex() {
    InternalVegaZIndexManager.setZIndex(this.showConfig.appendableElement, 'base');
  }
  /**
   * If the showConfigCallback is a function, call it and return the result, otherwise return the
   * showConfigCallback
   *
   * @returns {ElementAppenderShowConfig} The showConfigCallback is being returned.
   */
  getShowConfig() {
    if (this.showConfigCallback instanceof Function) {
      return this.showConfigCallback(this.showConfigCallbackTarget);
    }
    else {
      return this.showConfigCallback;
    }
  }
}
/**
 * To maintain the state between container and the visible dropdown within it.
 * Key is containerElement
 */
ElementAppender.containerMap = new Map();
__decorate([
  InjectVegaSlimmer()
], ElementAppender.prototype, "resizeObserverSlimmer", void 0);
__decorate([
  InjectVegaSlimmer()
], ElementAppender.prototype, "elementAppenderContainerController", void 0);
__decorate([
  InjectVegaSlimmer()
], ElementAppender.prototype, "positionCalculationController", void 0);
