// #region Imports
import { ResizeDirection } from '../Types/ResizeDirection';
/**
 * ResizeThumbController - A standalone controller for programmatic resize operations.
 *
 * @description
 * This controller encapsulates all resize logic and can be used independently
 * of any visual component. It accepts any HTMLElement as target and provides
 * full control over the resize operation.
 *
 * Use this controller when:
 * - You need programmatic resize without a visual thumb element
 * - You want to use a custom visual for the resize handle
 * - You're working in a non-Web-Component context (Angular, React, vanilla JS)
 * - You need resize logic for Canvas, WebGL, or other rendering contexts
 *
 * @example
 * ```typescript
 * // Basic usage with custom handle
 * const controller = new ResizeThumbController({
 *   target: document.getElementById('my-box'),
 *   direction: ResizeDirection.SouthEast,
 *   minWidth: 100,
 *   maxWidth: 500,
 *   onResizing: (e) => console.log('New size:', e.newWidth, e.newHeight)
 * });
 *
 * // Attach to your own handle element
 * myHandle.addEventListener('mousedown', (e) => {
 *   controller.startResize(e.clientX, e.clientY);
 * });
 *
 * // Or use the built-in handle attachment
 * controller.attachHandle(myHandle);
 * ```
 *
 * @public
 */
export class ResizeThumbController {
    // #region Fields
    _target;
    _direction;
    _minWidth;
    _minHeight;
    _maxWidth;
    _maxHeight;
    _autoApply;
    _onResizeStart;
    _onResizing;
    _onResizeEnd;
    _isResizing;
    _startX;
    _startY;
    _startWidth;
    _startHeight;
    _startLeft;
    _startTop;
    _attachedHandles;
    _handleListeners;
    _boundHandleMouseMove;
    _boundHandleMouseUp;
    _boundHandleTouchMove;
    _boundHandleTouchEnd;
    // #endregion
    // #region Ctor
    /**
     * Creates a new ResizeThumbController instance.
     *
     * @param options - Configuration options for the controller.
     * @public
     */
    constructor(options) {
        this._target = options.target;
        this._direction = options.direction ?? ResizeDirection.SouthEast;
        this._minWidth = options.minWidth ?? 0;
        this._minHeight = options.minHeight ?? 0;
        this._maxWidth = options.maxWidth ?? Number.MAX_SAFE_INTEGER;
        this._maxHeight = options.maxHeight ?? Number.MAX_SAFE_INTEGER;
        this._autoApply = options.autoApply ?? true;
        this._onResizeStart = options.onResizeStart;
        this._onResizing = options.onResizing;
        this._onResizeEnd = options.onResizeEnd;
        this._isResizing = false;
        this._startX = 0;
        this._startY = 0;
        this._startWidth = 0;
        this._startHeight = 0;
        this._startLeft = 0;
        this._startTop = 0;
        this._attachedHandles = new Set();
        this._handleListeners = new WeakMap();
        // Bind event handlers
        this._boundHandleMouseMove = this.handleMouseMove.bind(this);
        this._boundHandleMouseUp = this.handleMouseUp.bind(this);
        this._boundHandleTouchMove = this.handleTouchMove.bind(this);
        this._boundHandleTouchEnd = this.handleTouchEnd.bind(this);
    }
    // #endregion
    // #region Properties
    /**
     * Gets or sets the target element to resize.
     *
     * @public
     */
    get target() {
        return this._target;
    }
    set target(value) {
        this._target = value;
    }
    /**
     * Gets or sets the resize direction.
     *
     * @public
     */
    get direction() {
        return this._direction;
    }
    set direction(value) {
        this._direction = value;
    }
    /**
     * Gets or sets the minimum width constraint.
     *
     * @public
     */
    get minWidth() {
        return this._minWidth;
    }
    set minWidth(value) {
        this._minWidth = value;
    }
    /**
     * Gets or sets the minimum height constraint.
     *
     * @public
     */
    get minHeight() {
        return this._minHeight;
    }
    set minHeight(value) {
        this._minHeight = value;
    }
    /**
     * Gets or sets the maximum width constraint.
     *
     * @public
     */
    get maxWidth() {
        return this._maxWidth;
    }
    set maxWidth(value) {
        this._maxWidth = value;
    }
    /**
     * Gets or sets the maximum height constraint.
     *
     * @public
     */
    get maxHeight() {
        return this._maxHeight;
    }
    set maxHeight(value) {
        this._maxHeight = value;
    }
    /**
     * Gets or sets whether dimension changes are automatically applied.
     *
     * @public
     */
    get autoApply() {
        return this._autoApply;
    }
    set autoApply(value) {
        this._autoApply = value;
    }
    /**
     * Gets whether a resize operation is currently in progress.
     *
     * @public
     * @readonly
     */
    get isResizing() {
        return this._isResizing;
    }
    // #endregion
    // #region Methods
    /**
     * Attaches the controller to a handle element.
     * The handle will automatically trigger resize on mousedown/touchstart.
     *
     * @param handle - The element to use as resize handle.
     * @public
     */
    attachHandle(handle) {
        if (this._attachedHandles.has(handle)) {
            return;
        }
        const mousedownHandler = (e) => {
            e.preventDefault();
            this.startResize(e.clientX, e.clientY);
        };
        const touchstartHandler = (e) => {
            if (e.touches.length === 1) {
                e.preventDefault();
                this.startResize(e.touches[0].clientX, e.touches[0].clientY);
            }
        };
        handle.addEventListener('mousedown', mousedownHandler);
        handle.addEventListener('touchstart', touchstartHandler, { passive: false });
        this._attachedHandles.add(handle);
        this._handleListeners.set(handle, {
            mousedown: mousedownHandler,
            touchstart: touchstartHandler
        });
    }
    /**
     * Detaches the controller from a handle element.
     *
     * @param handle - The handle element to detach.
     * @public
     */
    detachHandle(handle) {
        const listeners = this._handleListeners.get(handle);
        if (listeners) {
            handle.removeEventListener('mousedown', listeners.mousedown);
            handle.removeEventListener('touchstart', listeners.touchstart);
            this._attachedHandles.delete(handle);
            this._handleListeners.delete(handle);
        }
    }
    /**
     * Detaches the controller from all handle elements.
     *
     * @public
     */
    detachAllHandles() {
        for (const handle of this._attachedHandles) {
            this.detachHandle(handle);
        }
    }
    /**
     * Starts a resize operation programmatically.
     *
     * @param clientX - The initial X coordinate.
     * @param clientY - The initial Y coordinate.
     * @public
     */
    startResize(clientX, clientY) {
        if (this._isResizing || !this._target) {
            return;
        }
        this._isResizing = true;
        this._startX = clientX;
        this._startY = clientY;
        // Capture initial dimensions
        const rect = this._target.getBoundingClientRect();
        const computedStyle = window.getComputedStyle(this._target);
        this._startWidth = rect.width;
        this._startHeight = rect.height;
        this._startLeft = parseFloat(computedStyle.left) || 0;
        this._startTop = parseFloat(computedStyle.top) || 0;
        // Emit start event
        this._onResizeStart?.({
            direction: this._direction,
            startX: this._startX,
            startY: this._startY,
            startWidth: this._startWidth,
            startHeight: this._startHeight
        });
        // Add global listeners
        this.addGlobalListeners();
    }
    /**
     * Updates the resize operation with new coordinates.
     * Call this during mousemove/touchmove if not using attachHandle.
     *
     * @param clientX - The current X coordinate.
     * @param clientY - The current Y coordinate.
     * @public
     */
    updateResize(clientX, clientY) {
        if (!this._isResizing || !this._target) {
            return;
        }
        const deltaX = clientX - this._startX;
        const deltaY = clientY - this._startY;
        const dimensions = this.calculateDimensions(deltaX, deltaY);
        if (this._autoApply) {
            this.applyDimensions(dimensions);
        }
        this._onResizing?.({
            direction: this._direction,
            deltaX,
            deltaY,
            currentX: clientX,
            currentY: clientY,
            newWidth: dimensions.width,
            newHeight: dimensions.height,
            newLeft: dimensions.left,
            newTop: dimensions.top
        });
    }
    /**
     * Ends the resize operation.
     *
     * @param clientX - The final X coordinate.
     * @param clientY - The final Y coordinate.
     * @public
     */
    endResize(clientX, clientY) {
        if (!this._isResizing) {
            return;
        }
        this._isResizing = false;
        const totalDeltaX = clientX - this._startX;
        const totalDeltaY = clientY - this._startY;
        const dimensions = this.calculateDimensions(totalDeltaX, totalDeltaY);
        this._onResizeEnd?.({
            direction: this._direction,
            totalDeltaX,
            totalDeltaY,
            finalWidth: dimensions.width,
            finalHeight: dimensions.height,
            finalLeft: dimensions.left,
            finalTop: dimensions.top
        });
        this.removeGlobalListeners();
    }
    /**
     * Cancels the current resize operation without applying final changes.
     *
     * @public
     */
    cancelResize() {
        if (!this._isResizing) {
            return;
        }
        this._isResizing = false;
        // Restore original dimensions if autoApply was enabled
        if (this._autoApply && this._target) {
            this._target.style.width = `${this._startWidth}px`;
            this._target.style.height = `${this._startHeight}px`;
            this._target.style.left = `${this._startLeft}px`;
            this._target.style.top = `${this._startTop}px`;
        }
        this.removeGlobalListeners();
    }
    /**
     * Calculates the new dimensions based on delta movement.
     * This method can be used externally to preview dimensions without applying.
     *
     * @param deltaX - The horizontal delta from start position.
     * @param deltaY - The vertical delta from start position.
     * @returns The calculated dimensions.
     * @public
     */
    calculateDimensions(deltaX, deltaY) {
        let width = this._startWidth;
        let height = this._startHeight;
        let left = this._startLeft;
        let top = this._startTop;
        const direction = this._direction;
        // Handle horizontal resize
        if (direction === ResizeDirection.East ||
            direction === ResizeDirection.NorthEast ||
            direction === ResizeDirection.SouthEast) {
            width = this._startWidth + deltaX;
        }
        else if (direction === ResizeDirection.West ||
            direction === ResizeDirection.NorthWest ||
            direction === ResizeDirection.SouthWest) {
            width = this._startWidth - deltaX;
            left = this._startLeft + deltaX;
        }
        // Handle vertical resize
        if (direction === ResizeDirection.South ||
            direction === ResizeDirection.SouthEast ||
            direction === ResizeDirection.SouthWest) {
            height = this._startHeight + deltaY;
        }
        else if (direction === ResizeDirection.North ||
            direction === ResizeDirection.NorthEast ||
            direction === ResizeDirection.NorthWest) {
            height = this._startHeight - deltaY;
            top = this._startTop + deltaY;
        }
        // Apply constraints
        width = Math.max(this._minWidth, Math.min(this._maxWidth, width));
        height = Math.max(this._minHeight, Math.min(this._maxHeight, height));
        // Adjust position if constrained
        if (direction === ResizeDirection.West ||
            direction === ResizeDirection.NorthWest ||
            direction === ResizeDirection.SouthWest) {
            left = this._startLeft + (this._startWidth - width);
        }
        if (direction === ResizeDirection.North ||
            direction === ResizeDirection.NorthEast ||
            direction === ResizeDirection.NorthWest) {
            top = this._startTop + (this._startHeight - height);
        }
        return {
            width,
            height,
            left,
            top
        };
    }
    /**
     * Applies dimensions to the target element.
     *
     * @param dimensions - The dimensions to apply.
     * @public
     */
    applyDimensions(dimensions) {
        if (!this._target) {
            return;
        }
        const direction = this._direction;
        // Apply width changes
        if (direction === ResizeDirection.East ||
            direction === ResizeDirection.West ||
            direction === ResizeDirection.NorthEast ||
            direction === ResizeDirection.NorthWest ||
            direction === ResizeDirection.SouthEast ||
            direction === ResizeDirection.SouthWest) {
            this._target.style.width = `${dimensions.width}px`;
        }
        // Apply height changes
        if (direction === ResizeDirection.North ||
            direction === ResizeDirection.South ||
            direction === ResizeDirection.NorthEast ||
            direction === ResizeDirection.NorthWest ||
            direction === ResizeDirection.SouthEast ||
            direction === ResizeDirection.SouthWest) {
            this._target.style.height = `${dimensions.height}px`;
        }
        // Apply position changes for north/west directions
        if (direction === ResizeDirection.West ||
            direction === ResizeDirection.NorthWest ||
            direction === ResizeDirection.SouthWest) {
            this._target.style.left = `${dimensions.left}px`;
        }
        if (direction === ResizeDirection.North ||
            direction === ResizeDirection.NorthEast ||
            direction === ResizeDirection.NorthWest) {
            this._target.style.top = `${dimensions.top}px`;
        }
    }
    /**
     * Disposes the controller and cleans up all resources.
     *
     * @public
     */
    dispose() {
        this.cancelResize();
        this.detachAllHandles();
    }
    /**
     * Adds global listeners for mouse/touch events.
     *
     * @private
     */
    addGlobalListeners() {
        window.addEventListener('mousemove', this._boundHandleMouseMove);
        window.addEventListener('mouseup', this._boundHandleMouseUp);
        window.addEventListener('touchmove', this._boundHandleTouchMove, { passive: false });
        window.addEventListener('touchend', this._boundHandleTouchEnd);
        window.addEventListener('touchcancel', this._boundHandleTouchEnd);
    }
    /**
     * Removes global listeners for mouse/touch events.
     *
     * @private
     */
    removeGlobalListeners() {
        window.removeEventListener('mousemove', this._boundHandleMouseMove);
        window.removeEventListener('mouseup', this._boundHandleMouseUp);
        window.removeEventListener('touchmove', this._boundHandleTouchMove);
        window.removeEventListener('touchend', this._boundHandleTouchEnd);
        window.removeEventListener('touchcancel', this._boundHandleTouchEnd);
    }
    /**
     * Handles mouse move events during resize.
     *
     * @private
     */
    handleMouseMove(e) {
        this.updateResize(e.clientX, e.clientY);
    }
    /**
     * Handles mouse up events to end resize.
     *
     * @private
     */
    handleMouseUp(e) {
        this.endResize(e.clientX, e.clientY);
    }
    /**
     * Handles touch move events during resize.
     *
     * @private
     */
    handleTouchMove(e) {
        if (e.touches.length === 1) {
            e.preventDefault();
            this.updateResize(e.touches[0].clientX, e.touches[0].clientY);
        }
    }
    /**
     * Handles touch end events to end resize.
     *
     * @private
     */
    handleTouchEnd(e) {
        const touch = e.changedTouches[0];
        this.endResize(touch.clientX, touch.clientY);
    }
}
//# sourceMappingURL=ResizeThumbController.js.map