// #region Imports
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { Emit } from '../../../../Events/Decorators/EmitDecorator';
import { on, HTMLElementEventEmitter } from '@breadstone/mosaik-elements';
import { clamp } from '../../../../Extensions/NumberExtensions';
import { CssLength } from '@breadstone/mosaik-themes';
import { Disableable } from '../../../Behaviors/Disableable';
import { Fitable } from '../../../Behaviors/Fitable';
import { Orientable } from '../../../Behaviors/Orientable';
import { Reversible } from '../../../Behaviors/Reversible';
import { ResizeController } from '../../../Controllers/ResizeController';
import { Attribute } from '../../../Decorators/AttributeDecorator';
import { Component } from '../../../Decorators/ComponentDecorator';
import { Property } from '../../../Decorators/PropertyDecorator';
import { Watch } from '../../../Decorators/WatchDecorator';
import { FlowDirection } from '../../../Types/FlowDirection';
import { Orientation } from '../../../Types/Orientation';
import { SplitLock } from '../../../Types/SplitLock';
import { CustomElement } from '../../Abstracts/CustomElement';
import { IconElement } from '../../Media/Icon/IconElement';
import { splitElementTemplate } from './SplitElementTemplate';
import { splitElementJoyStyle } from './Themes/SplitElement.Joy';
import { splitElementMemphisStyle } from './Themes/SplitElement.Memphis';
import { splitElementCosmopolitanStyle } from './Themes/SplitElement.Cosmopolitan';
import { Themeable } from '../../../Behaviors/Themeable';
import { Slottable } from '../../../Behaviors/Slottable';
// #endregion
/**
 * Split - A layout component that creates a resizable split pane layout with a draggable divider.
 *
 * Enables users to dynamically resize two content areas separated by an interactive divider.
 * Supports both horizontal and vertical orientations, position locking, snapping, and keyboard navigation.
 *
 * @name SplitElement
 * @element mosaik-split
 * @category Layout
 *
 * @slot start - The primary content panel positioned at the beginning of the split
 * @slot end - The secondary content panel positioned at the end of the split
 * @slot thumb - Custom grip icon for the draggable divider (defaults to three dots icon)
 *
 * @csspart panel - The container for each split panel content
 * @csspart start - The start panel container
 * @csspart end - The end panel container
 * @csspart divider - The interactive draggable divider element
 * @csspart thumb - The visual grip icon within the divider
 *
 * @cssprop --split-min - Minimum size constraint for the primary panel (default: 0%)
 * @cssprop --split-max - Maximum size constraint for the primary panel (default: 100%)
 * @cssprop --split-divider-width - Width/height of the divider based on orientation
 *
 * @dependency IconElement - For the default thumb grip icon
 *
 * @example
 * ```html
 * <!-- Basic split layout -->
 * <mosaik-split position="30">
 *   <div slot="start">Left Panel</div>
 *   <div slot="end">Right Panel</div>
 * </mosaik-split>
 *
 * <!-- Vertical split with snapping -->
 * <mosaik-split orientation="vertical" snaps="25% 50% 75%">
 *   <div slot="start">Top Panel</div>
 *   <div slot="end">Bottom Panel</div>
 * </mosaik-split>
 *
 * <!-- Locked split with custom thickness -->
 * <mosaik-split lock="start" thickness="4" position="200px">
 *   <nav slot="start">Navigation</nav>
 *   <main slot="end">Content</main>
 * </mosaik-split>
 * ```
 *
 * @fires splitRepositioned - Fired when the split position changes via user interaction
 *
 * @public
 */
let SplitElement = class SplitElement extends Themeable(Reversible(Orientable(Disableable(Fitable(Slottable(CustomElement)))))) {
    // #region Fields
    _resizeController;
    _splitRepositioned;
    _cachedPositionInPixels;
    _size;
    _position;
    _positionInPixels;
    _lock;
    _snaps;
    _snapThreshold;
    _thickness;
    // #endregion
    // #region Ctor
    /**
     * Constructs a new instance of the `SplitElement` class.
     *
     * @public
     */
    constructor() {
        super();
        this._position = 0;
        this._positionInPixels = 0;
        this._lock = SplitLock.None;
        this._snaps = [];
        this._snapThreshold = 12;
        this._thickness = 2;
        this._cachedPositionInPixels = 0;
        // this._resizeObserver = null;
        this._size = 0;
        this._resizeController = new ResizeController(this, {
            callback: (entries) => this.handleResize(entries)
        });
        this._splitRepositioned = new HTMLElementEventEmitter(this, 'splitRepositioned');
    }
    // #endregion
    // #region Properties
    /**
     * Returns the `{@link is}` property.
     * The `{@link is}` property represents natural name of this element.
     *
     * @public
     * @static
     * @readonly
     */
    static get is() {
        return 'mosaik-split';
    }
    /**
     * Gets or sets the `position` property.
     *
     * @public
     * @attr
     */
    get position() {
        return this._position;
    }
    set position(value) {
        if (this._position !== value) {
            this._position = value;
            this.requestUpdate('position');
        }
    }
    /**
     * Gets or sets the `positionInPixels` property.
     *
     * @public
     * @attr
     */
    get positionInPixels() {
        return this._positionInPixels;
    }
    set positionInPixels(value) {
        if (this._positionInPixels !== value) {
            this._positionInPixels = value;
            this.requestUpdate('positionInPixels');
        }
    }
    /**
     * Gets or sets the `lock` property.
     *
     * @public
     * @attr
     */
    get lock() {
        return this._lock;
    }
    set lock(value) {
        if (this._lock !== value) {
            this._lock = value;
            this.requestUpdate('lock');
        }
    }
    /**
     * Gets or sets the `snap` property.
     * The `snap` is a space separated list of snap points.
     * When the split is dragged, it will snap to the nearest snap point.
     *
     * @public
     * @attr
     */
    get snaps() {
        return this._snaps;
    }
    set snaps(value) {
        if (this._snaps !== value) {
            this._snaps = value;
            this.requestUpdate('snaps');
        }
    }
    /**
     * Gets or sets the `snapThreshold` property.
     *
     * @public
     * @attr
     */
    get snapThreshold() {
        return this._snapThreshold;
    }
    set snapThreshold(value) {
        if (this._snapThreshold !== value) {
            this._snapThreshold = value;
            this.requestUpdate('snapThreshold');
        }
    }
    /**
     * Gets or sets the `thickness` property.
     *
     * @public
     * @attr
     */
    get thickness() {
        return this._thickness;
    }
    set thickness(value) {
        if (this._thickness !== value) {
            this._thickness = value;
            this.requestUpdate('thickness');
        }
    }
    /**
     * Gets or sets the `hasPanels` property.
     *
     * @public
     */
    get hasPanels() {
        return this.getSlotAssignments('start').length > 0 && this.getSlotAssignments('end').length > 0;
    }
    /**
     * Called when the split is repositioned.
     * Provides reference to `{@link IEventDetail}` as event detail.
     *
     * @public
     * @readonly
     * @eventProperty
     */
    get splitRepositioned() {
        return this._splitRepositioned;
    }
    // #endregion
    // #region Methods
    /**
     * @public
     * @override
     */
    connectedCallback() {
        super.connectedCallback();
        void this.updateComplete.then(() => {
            this._resizeController.observe(this);
        });
        this.detectSize();
        if (this.positionInPixels && this.position === 0) {
            this._position = this.pixelsToPercentage(this.positionInPixels);
        }
        this._cachedPositionInPixels = this.positionInPixels
            ? this.positionInPixels
            : this.percentageToPixels(this.position);
    }
    /**
     * @public
     * @override
     */
    disconnectedCallback() {
        this._resizeController.unobserve(this);
        super.disconnectedCallback();
    }
    /**
     * @public
     * @override
     */
    onSlotChanges(_slotName) {
        this.requestUpdate('hasPanels');
    }
    /**
     * @private
     */
    handleDrag(event) {
        if (this.disabled) {
            return;
        }
        // Prevent text selection when dragging
        if (event.cancelable) {
            event.preventDefault();
        }
        this.drag(this, {
            onMove: (x, y) => {
                let newPositionInPixels = this.orientation === Orientation.Vertical
                    ? y
                    : x;
                // Flip for end panels
                if (this.lock === 'end') {
                    newPositionInPixels = this._size - newPositionInPixels;
                }
                // Check snap points
                this.snaps.forEach((value) => {
                    let snapPoint = null;
                    if (typeof value === 'number') {
                        snapPoint = value;
                    }
                    else if (value.endsWith('%')) {
                        snapPoint = this._size * (parseFloat(value) / 100);
                    }
                    else {
                        snapPoint = CssLength.extractValue(value);
                    }
                    if (this.dir === FlowDirection.RightToLeft && this.orientation === Orientation.Horizontal) {
                        snapPoint = this._size - snapPoint;
                    }
                    if (newPositionInPixels >= snapPoint - this.snapThreshold && newPositionInPixels <= snapPoint + this.snapThreshold) {
                        newPositionInPixels = snapPoint;
                    }
                });
                this.position = clamp(this.pixelsToPercentage(newPositionInPixels), 0, 100);
            },
            initialEvent: event
        });
    }
    /**
     * @private
     */
    handleKeyDown(event) {
        if (this.disabled) {
            return;
        }
        // TODO: replace with KeyboardController
        if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) {
            let newPosition = this.position;
            const incr = (event.shiftKey ? 10 : 1) * (this.lock === 'end' ? -1 : 1);
            event.preventDefault();
            if ((event.key === 'ArrowLeft' && this.orientation === Orientation.Horizontal) || (event.key === 'ArrowUp' && this.orientation === Orientation.Vertical)) {
                newPosition -= incr;
            }
            if ((event.key === 'ArrowRight' && this.orientation === Orientation.Horizontal) || (event.key === 'ArrowDown' && this.orientation === Orientation.Vertical)) {
                newPosition += incr;
            }
            if (event.key === 'Home') {
                newPosition = this.lock === 'end' ? 100 : 0;
            }
            if (event.key === 'End') {
                newPosition = this.lock === 'end' ? 0 : 100;
            }
            this.position = clamp(newPosition, 0, 100);
        }
    }
    /**
     * Emits the {@link splitRepositioned} event.
     *
     * @protected
     */
    onSplitRepositioned(args) {
        this._splitRepositioned.emit(args);
    }
    /**
     * @protected
     */
    onPositionPropertyChanged(_prev, next) {
        this._cachedPositionInPixels = next ? this.percentageToPixels(next) : 0;
        this._positionInPixels = next ? this.percentageToPixels(next) : 0;
        this.onSplitRepositioned({});
    }
    /**
     * @protected
     */
    onPositionInPixelsPropertyChanged(_prev, next) {
        this._position = next ? this.pixelsToPercentage(next) : 0;
        this.onSplitRepositioned({});
    }
    /**
     * @protected
     */
    onOrientationPropertyChanged(_prev, _next) {
        this.detectSize();
    }
    /**
     * @private
     */
    detectSize() {
        const rect = this.getBoundingClientRect();
        this._size = this.orientation === Orientation.Vertical
            ? rect.height
            : rect.width;
    }
    /**
     * @private
     */
    percentageToPixels(value) {
        return this._size * (value / 100);
    }
    /**
     * @private
     */
    pixelsToPercentage(value) {
        return (value / this._size) * 100;
    }
    /**
     * @private
     */
    handleResize(entries) {
        if (entries.length === 0) {
            return;
        }
        const rect = entries[0].contentRect;
        this._size = this.orientation === Orientation.Vertical ? rect.height : rect.width;
        // Resize when a primary panel is set
        if (this.lock !== SplitLock.None) {
            this.position = this.pixelsToPercentage(this._cachedPositionInPixels);
        }
    }
    /**
     * @private
     */
    drag(container, options) {
        const pointermove = on(document, 'pointermove', (e) => {
            const dims = container.getBoundingClientRect();
            const defaultView = container.ownerDocument.defaultView;
            const offsetX = dims.left + (defaultView?.scrollX ?? 0);
            const offsetY = dims.top + (defaultView?.scrollY ?? 0);
            const x = e.pageX - offsetX;
            const y = e.pageY - offsetY;
            if (options?.onMove) {
                options.onMove(x, y);
            }
        }, { passive: true });
        const pointerup = on(document, 'pointerup', () => {
            pointermove.dispose();
            pointerup.dispose();
            if (options?.onStop) {
                options.onStop();
            }
        });
        // If an initial event is set, trigger the first drag immediately
        if (options?.initialEvent instanceof PointerEvent) {
            pointermove.invoke(options.initialEvent);
        }
    }
};
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], SplitElement.prototype, "position", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], SplitElement.prototype, "positionInPixels", null);
__decorate([
    Attribute({ type: SplitLock }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], SplitElement.prototype, "lock", null);
__decorate([
    Attribute({
        type: Array,
        converter: {
            fromAttribute: (value) => {
                if (!value) {
                    return [];
                }
                return value
                    .split(' ')
                    .map((v) => v.trim());
            },
            toAttribute: (value) => value.join(' ')
        }
    }),
    __metadata("design:type", Array),
    __metadata("design:paramtypes", [Array])
], SplitElement.prototype, "snaps", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], SplitElement.prototype, "snapThreshold", null);
__decorate([
    Attribute({ type: Number }),
    __metadata("design:type", Number),
    __metadata("design:paramtypes", [Number])
], SplitElement.prototype, "thickness", null);
__decorate([
    Property({ type: Boolean }),
    __metadata("design:type", Boolean),
    __metadata("design:paramtypes", [])
], SplitElement.prototype, "hasPanels", null);
__decorate([
    Emit({ eventName: 'splitRepositioned' }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], SplitElement.prototype, "splitRepositioned", null);
__decorate([
    Watch('position'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number, Number]),
    __metadata("design:returntype", void 0)
], SplitElement.prototype, "onPositionPropertyChanged", null);
__decorate([
    Watch('positionInPixels'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number, Number]),
    __metadata("design:returntype", void 0)
], SplitElement.prototype, "onPositionInPixelsPropertyChanged", null);
__decorate([
    Watch('orientation'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Boolean, Boolean]),
    __metadata("design:returntype", void 0)
], SplitElement.prototype, "onOrientationPropertyChanged", null);
SplitElement = __decorate([
    Component({
        selector: 'mosaik-split',
        template: splitElementTemplate,
        themes: {
            joy: splitElementJoyStyle,
            memphis: splitElementMemphisStyle,
            cosmopolitan: splitElementCosmopolitanStyle
        },
        imports: [
            IconElement
        ]
    }),
    __metadata("design:paramtypes", [])
], SplitElement);
export { SplitElement };
//# sourceMappingURL=SplitElement.js.map