// #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
/**
 * Normalizes a CSS length attribute value.
 * If the value is a pure number (e.g., "50"), it appends "px".
 * Otherwise, it returns the value as-is (e.g., "50%", "10rem").
 *
 * @param value - The attribute value to normalize
 * @param defaultValue - The default value to return if value is null/empty
 * @returns A valid CssLength value
 */
function normalizeCssLengthAttribute(value, defaultValue) {
    if (!value) {
        return defaultValue;
    }
    // Check if the value is a pure number (no unit)
    const trimmed = value.trim();
    if ((/^-?\d+(\.\d+)?$/).test(trimmed)) {
        return `${trimmed}px`;
    }
    return trimmed;
}
/**
 * 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-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;
    _size;
    _position;
    _lock;
    _snaps;
    _snapThreshold;
    _thickness;
    _min;
    _max;
    // #endregion
    // #region Ctor
    /**
     * Constructs a new instance of the `SplitElement` class.
     *
     * @public
     */
    constructor() {
        super();
        this._position = '50%';
        this._lock = SplitLock.None;
        this._snaps = [];
        this._snapThreshold = 12;
        this._thickness = 2;
        this._min = '0%';
        this._max = '100%';
        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.
     * The position of the divider. Accepts any valid CSS length value except 'auto'.
     *
     * @example '50%', '200px', '10rem'
     * @public
     * @attr
     */
    get position() {
        return this._position;
    }
    set position(value) {
        if (this._position !== value) {
            this._position = value;
            this.requestUpdate('position');
        }
    }
    /**
     * Gets the position as a percentage value (0-100).
     * This is a computed property used internally for calculations.
     *
     * @public
     * @readonly
     */
    get positionPercentage() {
        return this.cssLengthToPercentage(this._position);
    }
    /**
     * Gets the position in pixels.
     * This is a computed property used internally for calculations.
     *
     * @public
     * @readonly
     */
    get positionInPixels() {
        return this.cssLengthToPixels(this._position);
    }
    /**
     * 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 `min` property.
     * The minimum position constraint for the primary panel.
     * Accepts any valid CSS length value except 'auto' (e.g., '10%', '100px').
     *
     * @public
     * @attr
     */
    get min() {
        return this._min;
    }
    set min(value) {
        if (this._min !== value) {
            this._min = value;
            this.requestUpdate('min');
        }
    }
    /**
     * Gets or sets the `max` property.
     * The maximum position constraint for the primary panel.
     * Accepts any valid CSS length value except 'auto' (e.g., '90%', '500px').
     *
     * @public
     * @attr
     */
    get max() {
        return this._max;
    }
    set max(value) {
        if (this._max !== value) {
            this._max = value;
            this.requestUpdate('max');
        }
    }
    /**
     * 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);
            // Detect size after render when the element has actual dimensions
            this.detectSize();
        });
    }
    /**
     * @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) => {
                    const snapPoint = this.cssLengthToPixels(value);
                    const adjustedSnapPoint = this.dir === FlowDirection.RightToLeft && this.orientation === Orientation.Horizontal
                        ? this._size - snapPoint
                        : snapPoint;
                    if (newPositionInPixels >= adjustedSnapPoint - this.snapThreshold && newPositionInPixels <= adjustedSnapPoint + this.snapThreshold) {
                        newPositionInPixels = adjustedSnapPoint;
                    }
                });
                // Clamp to min/max constraints
                const minPixels = this.cssLengthToPixels(this.min);
                const maxPixels = this.cssLengthToPixels(this.max);
                newPositionInPixels = clamp(newPositionInPixels, minPixels, maxPixels);
                // Convert back to percentage for consistent storage
                const percentage = this.pixelsToPercentage(newPositionInPixels);
                this.position = `${percentage}%`;
            },
            initialEvent: event
        });
    }
    /**
     * @private
     */
    handleKeyDown(event) {
        if (this.disabled) {
            return;
        }
        if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) {
            const currentPercentage = this.positionPercentage;
            const minPercentage = this.cssLengthToPercentage(this.min);
            const maxPercentage = this.cssLengthToPercentage(this.max);
            const incr = (event.shiftKey ? 10 : 1) * (this.lock === 'end' ? -1 : 1);
            let newPercentage = currentPercentage;
            event.preventDefault();
            if ((event.key === 'ArrowLeft' && this.orientation === Orientation.Horizontal) || (event.key === 'ArrowUp' && this.orientation === Orientation.Vertical)) {
                newPercentage -= incr;
            }
            if ((event.key === 'ArrowRight' && this.orientation === Orientation.Horizontal) || (event.key === 'ArrowDown' && this.orientation === Orientation.Vertical)) {
                newPercentage += incr;
            }
            if (event.key === 'Home') {
                newPercentage = this.lock === 'end' ? maxPercentage : minPercentage;
            }
            if (event.key === 'End') {
                newPercentage = this.lock === 'end' ? minPercentage : maxPercentage;
            }
            newPercentage = clamp(newPercentage, minPercentage, maxPercentage);
            this.position = `${newPercentage}%`;
        }
    }
    /**
     * Emits the {@link splitRepositioned} event.
     *
     * @protected
     */
    onSplitRepositioned(args) {
        this._splitRepositioned.emit(args);
    }
    /**
     * @protected
     */
    onPositionPropertyChanged(_prev, _next) {
        this.onSplitRepositioned({});
    }
    /**
     * @protected
     */
    onOrientationPropertyChanged(_prev, _next) {
        this.detectSize();
    }
    /**
     * Converts a CSS length value to pixels.
     *
     * @private
     */
    cssLengthToPixels(value) {
        // Handle raw number (treated as pixels)
        if (typeof value === 'number') {
            return value;
        }
        // Handle percentage
        if (CssLength.isPercentage(value)) {
            return this._size * (CssLength.extractValue(value) / 100);
        }
        // Handle other CSS length units (px, rem, etc.)
        return CssLength.extractValue(value);
    }
    /**
     * Converts a CSS length value to percentage.
     *
     * @private
     */
    cssLengthToPercentage(value) {
        // Handle raw number (treated as pixels, convert to percentage)
        if (typeof value === 'number') {
            return this._size > 0 ? (value / this._size) * 100 : 0;
        }
        // Handle percentage
        if (CssLength.isPercentage(value)) {
            return CssLength.extractValue(value);
        }
        // Convert pixel value to percentage
        const pixels = CssLength.extractValue(value);
        return this._size > 0 ? (pixels / this._size) * 100 : 0;
    }
    /**
     * @private
     */
    detectSize() {
        const rect = this.getBoundingClientRect();
        this._size = this.orientation === Orientation.Vertical
            ? rect.height
            : rect.width;
    }
    /**
     * @private
     */
    pixelsToPercentage(value) {
        return this._size > 0 ? (value / this._size) * 100 : 0;
    }
    /**
     * @private
     */
    handleResize(entries) {
        if (entries.length === 0) {
            return;
        }
        const rect = entries[0].contentRect;
        const previousSize = this._size;
        this._size = this.orientation === Orientation.Vertical ? rect.height : rect.width;
        // When locked, maintain the absolute pixel position during resize
        if (this.lock !== SplitLock.None && previousSize > 0) {
            const positionString = String(this._position);
            // If position is a percentage, convert to maintain absolute size
            if (positionString.endsWith('%')) {
                const absolutePixels = previousSize * (parseFloat(positionString) / 100);
                const newPercentage = this._size > 0 ? (absolutePixels / this._size) * 100 : 0;
                this._position = `${newPercentage}%`;
                this.requestUpdate('position');
            }
        }
    }
    /**
     * @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: String,
        converter: {
            fromAttribute: (value) => normalizeCssLengthAttribute(value, '50%'),
            toAttribute: (value) => String(value)
        }
    }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], SplitElement.prototype, "position", 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) => normalizeCssLengthAttribute(v.trim(), '0px'));
            },
            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([
    Attribute({
        type: String,
        converter: {
            fromAttribute: (value) => normalizeCssLengthAttribute(value, '0%'),
            toAttribute: (value) => String(value)
        }
    }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], SplitElement.prototype, "min", null);
__decorate([
    Attribute({
        type: String,
        converter: {
            fromAttribute: (value) => normalizeCssLengthAttribute(value, '100%'),
            toAttribute: (value) => String(value)
        }
    }),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [Object])
], SplitElement.prototype, "max", 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", [Object, Object]),
    __metadata("design:returntype", void 0)
], SplitElement.prototype, "onPositionPropertyChanged", 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