// #region Imports
import { createAbortHandle } from '../../Dom/AbortHandle';
export class ResizeContainerController {
    // #region Fields
    _host;
    _abortHandle = createAbortHandle();
    _options = {
        enabled: true,
        layer: getDefaultLayer
    };
    _id = -1;
    _hasPointerCapture = false;
    _state;
    _activeRef = null;
    _ghost = null;
    // #endregion
    // #region Ctor
    /**
     * Constructs a new instance of the `{@link ResizeContainerController}` class.
     *
     * @public
     */
    constructor(host, options) {
        this._host = host;
        this._host.addController(this);
        this.set(options);
    }
    // #endregion
    // #region Properties
    /** Whether the controller is enabled and will listen for events. */
    get enabled() {
        return Boolean(this._options.enabled);
    }
    get _element() {
        return this._activeRef ?? this._host;
    }
    get _resizeTarget() {
        return this._options.resizeTarget?.call(this._host) ?? this._host;
    }
    get _layer() {
        if (!this._isDeferred) {
            return this._host;
        }
        return this._options.layer?.() ?? this._host;
    }
    /** Whether the controller is in deferred mode. */
    get _isDeferred() {
        return this._options.mode === 'deferred';
    }
    get _stateParameters() {
        const { initial, current } = this._state;
        return {
            initial,
            current,
            deltaX: current.width - initial.width,
            deltaY: current.height - initial.height,
            ghost: this._ghost,
            trigger: this._activeRef
        };
    }
    // #endregion
    // #region Methods
    hostConnected() {
        this._host.addEventListener('pointerdown', this, {
            signal: this._abortHandle.signal
        });
        this._host.addEventListener('touchstart', this, {
            passive: false,
            signal: this._abortHandle.signal
        });
    }
    hostDisconnected() {
        this._abortHandle.abort();
        this._setResizeCancelListener(false);
        this._removeGhostElement();
    }
    /** Stops any resizing operation, cleaning up any additional elements and event listeners. */
    dispose() {
        this._setResizeState(false);
        this._removeGhostElement();
        this._activeRef = null;
    }
    handleEvent(event) {
        if (!this.enabled) {
            return;
        }
        switch (event.type) {
            case 'touchstart':
            case 'contextmenu':
                event.preventDefault();
                break;
            case 'keydown':
                this._handleCancel(event);
                break;
            case 'pointerdown':
                this._handlePointerDown(event);
                break;
            case 'pointermove':
                this._handlePointerMove(event);
                break;
            case 'lostpointercapture':
                this._handlePointerEnd(event);
                break;
        }
    }
    /** Updates the configuration of the resize controller. */
    set(options) {
        Object.assign(this._options, options);
    }
    _handlePointerDown(event) {
        // Non-primary buttons are ignored
        if (event.button || this._shouldSkip(event)) {
            return;
        }
        this._setInitialState(event);
        this._createGhostElement();
        const parameters = {
            event,
            state: this._stateParameters
        };
        if (this._options.start?.call(this._host, parameters) === false) {
            this.dispose();
            return;
        }
        this._setResizeState();
    }
    _handlePointerMove(event) {
        if (!this._hasPointerCapture) {
            return;
        }
        this._updateState(event);
        const parameters = {
            event,
            state: this._stateParameters
        };
        this._options.resize?.call(this._host, parameters);
        this._state.current = parameters.state.current;
        this._updatePosition(this._isDeferred ? this._ghost : this._resizeTarget);
    }
    _handlePointerEnd(event) {
        const parameters = {
            event,
            state: this._stateParameters
        };
        this._options.end?.call(this._host, parameters);
        this._state.current = parameters.state.current;
        // eslint-disable-next-line @typescript-eslint/no-unused-expressions
        parameters.state.commit?.() ?? this._updatePosition(this._resizeTarget);
        this.dispose();
    }
    _handleCancel(event) {
        const key = event.key.toLowerCase();
        if (this._hasPointerCapture && key === 'escape') {
            this._options.cancel?.call(this._host, this._stateParameters);
        }
    }
    _shouldSkip(event) {
        this._setActiveRef(event);
        return !this._activeRef;
    }
    _setActiveRef(event) {
        const xxx = Object.entries(this._options.thumbs ?? {});
        const refs = Object.entries(this._options.thumbs ?? {}).map((x) => x[1]) ?? [this._host];
        this._activeRef = findElementFromEventPath((e) => refs.includes(e), event) ?? null;
    }
    _setResizeState(enabled = true) {
        this._hasPointerCapture = enabled;
        if (enabled) {
            this._element.setPointerCapture(this._id);
        }
        else {
            this._element.releasePointerCapture(this._id);
        }
        this._setResizeCancelListener(enabled);
        // Toggle additional event listeners
        for (const type of [
            'pointermove',
            'lostpointercapture',
            'contextmenu'
        ]) {
            if (enabled) {
                this._host.addEventListener(type, this);
            }
            else {
                this._host.removeEventListener(type, this);
            }
        }
    }
    _setResizeCancelListener(enabled = true) {
        if (enabled) {
            globalThis.addEventListener('keydown', this);
        }
        else {
            globalThis.removeEventListener('keydown', this);
        }
    }
    _setInitialState({ pointerId }) {
        const dimensions = this._resizeTarget.getBoundingClientRect();
        this._id = pointerId;
        this._state = {
            initial: dimensions,
            current: structuredClone(dimensions)
        };
    }
    _updateState({ clientX, clientY }) {
        this._state.current.width = clientX - this._state.initial.x;
        this._state.current.height = clientY - this._state.initial.y;
    }
    _updatePosition(element) {
        if (element) {
            Object.assign(element.style, {
                width: `${this._state.current.width}px`,
                height: `${this._state.current.height}px`
            });
        }
    }
    _createGhostElement() {
        if (!this._isDeferred) {
            return;
        }
        this._ghost =
            this._options.deferredFactory?.call(this._host) ??
                createDefaultGhostElement(this._resizeTarget.getBoundingClientRect());
        this._ghost.setAttribute('data-resize-ghost', '');
        this._layer.append(this._ghost);
    }
    _removeGhostElement() {
        this._ghost?.remove();
        this._ghost = null;
    }
}
export function addResizeContainerController(host, options) {
    return new ResizeContainerController(host, options);
}
function isElement(node) {
    return node instanceof Node && node.nodeType === Node.ELEMENT_NODE;
}
function findElementFromEventPath(predicate, event) {
    const func = typeof predicate === 'string'
        ? (e) => e.matches(predicate)
        : (e) => predicate(e);
    return event.composedPath().filter((item) => isElement(item))
        .find(func);
}
function createDefaultGhostElement({ x, y, width, height }) {
    const element = document.createElement('div');
    const { scrollX, scrollY } = window;
    Object.assign(element.style, {
        position: 'absolute',
        top: `${y + scrollY}px`,
        left: `${x + scrollX}px`,
        zIndex: 1000,
        background: 'pink',
        opacity: 0.85,
        width: `${width}px`,
        height: `${height}px`
    });
    return element;
}
function getDefaultLayer() {
    return document.body;
}
//# sourceMappingURL=ResizeContainerController.js.map