/**
 * @license
 * Copyright 2023 Tyler Technologies, Inc. 
 * License: Apache-2.0
 */
import { isDefined } from '@tylertech/forge-core';
import { safeMin, scaleValue } from '../../core/utils/utils';
import { eventIncludesArrowKey } from '../../core/utils/event-utils';
import { SPLIT_VIEW_PANEL_CONSTANTS } from './split-view-panel-constants';
import { clampSize, clearState, getPixelDimension, getValuenow, handleBoundariesAfterResize, handleBoundariesDuringResize, initState, keyboardResize, maxResize, minResize, pointerResize, setState } from './split-view-panel-utils';
export class SplitViewPanelFoundation {
    constructor(_adapter) {
        this._adapter = _adapter;
        // API
        this._size = 200;
        this._min = 0;
        this._accessibleLabel = 'Split view panel';
        this._open = true;
        // State
        this._state = initState();
        this._isInitialized = false;
        // Properties inherited from parent split view
        this._parentProperties = {};
        this._pointerdownListener = evt => this._onPointerdown(evt);
        this._pointerupListener = evt => this._onPointerup(evt);
        this._pointermoveListener = evt => this._onPointermove(evt);
        this._keydownListener = evt => this._onKeydown(evt);
        this._keyupListener = evt => this._onKeyup(evt);
    }
    // Properties stored in state
    get _orientation() {
        return this._state.orientation;
    }
    set _orientation(value) {
        this._state.orientation = value;
    }
    get _resizable() {
        return this._state.resizable;
    }
    set _resizable(value) {
        this._state.resizable = value;
    }
    get _pixelMin() {
        return this._state.min;
    }
    set _pixelMin(value) {
        this._state.min = value;
    }
    get _pixelMax() {
        return this._state.max;
    }
    set _pixelMax(value) {
        this._state.max = value;
    }
    // Applied properties that can be inherited from parent
    get _appliedDisabled() {
        var _a, _b;
        return (_b = (_a = this._disabled) !== null && _a !== void 0 ? _a : this._parentProperties.disabled) !== null && _b !== void 0 ? _b : false;
    }
    get _appliedAllowClose() {
        var _a, _b;
        return (_b = (_a = this._allowClose) !== null && _a !== void 0 ? _a : this._parentProperties.allowClose) !== null && _b !== void 0 ? _b : false;
    }
    get _appliedAutoClose() {
        var _a, _b;
        return (_b = (_a = this._autoClose) !== null && _a !== void 0 ? _a : this._parentProperties.autoClose) !== null && _b !== void 0 ? _b : false;
    }
    get _appliedAutoCloseThreshold() {
        var _a, _b;
        return (_b = (_a = this._autoCloseThreshold) !== null && _a !== void 0 ? _a : this._parentProperties.autoCloseThreshold) !== null && _b !== void 0 ? _b : 0;
    }
    initialize() {
        this._adapter.initialize();
        this._adapter.setPointerdownListener(this._pointerdownListener);
        this._adapter.setKeydownListener(this._keydownListener);
        this._getParentProperties();
        this._applyResizable();
        this._applyMin();
        this._applyMax();
        this._applySize();
        this._applyAccessibleLabel();
        this._applyOpen();
        this._applyDisabled();
        this._applyAllowClose();
        this._applyAutoClose();
        this._applyAutoCloseThreshold();
        this._isInitialized = true;
    }
    disconnect() {
        this._adapter.tryRemoveOverlay();
        this._adapter.removePointerupListener(this._pointerupListener);
        this._adapter.removePointermoveListener(this._pointermoveListener);
    }
    /**
     * Handles a pointerdown event and sets further pointer event listeners.
     * @param evt The pointer event.
     */
    _onPointerdown(evt) {
        if (this._appliedDisabled || !this._allowResize('pointer')) {
            return;
        }
        evt.preventDefault();
        this._adapter.setPointermoveListener(this._pointermoveListener);
        this._adapter.setPointerupListener(this._pointerupListener);
        this._handlePointerdown(evt);
    }
    /**
     * Handles a pointerup event and removes pointer event listeners.
     * @param evt The pointer event.
     */
    _onPointerup(evt) {
        evt.preventDefault();
        this._adapter.removePointermoveListener(this._pointermoveListener);
        this._adapter.removePointerupListener(this._pointerupListener);
        this._handlePointerup();
    }
    /**
     * Handles a pointermove event and removes pointer events if the mouse button is released.
     * @param evt The pointer event.
     */
    _onPointermove(evt) {
        if (this._appliedDisabled) {
            return;
        }
        evt.preventDefault();
        // Detect when the mouse button is released outside of the document
        if (evt.buttons === 0) {
            this._adapter.removePointermoveListener(this._pointermoveListener);
            this._adapter.removePointerupListener(this._pointerupListener);
            this._handlePointerup();
            return;
        }
        this._handlePointermove(evt);
    }
    /**
     * Handles a keydown event and sets a keyup listener if an arrow key is pressed.
     * @param evt The keyboard event.
     */
    _onKeydown(evt) {
        if (this._appliedDisabled) {
            return;
        }
        if (evt.key === 'Enter') {
            this._handleEnterKey(evt);
        }
        else if (evt.key === 'Home' && this._allowResize('keyboard')) {
            this._handleHomeKey(evt);
        }
        else if (evt.key === 'End' && this._allowResize('keyboard')) {
            this._handleEndKey(evt);
        }
        else if (eventIncludesArrowKey(evt) && this._allowResize('keyboard')) {
            this._adapter.setKeyupListener(this._keyupListener);
            this._handleArrowKey(evt);
        }
    }
    /**
     * Handles a keyup event and removes the keyup listener if an arrow key was released.
     * @param evt The keyboard event.
     */
    _onKeyup(evt) {
        if (eventIncludesArrowKey(evt)) {
            this._adapter.removeKeyupListener(this._keyupListener);
            this._handleArrowKeyUp();
        }
    }
    /**
     * Toggles the open state.
     * @param evt
     */
    _handleEnterKey(evt) {
        if (!this._appliedAllowClose) {
            return;
        }
        evt.preventDefault();
        this._tryOpenOrClose(!this._open, false, true);
    }
    /**
     * Sets panel size to the min.
     * @param evt
     */
    _handleHomeKey(evt) {
        evt.preventDefault();
        const size = minResize(this._adapter, this._state);
        this._adapter.emitHostEvent(SPLIT_VIEW_PANEL_CONSTANTS.events.RESIZE, size);
        this._tryAutoClose();
    }
    /**
     * Sets panel size to the max.
     * @param evt
     */
    _handleEndKey(evt) {
        evt.preventDefault();
        const size = maxResize(this._adapter, this._state);
        this._adapter.emitHostEvent(SPLIT_VIEW_PANEL_CONSTANTS.events.RESIZE, size);
    }
    /**
     * Runs resize logic if an arrow key is included in the event.
     * @param evt
     */
    _handleArrowKey(evt) {
        let increment = 0;
        if (this._orientation === 'horizontal') {
            switch (evt.key) {
                case 'ArrowLeft':
                    increment = -1;
                    break;
                case 'ArrowRight':
                    increment = 1;
                    break;
                default:
                    return;
            }
        }
        else {
            switch (evt.key) {
                case 'ArrowUp':
                    increment = -1;
                    break;
                case 'ArrowDown':
                    increment = 1;
                    break;
                default:
                    return;
            }
        }
        evt.preventDefault();
        if (this._resizable === 'start') {
            increment *= -1;
        }
        if (evt.shiftKey) {
            increment *= 10;
        }
        this._tryHandleArrowKeyDown();
        this._handleArrowKeyHeld(increment);
    }
    /**
     * Sets resize properties when an arrow key is first pressed.
     */
    _tryHandleArrowKeyDown() {
        if (!this._state.arrowKeyHeld) {
            this._startResize();
        }
        this._state.arrowKeyHeld = true;
    }
    /**
     * Performs cleanup logic after a keyboard driven resize.
     */
    _handleArrowKeyUp() {
        this._endResize();
    }
    /**
     * Resizes the panel by a set amount.
     * @param increment The pixel change in size.
     */
    _handleArrowKeyHeld(increment) {
        if (keyboardResize(this._adapter, increment, this._state)) {
            this._adapter.emitHostEvent(SPLIT_VIEW_PANEL_CONSTANTS.events.RESIZE, this._state.currentSize);
        }
    }
    /**
     * Handles the beginning of a pointer driven resize.
     * @param evt
     */
    _handlePointerdown(evt) {
        this._adapter.setGrabbed(true);
        this._adapter.focusHandle();
        this._startResize();
        this._state.startPoint = this._orientation === 'horizontal' ? evt.clientX : evt.clientY;
        handleBoundariesDuringResize(this._adapter, this._state, 'pointer');
    }
    /**
     * Handles the end of a pointer driven resize.
     */
    _handlePointerup() {
        this._adapter.setGrabbed(false);
        this._adapter.deactivateRipple();
        this._endResize();
    }
    /**
     * Resizes the panel from a pointer event.
     * @param evt
     */
    _handlePointermove(evt) {
        if (pointerResize(this._adapter, evt, this._state)) {
            this._adapter.emitHostEvent(SPLIT_VIEW_PANEL_CONSTANTS.events.RESIZE, this._state.currentSize);
        }
    }
    /**
     * Emits a will resize event and allows it to be cancelled.
     * @returns Whether the resize should proceed.
     */
    _allowResize(inputDeviceType) {
        return this._adapter.emitHostEvent(SPLIT_VIEW_PANEL_CONSTANTS.events.WILL_RESIZE, { inputDeviceType }, true, true);
    }
    /**
     * Handles common logic to begin a resize.
     */
    _startResize() {
        this._state = setState(this._adapter, this._state);
        this._adapter.emitHostEvent(SPLIT_VIEW_PANEL_CONSTANTS.events.RESIZE_START, this._state.startSize);
    }
    /**
     * Handles common logic to end a resize.
     */
    _endResize() {
        this._adapter.emitHostEvent(SPLIT_VIEW_PANEL_CONSTANTS.events.RESIZE_END, this._state.currentSize);
        this._adapter.updateParent({ accessibility: this._state.startSize !== this._state.currentSize, cursor: true });
        this._state = clearState(this._state);
        this._tryAutoClose();
    }
    /**
     * Emits a will open or will close event and sets the panel open or closed if allowed.
     * @param shouldOpen Whether the panel should open or close. Defaults to `true`.
     * @param auto Whether the panel auto-opened or auto-closed.
     * @param userInitiated Whether opening or closing via user action instead of programmatically.
     */
    _tryOpenOrClose(shouldOpen = true, auto = false, userInitiated = false) {
        const eventType = shouldOpen ? SPLIT_VIEW_PANEL_CONSTANTS.events.WILL_OPEN : SPLIT_VIEW_PANEL_CONSTANTS.events.WILL_CLOSE;
        const event = {
            auto,
            userInitiated
        };
        const isAllowed = this._adapter.emitHostEvent(eventType, event, true, true);
        if (!isAllowed) {
            return;
        }
        this._open = shouldOpen;
        this._applyOpen(event);
    }
    /**
     * Auto close the panel if enabled and within the size threshold.
     */
    _tryAutoClose() {
        const size = this._adapter.getContentSize(this._orientation);
        if (this._appliedAutoClose && size <= this._appliedAutoCloseThreshold) {
            this._tryOpenOrClose(false, true, false);
        }
    }
    /**
     * Sets orientation, disabled, disable close, and autoclose to reflect the parent split view.
     */
    _getParentProperties() {
        // Parent disabled state
        const parentDisabled = this._adapter.getParentProperty('disabled');
        this._parentProperties.disabled = parentDisabled;
        this._applyParentDisabled();
        // Parent disable close
        const parentAllowClose = this._adapter.getParentProperty('allowClose');
        this._parentProperties.allowClose = parentAllowClose;
        // Parent auto close
        const parentAutoClose = this._adapter.getParentProperty('autoClose');
        this._parentProperties.autoClose = parentAutoClose;
        this._applyParentAutoClose();
        // Parent auto close threshold
        const parentAutoCloseThreshold = this._adapter.getParentProperty('autoCloseThreshold');
        this._parentProperties.autoCloseThreshold = parentAutoCloseThreshold;
        this._applyParentAutoCloseThreshold();
    }
    _applyOrientation() {
        this._adapter.setOrientation(this._orientation);
    }
    /**
     * Get/set resizable. This affects the side the handle appears on and the direction the panel closes into.
     */
    get resizable() {
        return this._resizable;
    }
    set resizable(value) {
        if (this._resizable !== value) {
            this._resizable = value;
            this._applyResizable();
        }
    }
    _applyResizable() {
        this._adapter.setHostAttribute(SPLIT_VIEW_PANEL_CONSTANTS.attributes.RESIZABLE, this._resizable);
        this._adapter.setResizable(this._resizable);
    }
    /**
     * Get/set panel size.
     */
    get size() {
        return this._size;
    }
    set size(value) {
        if (this._size.toString() !== value.toString()) {
            this._size = value;
            this._applySize();
        }
    }
    _applySize() {
        const parentSize = this._adapter.getParentSize(this._orientation);
        const pixelSize = getPixelDimension(this._size, parentSize);
        this._adapter.setHostAttribute(SPLIT_VIEW_PANEL_CONSTANTS.attributes.SIZE, this._size.toString());
        this._adapter.setContentSize(pixelSize);
        // Wait for the DOM to render to get available space
        window.requestAnimationFrame(() => {
            const availableSpace = this._adapter.getAvailableSpace(this._orientation, this._resizable);
            const maxSize = safeMin(this._pixelMax, availableSpace);
            const newValue = scaleValue(pixelSize, this._pixelMin, maxSize);
            this._adapter.setValuenow(newValue);
            this._adapter.updateParent({ cursor: true });
        });
    }
    /** Get/set min panel size. */
    get min() {
        return this._min;
    }
    set min(value) {
        if (this._min.toString() !== value.toString()) {
            this._min = value;
            this._applyMin();
        }
    }
    _applyMin() {
        this._adapter.setHostAttribute(SPLIT_VIEW_PANEL_CONSTANTS.attributes.MIN, this._min.toString());
        const parentSize = this._adapter.getParentSize(this._orientation);
        this._pixelMin = getPixelDimension(this._min, parentSize);
        if (this._resizable === 'off') {
            return;
        }
        const size = this._adapter.getContentSize(this._orientation);
        if (size < this._pixelMin) {
            this.setContentSize(size);
        }
    }
    /** Get/set max panel size. */
    get max() {
        return this._max;
    }
    set max(value) {
        var _a;
        if (((_a = this._max) === null || _a === void 0 ? void 0 : _a.toString()) !== (value === null || value === void 0 ? void 0 : value.toString())) {
            this._max = value;
            this._applyMax();
        }
    }
    _applyMax() {
        var _a;
        this._adapter.toggleHostAttribute(SPLIT_VIEW_PANEL_CONSTANTS.attributes.MAX, this._max !== undefined, (_a = this._max) === null || _a === void 0 ? void 0 : _a.toString());
        if (this._max === undefined) {
            this._pixelMax = undefined;
            return;
        }
        const parentSize = this._adapter.getParentSize(this._orientation);
        this._pixelMax = getPixelDimension(this._max, parentSize);
        if (this._resizable === 'off') {
            return;
        }
        const size = this._adapter.getContentSize(this._orientation);
        if (size > this._pixelMax) {
            this.setContentSize(size);
        }
    }
    /**
     * Get/set the accessible label.
     */
    get accessibleLabel() {
        return this._accessibleLabel;
    }
    set accessibleLabel(value) {
        if (this._accessibleLabel !== value) {
            this._accessibleLabel = value;
            this._applyAccessibleLabel();
        }
    }
    _applyAccessibleLabel() {
        this._adapter.setHostAttribute(SPLIT_VIEW_PANEL_CONSTANTS.attributes.ACCESSIBLE_LABEL, this._accessibleLabel);
        this._adapter.setAccessibleLabel(this._accessibleLabel);
    }
    /**
     * Get/set whether the panel is open.
     */
    get open() {
        return this._open;
    }
    set open(value) {
        if (this._open !== value) {
            if (this._isInitialized) {
                this._tryOpenOrClose(value, false, false);
                return;
            }
            this._open = value;
            const event = this._isInitialized ? {
                auto: false,
                userInitiated: false
            } : undefined;
            this._applyOpen(event);
        }
    }
    _applyOpen(event) {
        this._adapter.setHostAttribute(SPLIT_VIEW_PANEL_CONSTANTS.attributes.OPEN, this._open.toString());
        this._adapter.setOpen(this._open, this._isInitialized, event);
    }
    /**
     * Get/set whether interactions are disabled.
     */
    get disabled() {
        return this._disabled;
    }
    set disabled(value) {
        if (this._disabled !== value) {
            this._disabled = value;
            this._applyDisabled();
        }
    }
    _applyDisabled() {
        var _a;
        this._adapter.toggleHostAttribute(SPLIT_VIEW_PANEL_CONSTANTS.attributes.DISABLED, (_a = this._disabled) !== null && _a !== void 0 ? _a : false);
        this._adapter.setDisabled(this._appliedDisabled);
    }
    _applyParentDisabled() {
        if (this._isInitialized && !isDefined(this._disabled)) {
            this._adapter.setDisabled(this._appliedDisabled);
        }
    }
    /**
     * Get/set whether closing the panel is disabled.
     */
    get allowClose() {
        return this._allowClose;
    }
    set allowClose(value) {
        if (this._allowClose !== value) {
            this._allowClose = value;
            this._applyAllowClose();
        }
    }
    _applyAllowClose() {
        var _a;
        this._adapter.toggleHostAttribute(SPLIT_VIEW_PANEL_CONSTANTS.attributes.ALLOW_CLOSE, (_a = this._allowClose) !== null && _a !== void 0 ? _a : false);
    }
    /**
     * Get/set whether the panel closes when a threshold size is reached.
     */
    get autoClose() {
        return this._autoClose;
    }
    set autoClose(value) {
        if (this._autoClose !== value) {
            this._autoClose = value;
            this._applyAutoClose();
        }
    }
    _applyAutoClose() {
        var _a;
        this._adapter.toggleHostAttribute(SPLIT_VIEW_PANEL_CONSTANTS.attributes.AUTO_CLOSE, (_a = this._autoClose) !== null && _a !== void 0 ? _a : false);
        if (this._isInitialized) {
            this._tryAutoClose();
        }
    }
    _applyParentAutoClose() {
        if (this._isInitialized && !isDefined(this._autoClose)) {
            this._tryAutoClose();
        }
    }
    /** Get/set the size at which the panel auto closes. */
    get autoCloseThreshold() {
        return this._autoCloseThreshold;
    }
    set autoCloseThreshold(value) {
        if (this._autoCloseThreshold !== value) {
            this._autoCloseThreshold = value;
            this._applyAutoCloseThreshold();
        }
    }
    _applyAutoCloseThreshold() {
        var _a;
        this._adapter.toggleHostAttribute(SPLIT_VIEW_PANEL_CONSTANTS.attributes.AUTO_CLOSE_THRESHOLD, isDefined(this._autoCloseThreshold), (_a = this.autoCloseThreshold) === null || _a === void 0 ? void 0 : _a.toString());
        if (this._isInitialized) {
            this._tryAutoClose();
        }
    }
    _applyParentAutoCloseThreshold() {
        if (this._isInitialized && !isDefined(this._autoCloseThreshold)) {
            this._tryAutoClose();
        }
    }
    /**
     * Gets the size of panel content without the handle.
     * @returns Content size in pixels.
     */
    getContentSize() {
        return this._adapter.getContentSize(this._orientation);
    }
    /**
     * Gets how much the panel can shrink from its current size.
     * @returns The difference between the current and min size in pixels.
     */
    getCollapsibleSize() {
        const parentSize = this._adapter.getParentSize(this._orientation);
        const pixelMin = getPixelDimension(this._min, parentSize);
        return this._adapter.getContentSize(this._orientation) - pixelMin;
    }
    /**
     * Sets a new size for the content area.
     * @param size The new content size in pixels.
     */
    setContentSize(size) {
        if (this._resizable === 'off') {
            return;
        }
        const newSize = clampSize(size, this._state);
        this._adapter.setContentSize(newSize);
        if (this._isInitialized) {
            this._adapter.emitHostEvent(SPLIT_VIEW_PANEL_CONSTANTS.events.RESIZE, newSize);
        }
    }
    /**
     * Updates the proved characteristics.
     * @param config An update configuration.
     */
    update(config) {
        // Orientation
        if (config.orientation) {
            this._orientation = config.orientation;
            this._applyOrientation();
        }
        // Parent properties
        if (config.properties) {
            if (isDefined(config.properties.disabled) && this._parentProperties.disabled !== config.properties.disabled) {
                this._parentProperties.disabled = config.properties.disabled;
                this._applyDisabled();
            }
            if (isDefined(config.properties.allowClose)) {
                this._parentProperties.allowClose = config.properties.allowClose;
            }
            if (isDefined(config.properties.autoClose) && this._parentProperties.autoClose !== config.properties.autoClose) {
                this._parentProperties.autoClose = config.properties.autoClose;
                if (!isDefined(this._autoClose)) {
                    this._applyAutoClose();
                }
            }
            if (isDefined(config.properties.autoCloseThreshold) && this._parentProperties.autoCloseThreshold !== config.properties.autoCloseThreshold) {
                this._parentProperties.autoCloseThreshold = config.properties.autoCloseThreshold;
                if (!isDefined(this._autoCloseThreshold)) {
                    this._applyAutoCloseThreshold();
                }
            }
        }
        // Size
        if (config.size && this.open) {
            const parentSize = this._adapter.getParentSize(this._orientation);
            this._pixelMin = getPixelDimension(this._min, parentSize);
            this._pixelMax = isDefined(this._max) ? getPixelDimension(this._max, parentSize) : undefined;
            this.setContentSize(this._adapter.getContentSize(this._orientation));
        }
        // The following properties don't apply to non-resizable panels
        if (this._resizable === 'off') {
            return;
        }
        const size = this._adapter.getContentSize(this._orientation);
        const availableSpace = this._adapter.getAvailableSpace(this._orientation, this._resizable);
        // Accessibility
        if (config.accessibility && this.open) {
            const valueNow = getValuenow(size, Object.assign(Object.assign({}, this._state), { availableSpace }));
            this._adapter.setValuenow(valueNow);
        }
        // Contextual cursor
        if (config.cursor) {
            handleBoundariesAfterResize(this._adapter, size, Object.assign(Object.assign({}, this._state), { availableSpace }));
        }
    }
}
