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 { InjectVegaSlimmer, MapToComponentMethod, VegaSlimmer } from 'vega-slimmer/core';
import { ElementAppender } from '../../ui/element-appender';
import { findParent, isParent } from '../../../utils/ui';
import ChangeManager from '../../change-manager/change-manager';
import { SHADOW_ROOT_ELEMENT_SCROLLING } from '../../change-manager/subject/static-subject/static-subject-title';
import { Observer } from '../../change-manager/observer/observer';
import { ElementAppenderHierarchyTreeManager } from './element-appender-hierarchy-tree-manager';
import { ClickTriggerStrategy } from './trigger-strategies/click-trigger-strategy';
import { HoverTriggerStrategy } from './trigger-strategies/hover-trigger-strategy';
import { NoneTriggerStrategy } from './trigger-strategies/none-trigger-strategy';
import { isEATriggerStrategy } from '../../../types/type-guard';
export class ElementAppenderSlimmer extends VegaSlimmer {
  constructor(showConfigFactory, getTrigger, isDropdownShowName = 'isDropdownShow', disableInteractiveGetter = () => false) {
    super();
    this.hierarchyTreeManager = new ElementAppenderHierarchyTreeManager(this);
    /**
     * It adds event listeners to the target and the appendable element
     */
    this.registerElementAppenderListener = () => {
      var _a, _b, _c, _d, _e, _f;
      this.unRegisterElementAppenderListener();
      if (this.showConfig) {
        (_a = this.showConfig.target) === null || _a === void 0 ? void 0 : _a.addEventListener('click', this.targetClickHandler);
        (_b = this.showConfig.target) === null || _b === void 0 ? void 0 : _b.addEventListener('mouseenter', this.targetMouseEnterHandler);
        (_c = this.showConfig.target) === null || _c === void 0 ? void 0 : _c.addEventListener('mouseleave', this.targetMouseLeaveHandler);
        (_d = this.showConfig.target) === null || _d === void 0 ? void 0 : _d.addEventListener('focusout', this.targetFocusoutHandler);
        (_e = this.showConfig.appendableElement) === null || _e === void 0 ? void 0 : _e.addEventListener('mouseenter', this.elementMouseEnterHandler);
        (_f = this.showConfig.appendableElement) === null || _f === void 0 ? void 0 : _f.addEventListener('mouseleave', this.elementMouseLeaveHandler);
      }
      window.addEventListener('mousedown', this.globalMouseDownHandler);
      // eslint-disable-next-line no-restricted-globals
      document.addEventListener('scroll', this.globalScrollHandler, { capture: true });
      this.listenShadowRootElementScrolling();
    };
    /* It's a function that listens to the click event on the dropdown. It checks if the trigger
    property is equal to 'click'. If it is, then it toggles the dropdown. */
    this.targetClickHandler = (event) => {
      const triggerStrategy = this.getTriggerStrategy();
      if (triggerStrategy.canEvaluate('target-click')) {
        this.executeTriggerStrategy(triggerStrategy, event, 'target-click');
      }
    };
    /* It's a function that listens to the mouseenter event on the dropdown. It checks if the trigger
    property is equal to 'hover'. If it is, then it clears the timer. */
    this.targetMouseEnterHandler = (event) => {
      const triggerStrategy = this.getTriggerStrategy();
      if (triggerStrategy.canEvaluate('target-mouseenter')) {
        this.executeTriggerStrategy(triggerStrategy, event, 'target-mouseenter');
        this.clearDelayHideTimer();
      }
    };
    /* It's a function that listens to the mouseleave event on the dropdown. It checks if the trigger
    property is equal to 'hover'. If it is, then it creates a timer that will hide the dropdown after a
    certain amount of time. */
    this.targetMouseLeaveHandler = (event) => {
      const triggerStrategy = this.getTriggerStrategy();
      if (triggerStrategy.canEvaluate('target-mouseleave')) {
        this.createDelayHideTimer(event, 'target-mouseleave');
      }
    };
    /* istanbul ignore next */
    /* While focused element is in appendable element, like search input or vega-dropdown-item, we should not hide dropdown */
    this.targetFocusoutHandler = (e) => {
      var _a;
      if (!isParent(e.relatedTarget, (_a = this.showConfig) === null || _a === void 0 ? void 0 : _a.appendableElement)) {
        this.targetMouseLeaveHandler(e);
      }
    };
    /* It's a function that listens to the mouseenter event on the dropdown. It checks if the trigger
    property is equal to 'hover'. If it is, then it clears the timer. */
    this.elementMouseEnterHandler = () => {
      var _a;
      (_a = this.hierarchyTreeManager.getParent()) === null || _a === void 0 ? void 0 : _a.elementMouseEnterHandler();
      const triggerStrategy = this.getTriggerStrategy();
      if (triggerStrategy.canEvaluate('element-mouseenter') && !this.disableInteractiveGetter()) {
        this.clearDelayHideTimer();
      }
    };
    /* It's a function that listens to the mouseleave event on the dropdown. It checks if the trigger
    property is equal to 'hover'. If it is, then it creates a timer that will hide the dropdown after a
    certain amount of time. */
    this.elementMouseLeaveHandler = (event) => {
      var _a;
      (_a = this.hierarchyTreeManager.getParent()) === null || _a === void 0 ? void 0 : _a.elementMouseLeaveHandler(event);
      const triggerStrategy = this.getTriggerStrategy();
      if (triggerStrategy.canEvaluate('element-mouseleave') && !this.disableInteractiveGetter()) {
        this.createDelayHideTimer(event, 'element-mouseleave');
      }
    };
    /* It's a function that listens to the global mousedown event. It checks if the dropdown is showing,
    and if the target of the event is not a child of the dropdown or the trigger. If it is, then it
    hides the dropdown. */
    this.globalMouseDownHandler = (e) => {
      const currentTarget = e.composedPath()[0];
      const hasChildShowing = this.hierarchyTreeManager
        .getChildren()
        .some((child) => child.isShowing());
      const triggerStrategy = this.getTriggerStrategy();
      if (!hasChildShowing &&
        triggerStrategy.canEvaluate('click-outside') &&
        this.showConfig &&
        !isParent(currentTarget, this.showConfig.appendableElement) &&
        !isParent(currentTarget, this.showConfig.target) &&
        (this.showConfig.isScreenPosition ||
          findParent(currentTarget, 'vega-modal') === findParent(this.showConfig.target, 'vega-modal'))) {
        this.executeTriggerStrategy(triggerStrategy, e, 'click-outside');
      }
    };
    /* istanbul ignore next */
    /* Listen global scroll event */
    this.globalScrollHandler = () => {
      var _a;
      if (this.showConfig && this.showConfig.isScreenPosition && this.isShowing()) {
        (_a = this.elementAppender) === null || _a === void 0 ? void 0 : _a.onScroll();
      }
    };
    /* A timer that will hide the dropdown after a certain amount of time. */
    this.createDelayHideTimer = (event, eventType) => {
      const triggerStrategy = this.getTriggerStrategy();
      this.delayHideTimer = setTimeout(() => {
        this.executeTriggerStrategy(triggerStrategy, event, eventType);
        clearTimeout(this.delayHideTimer);
      }, ElementAppenderSlimmer.DELAY_HIDE_TIME);
    };
    /* A function that clears the timer. */
    this.clearDelayHideTimer = () => {
      this.delayHideTimer && clearTimeout(this.delayHideTimer);
    };
    this.executeTriggerStrategy = (triggerStrategy, event, eventType) => {
      if (this.showConfig) {
        const result = triggerStrategy.evaluate({
          target: this.showConfig.target,
          appendableElement: this.showConfig.appendableElement,
          htmlEvent: event,
          eventType: eventType,
          currentState: {
            isVisible: this.isShowing(),
          },
        });
        if (result) {
          result.nextAction === 'hide' && this.hide();
          result.nextAction === 'show' && this.show();
        }
      }
    };
    this.showConfigFactory = showConfigFactory;
    this.getTrigger = getTrigger;
    this.isDropdownShowName = isDropdownShowName;
    this.disableInteractiveGetter = disableInteractiveGetter;
  }
  connectedCallback() {
    if (this.elementAppender === null) {
      this.setElementAppender();
      this.registerElementAppenderListener();
    }
  }
  disconnectedCallback() {
    this.unRegisterElementAppenderListener();
    this.destroyElementAppender();
  }
  componentDidLoad() {
    this.initElementAppender();
  }
  getElementAppender() {
    return this.elementAppender;
  }
  show() {
    this.elementAppender && this.elementAppender.show();
    this[this.isDropdownShowName] = true;
    this.setAriaOwns();
  }
  hide() {
    this.elementAppender && this.elementAppender.hide();
    this[this.isDropdownShowName] = false;
    this.hierarchyTreeManager.getChildren().map((child) => {
      child.hide();
    });
  }
  destroy() {
    this.removeAriaOwns();
    this.destroyElementAppender();
    this[this.isDropdownShowName] = false;
  }
  updateShowConfigFactory(showConfigFactory) {
    this.showConfigFactory = showConfigFactory;
    this.initElementAppender();
  }
  destroyElementAppender() {
    if (this.elementAppender) {
      this.elementAppender.destroy();
      this.elementAppender = null;
    }
  }
  initElementAppender() {
    this.setShowConfig();
    this.setElementAppender();
    this.registerElementAppenderListener();
  }
  setElementAppender() {
    this.elementAppender = new ElementAppender(this.showConfigFactory, this);
  }
  isShowing() {
    return this[this.isDropdownShowName];
  }
  setShowConfig() {
    this.showConfig = this.showConfigFactory();
  }
  /**
   * It removes all event listeners that were added in the `register()` function
   */
  unRegisterElementAppenderListener() {
    var _a, _b, _c, _d, _e, _f;
    if (this.showConfig) {
      (_a = this.showConfig.target) === null || _a === void 0 ? void 0 : _a.removeEventListener('click', this.targetClickHandler);
      (_b = this.showConfig.target) === null || _b === void 0 ? void 0 : _b.removeEventListener('mouseenter', this.targetMouseEnterHandler);
      (_c = this.showConfig.target) === null || _c === void 0 ? void 0 : _c.removeEventListener('mouseleave', this.targetMouseLeaveHandler);
      (_d = this.showConfig.target) === null || _d === void 0 ? void 0 : _d.removeEventListener('focusout', this.targetMouseLeaveHandler);
      (_e = this.showConfig.appendableElement) === null || _e === void 0 ? void 0 : _e.removeEventListener('mouseenter', this.elementMouseEnterHandler);
      (_f = this.showConfig.appendableElement) === null || _f === void 0 ? void 0 : _f.removeEventListener('mouseleave', this.elementMouseLeaveHandler);
    }
    window.removeEventListener('mousedown', this.globalMouseDownHandler);
    // eslint-disable-next-line no-restricted-globals
    document.removeEventListener('scroll', this.globalScrollHandler);
    this.removeShadowRootElementScrolling();
  }
  /**
   * Sets up an observer to handle scroll events in a shadow root element.
   */
  listenShadowRootElementScrolling() {
    this.shadowRootElementScrollingObserver = new Observer(this.canAcceptShadowRootScrollEvent.bind(this), this.globalScrollHandler.bind(this));
    ChangeManager.register(SHADOW_ROOT_ELEMENT_SCROLLING, this.shadowRootElementScrollingObserver);
  }
  /**
   * Checks if the host element is a parent of the target element.
   *
   * @param {ShadowRootElementScrollingPayload} payload - payload.
   * @returns {boolean} - Can accept.
   */
  canAcceptShadowRootScrollEvent(payload) {
    return Boolean(this.showConfig && isParent(this.showConfig.target, payload.host));
  }
  /**
   * Remove observer for scrolling in a shadow root element.
   */
  removeShadowRootElementScrolling() {
    ChangeManager.unregister(SHADOW_ROOT_ELEMENT_SCROLLING, this.shadowRootElementScrollingObserver);
  }
  getTriggerStrategy() {
    const trigger = this.getTrigger();
    if (trigger === 'click') {
      return new ClickTriggerStrategy();
    }
    else if (trigger === 'hover') {
      return new HoverTriggerStrategy();
    }
    else if (isEATriggerStrategy(trigger)) {
      return trigger;
    }
    else {
      return new NoneTriggerStrategy();
    }
  }
  /**
   * Sets `ariaOwnsElements` on the target element to reference the appendable element directly.
   * Uses the Element Reflection API which works across shadow DOM boundaries by using
   * direct element references instead of IDREF strings.
   * This ensures screen readers (e.g. VoiceOver) can navigate to the portal-appended
   * content immediately after the trigger, rather than at the end of the document.
   */
  setAriaOwns() {
    if (this.showConfig) {
      const target = this.showConfig.target;
      const appendableElement = this.showConfig.appendableElement;
      if (target && appendableElement) {
        target.ariaOwnsElements = [appendableElement];
      }
    }
  }
  /**
   * Removes `ariaOwnsElements` from the target element when the popup is hidden or destroyed.
   */
  removeAriaOwns() {
    var _a;
    if ((_a = this.showConfig) === null || _a === void 0 ? void 0 : _a.target) {
      this.showConfig.target.ariaOwnsElements = [];
    }
  }
}
ElementAppenderSlimmer.DELAY_HIDE_TIME = 100;
__decorate([
  InjectVegaSlimmer()
], ElementAppenderSlimmer.prototype, "hierarchyTreeManager", void 0);
__decorate([
  MapToComponentMethod('connectedCallback')
], ElementAppenderSlimmer.prototype, "connectedCallback", null);
__decorate([
  MapToComponentMethod('disconnectedCallback')
], ElementAppenderSlimmer.prototype, "disconnectedCallback", null);
__decorate([
  MapToComponentMethod('componentDidLoad')
], ElementAppenderSlimmer.prototype, "componentDidLoad", null);
