function _check_private_redeclaration(obj, privateCollection) {
    if (privateCollection.has(obj)) {
        throw new TypeError("Cannot initialize the same private elements twice on an object");
    }
}
function _class_apply_descriptor_get(receiver, descriptor) {
    if (descriptor.get) {
        return descriptor.get.call(receiver);
    }
    return descriptor.value;
}
function _class_apply_descriptor_set(receiver, descriptor, value) {
    if (descriptor.set) {
        descriptor.set.call(receiver, value);
    } else {
        if (!descriptor.writable) {
            throw new TypeError("attempted to set read only private field");
        }
        descriptor.value = value;
    }
}
function _class_extract_field_descriptor(receiver, privateMap, action) {
    if (!privateMap.has(receiver)) {
        throw new TypeError("attempted to " + action + " private field on non-instance");
    }
    return privateMap.get(receiver);
}
function _class_private_field_get(receiver, privateMap) {
    var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
    return _class_apply_descriptor_get(receiver, descriptor);
}
function _class_private_field_init(obj, privateMap, value) {
    _check_private_redeclaration(obj, privateMap);
    privateMap.set(obj, value);
}
function _class_private_field_set(receiver, privateMap, value) {
    var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set");
    _class_apply_descriptor_set(receiver, descriptor, value);
    return value;
}
function _class_private_method_get(receiver, privateSet, fn) {
    if (!privateSet.has(receiver)) {
        throw new TypeError("attempted to get private field on non-instance");
    }
    return fn;
}
function _class_private_method_init(obj, privateSet) {
    _check_private_redeclaration(obj, privateSet);
    privateSet.add(obj);
}
import { isSafari } from '../../../helpers/browser.mjs';
var /**
   * Reference to the parent Overlays coordinator.
   *
   * @type {Overlays}
   */ _overlays = /*#__PURE__*/ new WeakMap(), /**
   * Whether sticky-scroll mode is currently active.
   *
   * @type {boolean}
   */ _active = /*#__PURE__*/ new WeakMap(), /**
   * Whether the mouse button is currently pressed.
   *
   * @type {boolean}
   */ _mouseDown = /*#__PURE__*/ new WeakMap(), /**
   * Activates sticky-scroll mode: switches spreaders to `position: sticky`
   * using the exact visual offset so no jump occurs.
   */ _activate = /*#__PURE__*/ new WeakSet(), /**
   * Returns the current vertical scroll offset in the hider's coordinate system.
   *
   * @returns {number}
   */ _getScrollTop = /*#__PURE__*/ new WeakSet(), /**
   * Returns the current horizontal scroll offset in the hider's coordinate system.
   *
   * @returns {number}
   */ _getScrollLeft = /*#__PURE__*/ new WeakSet(), /**
   * Sets the scroll position, converting from hider coordinates back to absolute.
   *
   * @param {number} top Vertical offset (hider coordinates).
   * @param {number} left Horizontal offset (hider coordinates).
   */ _setScrollPosition = /*#__PURE__*/ new WeakSet(), /**
   * Clears inset offset inline styles so `position: relative` is not shifted
   * by leftover `top` / `left` / `right` / `bottom` from sticky mode.
   *
   * @param {HTMLElement} element The spreader element.
   */ _clearSpreaderInsetStyles = /*#__PURE__*/ new WeakSet(), /**
   * Applies position styles to the master spreader and (in element-scroll mode)
   * the overlay clone spreaders.
   *
   * @param {'sticky'|'relative'} position The CSS position value.
   * @param {number} stickyTop The vertical offset (only used for sticky).
   * @param {number} stickyLeft The horizontal offset (only used for sticky).
   */ _applySpreaderStyles = /*#__PURE__*/ new WeakSet(), /**
   * Returns whether the mousedown event originated from a native scrollbar.
   *
   * When a user clicks a native browser scrollbar, the event target is the
   * scrollable container itself (the scrollbar is part of the element's
   * rendering but not a child DOM node). Clicks on child elements (cells,
   * headers, etc.) set a descendant as the target, so they are excluded.
   *
   * @param {MouseEvent} event The mousedown event.
   * @returns {boolean}
   */ _isScrollbarTarget = /*#__PURE__*/ new WeakSet(), /**
   * Returns whether the scrollable element is the window.
   *
   * @returns {boolean}
   */ _isWindowScroll = /*#__PURE__*/ new WeakSet(), /**
   * Returns whether the scrollable element is in RTL mode.
   *
   * @returns {boolean}
   */ _isRtl = /*#__PURE__*/ new WeakSet();
/**
 * Manages the sticky-scroll optimization during native scrollbar drag.
 *
 * When the user grabs the native scrollbar and drags, the master spreader
 * (and overlay clone spreaders in element-scroll mode) switch to
 * `position: sticky` so the browser keeps them visible in the viewport.
 * Row/column content still updates via `refreshAll()` on each scroll event,
 * but the spreader positioning is handled by CSS rather than JS, eliminating
 * visual flickering.
 *
 * The class owns the full lifecycle: detection, activation, offset updates,
 * deactivation, and scroll position correction on release.
 *
 * @class StickyScrollStrategy
 */ export class StickyScrollStrategy {
    /**
   * Registers mousedown/mouseup listeners for scrollbar drag detection.
   * Called from `Overlays.registerListeners()`.
   */ registerListeners() {
        const { eventManager, domBindings: { rootDocument } } = _class_private_field_get(this, _overlays);
        // Scrollable elements may have changed (e.g. updateSettings). Discard any
        // leftover sticky state so a stale deactivate() cannot compute a wrong scroll.
        if (_class_private_field_get(this, _active)) {
            _class_private_field_set(this, _active, false);
            _class_private_method_get(this, _applySpreaderStyles, applySpreaderStyles).call(this, 'relative', 0, 0);
        }
        _class_private_field_set(this, _mouseDown, false);
        eventManager.addEventListener(rootDocument, 'mousedown', (event)=>{
            _class_private_field_set(this, _mouseDown, _class_private_method_get(this, _isScrollbarTarget, isScrollbarTarget).call(this, event));
        });
        eventManager.addEventListener(rootDocument, 'mouseup', ()=>{
            _class_private_field_set(this, _mouseDown, false);
            if (_class_private_field_get(this, _active)) {
                this.deactivate();
            }
        });
    }
    /**
   * Checks whether sticky-scroll should activate based on the current state.
   * Called from `Overlays.syncScrollPositions()` after scroll direction is determined.
   *
   * @param {boolean} verticalScrolling Whether vertical scrolling occurred.
   * @param {boolean} horizontalScrolling Whether horizontal scrolling occurred.
   */ tryActivate(verticalScrolling, horizontalScrolling) {
        if (_class_private_field_get(this, _mouseDown) && !_class_private_field_get(this, _active) && !isSafari() && (verticalScrolling || horizontalScrolling)) {
            _class_private_method_get(this, _activate, activate).call(this);
        }
    }
    /**
   * Recalculates and applies sticky offsets on all spreaders. Called after
   * every render (both full and fast draws) to keep the sticky offset in
   * sync with the current scroll position.
   */ syncOffsets() {
        if (!_class_private_field_get(this, _active)) {
            return;
        }
        const overlays = _class_private_field_get(this, _overlays);
        const { wtViewport } = overlays.wot;
        const startTop = wtViewport.rowsRenderCalculator.startPosition;
        const startLeft = wtViewport.columnsRenderCalculator.startPosition;
        // startPosition is null when nothing is rendered (empty dataset or trimmed-away rows/columns).
        // Arithmetic with null produces NaN, which would set "NaNpx" on the style. Skip the update.
        if (typeof startTop !== 'number' || typeof startLeft !== 'number') {
            return;
        }
        const stickyTop = startTop - _class_private_method_get(this, _getScrollTop, getScrollTop).call(this);
        const stickyLeft = startLeft - _class_private_method_get(this, _getScrollLeft, getScrollLeft).call(this);
        _class_private_method_get(this, _applySpreaderStyles, applySpreaderStyles).call(this, 'sticky', stickyTop, stickyLeft);
    }
    /**
   * Cleans up on destroy.
   */ destroy() {
        if (_class_private_field_get(this, _active)) {
            _class_private_method_get(this, _applySpreaderStyles, applySpreaderStyles).call(this, 'relative', 0, 0);
        }
        _class_private_field_set(this, _active, false);
        _class_private_field_set(this, _mouseDown, false);
    }
    /**
   * Deactivates sticky-scroll mode: captures the current offsets, adjusts
   * the scroll position to match, then restores `position: relative` and
   * triggers a final full render.
   */ deactivate() {
        const overlays = _class_private_field_get(this, _overlays);
        const spreader = overlays.wtTable.spreader;
        const { wtViewport } = overlays.wot;
        const isRtl = overlays.wtSettings.getSetting('rtlMode');
        const leftProp = isRtl ? 'right' : 'left';
        // Render while still active (same code path as during drag).
        overlays.refreshAll();
        // Capture the sticky offsets and computed start positions.
        const lastStickyTop = parseInt(spreader.style.top, 10) || 0;
        const lastStickyLeft = parseInt(spreader.style[leftProp], 10) || 0;
        const startTop = wtViewport.rowsRenderCalculator.startPosition;
        const startLeft = wtViewport.columnsRenderCalculator.startPosition;
        // Adjust scroll position while still active to prevent cascading renders.
        const targetScrollTop = typeof startTop === 'number' ? startTop - lastStickyTop : _class_private_method_get(this, _getScrollTop, getScrollTop).call(this);
        const targetScrollLeft = typeof startLeft === 'number' ? startLeft - lastStickyLeft : _class_private_method_get(this, _getScrollLeft, getScrollLeft).call(this);
        _class_private_method_get(this, _setScrollPosition, setScrollPosition).call(this, targetScrollTop, targetScrollLeft);
        // Deactivate and restore relative positioning.
        _class_private_field_set(this, _active, false);
        _class_private_method_get(this, _applySpreaderStyles, applySpreaderStyles).call(this, 'relative', 0, 0);
        // Final render in normal mode at the corrected scroll position.
        overlays.refreshAll();
        overlays.applyToDOM();
    }
    /**
   * @param {Overlays} overlays The parent Overlays instance.
   */ constructor(overlays){
        _class_private_method_init(this, _activate);
        _class_private_method_init(this, _getScrollTop);
        _class_private_method_init(this, _getScrollLeft);
        _class_private_method_init(this, _setScrollPosition);
        _class_private_method_init(this, _clearSpreaderInsetStyles);
        _class_private_method_init(this, _applySpreaderStyles);
        _class_private_method_init(this, _isScrollbarTarget);
        _class_private_method_init(this, _isWindowScroll);
        _class_private_method_init(this, _isRtl);
        _class_private_field_init(this, _overlays, {
            writable: true,
            value: void 0
        });
        _class_private_field_init(this, _active, {
            writable: true,
            value: void 0
        });
        _class_private_field_init(this, _mouseDown, {
            writable: true,
            value: void 0
        });
        _class_private_field_set(this, _active, false);
        _class_private_field_set(this, _mouseDown, false);
        _class_private_field_set(this, _overlays, overlays);
    }
}
function activate() {
    const overlays = _class_private_field_get(this, _overlays);
    const spreader = overlays.wtTable.spreader;
    const isRtl = overlays.wtSettings.getSetting('rtlMode');
    const leftProp = isRtl ? 'right' : 'left';
    const startTop = parseInt(spreader.style.top, 10) || 0;
    const stickyTop = startTop - _class_private_method_get(this, _getScrollTop, getScrollTop).call(this);
    const startLeft = parseInt(spreader.style[leftProp], 10) || 0;
    const stickyLeft = startLeft - _class_private_method_get(this, _getScrollLeft, getScrollLeft).call(this);
    _class_private_field_set(this, _active, true);
    _class_private_method_get(this, _applySpreaderStyles, applySpreaderStyles).call(this, 'sticky', stickyTop, stickyLeft);
}
function getScrollTop() {
    const { topOverlay } = _class_private_field_get(this, _overlays);
    return topOverlay.getScrollPosition() - topOverlay.getTableParentOffset();
}
function getScrollLeft() {
    const { inlineStartOverlay } = _class_private_field_get(this, _overlays);
    // For RTL window scrolling, scrollX increases as the user scrolls left (away from
    // the right-edge origin). getTableParentOffset() returns the left-edge offset which
    // doesn't apply to the RTL coordinate system, so skip it and return scrollX directly.
    if (_class_private_method_get(this, _isRtl, isRtl).call(this) && _class_private_method_get(this, _isWindowScroll, isWindowScroll).call(this)) {
        return Math.abs(inlineStartOverlay.getScrollPosition());
    }
    return Math.abs(inlineStartOverlay.getScrollPosition()) - inlineStartOverlay.getTableParentOffset();
}
function setScrollPosition(top, left) {
    const overlays = _class_private_field_get(this, _overlays);
    if (_class_private_method_get(this, _isWindowScroll, isWindowScroll).call(this)) {
        const { rootWindow } = overlays.domBindings;
        // RTL + window-scroll: left is in scrollX coordinates (distance from the right-edge
        // origin), so pass it directly. LTR: add back the table's left page offset.
        const absoluteLeft = _class_private_method_get(this, _isRtl, isRtl).call(this) ? left : left + overlays.inlineStartOverlay.getTableParentOffset();
        rootWindow.scrollTo(absoluteLeft, top + overlays.topOverlay.getTableParentOffset());
    } else {
        overlays.wtTable.holder.scrollTop = top;
        overlays.wtTable.holder.scrollLeft = _class_private_method_get(this, _isRtl, isRtl).call(this) ? -left : left;
    }
}
function clearSpreaderInsetStyles(element) {
    element.style.top = '';
    element.style.bottom = '';
    element.style.left = '';
    element.style.right = '';
}
function applySpreaderStyles(position, stickyTop, stickyLeft) {
    const overlays = _class_private_field_get(this, _overlays);
    const spreader = overlays.wtTable.spreader;
    const isRtl1 = _class_private_method_get(this, _isRtl, isRtl).call(this);
    const leftProp = isRtl1 ? 'right' : 'left';
    const isSticky = position === 'sticky';
    // Master spreader
    spreader.style.position = position;
    if (isSticky) {
        spreader.style.top = `${stickyTop}px`;
        spreader.style.bottom = '';
        spreader.style[leftProp] = `${stickyLeft}px`;
        spreader.style[isRtl1 ? 'left' : 'right'] = '';
    } else {
        _class_private_method_get(this, _clearSpreaderInsetStyles, clearSpreaderInsetStyles).call(this, spreader);
    }
    // Overlay clone spreaders — only in element scroll mode.
    // In window scroll mode, clones use the overlay system's own
    // absolute/fixed positioning which conflicts with sticky.
    if (_class_private_method_get(this, _isWindowScroll, isWindowScroll).call(this)) {
        return;
    }
    if (overlays.inlineStartOverlay.needFullRender) {
        const cloneSpreader = overlays.inlineStartOverlay.clone.wtTable.spreader;
        cloneSpreader.style.position = position;
        if (isSticky) {
            cloneSpreader.style.top = `${stickyTop}px`;
        } else {
            _class_private_method_get(this, _clearSpreaderInsetStyles, clearSpreaderInsetStyles).call(this, cloneSpreader);
        }
    }
    if (overlays.topOverlay.needFullRender) {
        const cloneSpreader = overlays.topOverlay.clone.wtTable.spreader;
        cloneSpreader.style.position = position;
        if (isSticky) {
            cloneSpreader.style[leftProp] = `${stickyLeft}px`;
            cloneSpreader.style[isRtl1 ? 'left' : 'right'] = '';
        } else {
            _class_private_method_get(this, _clearSpreaderInsetStyles, clearSpreaderInsetStyles).call(this, cloneSpreader);
        }
    }
    if (overlays.bottomOverlay.needFullRender && overlays.bottomOverlay.clone) {
        const cloneSpreader = overlays.bottomOverlay.clone.wtTable.spreader;
        cloneSpreader.style.position = position;
        if (isSticky) {
            cloneSpreader.style[leftProp] = `${stickyLeft}px`;
            cloneSpreader.style[isRtl1 ? 'left' : 'right'] = '';
        } else {
            _class_private_method_get(this, _clearSpreaderInsetStyles, clearSpreaderInsetStyles).call(this, cloneSpreader);
        }
    }
}
function isScrollbarTarget(event) {
    if (_class_private_method_get(this, _isWindowScroll, isWindowScroll).call(this)) {
        return event.target === _class_private_field_get(this, _overlays).domBindings.rootDocument.documentElement;
    }
    return event.target === _class_private_field_get(this, _overlays).scrollableElement;
}
function isWindowScroll() {
    return _class_private_field_get(this, _overlays).scrollableElement === _class_private_field_get(this, _overlays).domBindings.rootWindow;
}
function isRtl() {
    return _class_private_field_get(this, _overlays).wtSettings.getSetting('rtlMode');
}
