/**
 * @license
 * Copyright (c) 2017 - 2026 Vaadin Ltd.
 * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
 */
import { isTouch } from '@vaadin/component-base/src/browser-utils.js';
import { eventInWindow, getMouseOrFirstTouchEvent } from './vaadin-dialog-utils.js';

/**
 * @polymerMixin
 */
export const DialogDraggableMixin = (superClass) =>
  class VaadinDialogDraggableMixin extends superClass {
    static get properties() {
      return {
        /**
         * Set to true to enable repositioning the dialog by clicking and dragging.
         *
         * By default, only the overlay area can be used to drag the element. But,
         * a child element can be marked as a draggable area by adding a
         * "`draggable`" class to it, this will by default make all of its children draggable also.
         * If you want a child element to be draggable
         * but still have its children non-draggable (by default), mark it with
         * "`draggable-leaf-only`" class name.
         *
         */
        draggable: {
          type: Boolean,
          value: false,
          reflectToAttribute: true,
        },

        /** @private */
        _touchDevice: {
          type: Boolean,
          value: isTouch,
        },

        /* TODO: Expose as a public property (check naming) */
        __dragHandleClassName: {
          type: String,
        },
      };
    }

    /** @protected */
    ready() {
      super.ready();
      this._originalBounds = {};
      this._originalMouseCoords = {};
      this._startDrag = this._startDrag.bind(this);
      this._drag = this._drag.bind(this);
      this._stopDrag = this._stopDrag.bind(this);

      this.$.overlay.$.overlay.addEventListener('mousedown', this._startDrag);
      this.$.overlay.$.overlay.addEventListener('touchstart', this._startDrag);
    }

    /** @private */
    _startDrag(e) {
      // Don't initiate when there's more than 1 touch (pinch zoom)
      if (e.type === 'touchstart' && e.touches.length > 1) {
        return;
      }

      // Don't initiate drag if a nested dialog already handled this event.
      // This prevents dragging both dialogs simultaneously.
      if (e.defaultPrevented) {
        return;
      }

      if (this.draggable && (e.button === 0 || e.touches)) {
        const resizerContainer = this.$.overlay.$.resizerContainer;
        const isResizerContainer = e.target === resizerContainer;
        const isResizerContainerScrollbar =
          e.offsetX > resizerContainer.clientWidth || e.offsetY > resizerContainer.clientHeight;
        const isContentPart = e.target === this.$.overlay.$.content;

        const isDraggable = e.composedPath().some((node, index) => {
          if (!node.classList) {
            return false;
          }

          const isDraggableNode = node.classList.contains(this.__dragHandleClassName || 'draggable');
          const isDraggableLeafOnly = node.classList.contains('draggable-leaf-only');
          const isLeafNode = index === 0;
          return (isDraggableLeafOnly && isLeafNode) || (isDraggableNode && (!isDraggableLeafOnly || isLeafNode));
        });

        if ((isResizerContainer && !isResizerContainerScrollbar) || isContentPart || isDraggable) {
          // Signal that we're handling this drag event, so parent dialogs won't also drag
          e.preventDefault();
          this._originalBounds = this.$.overlay.getBounds();
          const event = getMouseOrFirstTouchEvent(e);
          this._originalMouseCoords = { top: event.pageY, left: event.pageX };
          window.addEventListener('mouseup', this._stopDrag);
          window.addEventListener('touchend', this._stopDrag);
          window.addEventListener('mousemove', this._drag);
          window.addEventListener('touchmove', this._drag);

          const { top, left, width, height } = this._originalBounds;
          if (this.$.overlay.$.overlay.style.position !== 'absolute') {
            this.top = top;
            this.left = left;
          }
          this.dispatchEvent(new CustomEvent('drag-start', { detail: { width, height, top, left } }));
        }
      }
    }

    /** @private */
    _drag(e) {
      const event = getMouseOrFirstTouchEvent(e);
      if (eventInWindow(event)) {
        let top = this._originalBounds.top + (event.pageY - this._originalMouseCoords.top);
        let left = this._originalBounds.left + (event.pageX - this._originalMouseCoords.left);

        if (this.keepInViewport) {
          // Constrain the dialog position so that it stays within the overlay host bounds,
          // respecting the `--vaadin-overlay-viewport-inset` (offset from the viewport edges).
          const { width, height } = this._originalBounds;
          const overlayHostBounds = this.$.overlay.getBoundingClientRect();
          const maxLeft = overlayHostBounds.right - overlayHostBounds.left - width;
          const maxTop = overlayHostBounds.bottom - overlayHostBounds.top - height;
          left = Math.max(0, Math.min(left, maxLeft));
          top = Math.max(0, Math.min(top, maxTop));
        }

        this.top = top;
        this.left = left;
      }
    }

    /** @private */
    _stopDrag() {
      this.dispatchEvent(new CustomEvent('dragged', { detail: { top: this.top, left: this.left } }));
      window.removeEventListener('mouseup', this._stopDrag);
      window.removeEventListener('touchend', this._stopDrag);
      window.removeEventListener('mousemove', this._drag);
      window.removeEventListener('touchmove', this._drag);
    }

    /**
     * Fired when the dialog drag is started.
     *
     * @event drag-start
     */

    /**
     * Fired when the dialog drag is finished.
     *
     * @event dragged
     */
  };
