import * as i2 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { EventEmitter, Component, ChangeDetectionStrategy, Self, Optional, Output, Input, ContentChild, HostListener, NgModule } from '@angular/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { Destroy } from '@deja-js/component/core';
import { fromEvent, mergeWith, take, map, takeUntil } from 'rxjs';
import * as i1 from '@angular/forms';

/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */
/**
 * IRange class implementation
 */
class Range {
    constructor(min, max, $width, trackBy) {
        this.min = min;
        this.max = max;
        this.$width = $width;
        this.trackBy = trackBy;
    }
}

/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */
class DejaRangeComponent extends Destroy {
    constructor(changeDetectorRef, elementRef, control) {
        super();
        this.changeDetectorRef = changeDetectorRef;
        this.elementRef = elementRef;
        this.control = control;
        // emit the selected range
        // eslint-disable-next-line @angular-eslint/no-output-native
        this.select = new EventEmitter();
        // error emitter, used to notify the outside when forbidden actions are performed
        this.errorFeedback = new EventEmitter();
        // step can be either a numeric value, an array of accepted intervals or a function returning the next accepted interval
        this.step = 1;
        // index of the selected range
        this.selected = 0;
        // minimum range percentage, used to avoid 2 separator being on the same visual space
        this.minimumRangePercentage = 0.01;
        this._readOnly = true;
        this._disabled = false;
        // eslint-disable-next-line @typescript-eslint/naming-convention
        this._onChangeCallback = (_a) => undefined;
        // eslint-disable-next-line @typescript-eslint/naming-convention
        this._onTouchCallback = () => undefined;
        if (this.control) {
            this.control.valueAccessor = this;
        }
    }
    onResize() {
        this.ranges = this.ranges.concat();
    }
    registerOnChange(fn) {
        this._onChangeCallback = fn;
    }
    registerOnTouched(fn) {
        this._onTouchCallback = fn;
    }
    // inner model
    get ranges() {
        return this._ranges || [];
    }
    set ranges(ranges) {
        if (ranges) {
            this.writeValue(ranges);
            this._onChangeCallback(ranges);
        }
    }
    set disabled(value) {
        this._disabled = coerceBooleanProperty(value);
        this.changeDetectorRef.markForCheck();
    }
    get disabled() {
        return this._disabled;
    }
    set readOnly(value) {
        this._readOnly = coerceBooleanProperty(value);
    }
    get readOnly() {
        return this._readOnly || this.disabled;
    }
    // ControlValueAccessor implementation
    writeValue(ranges) {
        if (!!ranges && !!ranges.length) {
            const host = this.elementRef.nativeElement.firstElementChild;
            const hostWidth = host.getBoundingClientRect().width;
            const totalDifference = ranges[ranges.length - 1].max - ranges[0].min;
            this._ranges = ranges.map((range, index) => {
                // calculate new width
                const difference = ranges[index].max - ranges[index].min;
                const modelPercent = totalDifference / 100;
                const percent = difference / modelPercent;
                const viewValue = hostWidth * percent / 100;
                range.$width = +viewValue.toFixed(2);
                return range;
            });
            this.changeDetectorRef.markForCheck();
        }
    }
    setDisabledState(isDisabled) {
        this.disabled = isDisabled;
    }
    // End of ControlValueAccessor implementation
    // add a new range, by splitting the selected one into 2 new ranges
    add() {
        if (!this.readOnly) {
            if (typeof this.step === 'number') {
                const ranges = this.ranges;
                const index = this.selected;
                const selected = ranges[index];
                const difference = selected.max - selected.min;
                const totalDifference = ranges[ranges.length - 1].max - ranges[0].min;
                const minimumViewDifference = this.minimumRangePercentage * 2;
                const modelDifferencePercentage = difference / totalDifference;
                const isViewDifferenceEnough = modelDifferencePercentage > minimumViewDifference;
                let newMax;
                let newRange;
                const isModelDifferenceEnough = difference >= this.step * 2;
                if (isViewDifferenceEnough && isModelDifferenceEnough) {
                    newMax = selected.min + difference / 2;
                    newRange = new Range(selected.min, newMax);
                    selected.min = newMax;
                    // split array in half excluding the selected range
                    const leftSide = ranges.slice(0, index);
                    const rightSide = ranges.length - 1 > index ? ranges.slice(index + 1) : [];
                    // build new array with new range
                    let newRanges = [...leftSide, newRange, selected, ...rightSide];
                    // step
                    const newRangeIndex = newRanges.indexOf(newRange);
                    newRanges[newRangeIndex].max = this.toStep(newRanges, newRangeIndex, newRanges[newRangeIndex].max);
                    newRanges = newRanges
                        .map((range, _index) => {
                        if (_index !== 0) {
                            range.min = newRanges[_index - 1].max;
                        }
                        return range;
                    });
                    this.ranges = newRanges;
                }
                else {
                    this.errorFeedback.emit(new Error('Range is too small to be splitted'));
                }
            }
            else {
                throw new Error('Invalid step type, you have to implement the add function yourself for the fn & array.');
            }
        }
    }
    // remove the select range
    remove() {
        if (!this.readOnly && this.ranges.length > 2) {
            const ranges = this.ranges
                .filter((_range, index) => this.selected !== index);
            this.ranges = ranges
                .map((range, index) => {
                if (index !== ranges.length - 1) {
                    range.max = ranges[index + 1].min;
                }
                return range;
            });
        }
    }
    /** Force control to rebind */
    refresh() {
        this.changeDetectorRef.markForCheck();
    }
    // set the new selected index and emit a IRangeEvent
    onSelect(e, index) {
        if (this.disabled) {
            return;
        }
        if (this.selected !== index) {
            const event = e;
            event.range = this.ranges[index];
            event.index = index;
            event.ranges = this.ranges;
            this.select.emit(event);
            this.selected = index;
        }
    }
    onMouseDown($event, index) {
        if (!this.readOnly) {
            const xStart = $event.pageX;
            const target = $event.target;
            const ranges = this.ranges;
            const range = this.ranges[index];
            const rangeStart = range.max;
            // get the block HTMLElement (contains range HTMLElement & separator HTMLElement)
            let parentElement = target.parentElement;
            // eslint-disable-next-line no-loops/no-loops
            while (!parentElement.classList.contains('block')) {
                parentElement = parentElement.parentElement;
            }
            const up$ = fromEvent(document, 'mouseup');
            const leave$ = fromEvent(document.body, 'mouseleave');
            const kill$ = up$.pipe(mergeWith(leave$), take(1), map(() => {
                const host = this.elementRef.nativeElement.firstElementChild;
                host.ownerDocument.body.classList.remove('noselect');
                this._onChangeCallback(this._ranges);
            }), mergeWith(this.destroyed$));
            const move$ = fromEvent(document, 'mousemove');
            move$.pipe(takeUntil(kill$)).subscribe(event => {
                const x = event.pageX;
                const xDifference = -(xStart - x);
                const nextRange = this.ranges[index + 1];
                // compute total difference
                const totalDifference = ranges[ranges.length - 1].max - ranges[0].min;
                // calculate new width of the range, get host width
                const host = this.elementRef.nativeElement.firstElementChild;
                const hostWidth = host.getBoundingClientRect().width;
                // avoid drag
                host.ownerDocument.body.classList.add('noselect');
                // compute new model value
                const modelDifference = xDifference * totalDifference / hostWidth;
                let newMax = rangeStart + modelDifference;
                const minDifference = this.minimumRangePercentage * totalDifference;
                const min = range.min + minDifference;
                const max = nextRange.max - minDifference;
                newMax = Math.min(newMax, max);
                newMax = Math.max(newMax, min);
                const newStepped = this.toStep(ranges, index, newMax);
                nextRange.min = range.max = newStepped;
                ranges[index] = range;
                ranges[index + 1] = nextRange;
                this.writeValue(ranges);
            });
        }
    }
    trackBy(_index, range) {
        return range.trackBy || range;
    }
    toStep(ranges, index, newMax) {
        const range = ranges[index];
        const maybeNextIndex = ranges.length - 1 > index ? index + 1 : index;
        const nextMax = ranges[maybeNextIndex].max;
        const previousMax = index !== 0 ? ranges[index - 1].max : 0;
        const totalDifference = ranges[ranges.length - 1].max - ranges[0].min;
        const minimumViewDifference = this.minimumRangePercentage * totalDifference;
        const viewMin = range.min + minimumViewDifference;
        const viewMax = nextMax - minimumViewDifference;
        if (typeof this.step === 'number') {
            const numericStep = this.step;
            const precision = 100 / (numericStep * 100);
            const steppedValue = (Math.round(newMax * precision) / precision);
            const mantisseLength = numericStep
                .toString()
                .replace(/[0-9]+\./, '')
                .length;
            const fixedValue = +steppedValue.toFixed(mantisseLength);
            const modelMax = Math.min(fixedValue, nextMax - numericStep);
            const modelMin = Math.max(fixedValue, previousMax + numericStep);
            const min = viewMin < modelMin ? modelMin : viewMin;
            const max = viewMax > modelMax ? modelMax : viewMax;
            const bestValue = newMax > range.max ? max : min;
            return bestValue;
        }
        else if (typeof this.step === 'function') {
            const event = {};
            event.range = this.ranges[index];
            event.index = index;
            event.ranges = this.ranges;
            event.newMax = newMax;
            return this.step(event);
        }
        else if (this.step instanceof Array) {
            let idealValue = newMax;
            let bestDiff;
            this.step
                .filter(value => value <= viewMax && value >= viewMin)
                .forEach(value => {
                const diff = Math.abs(value - newMax);
                if (bestDiff === undefined || bestDiff > diff) {
                    idealValue = value;
                    bestDiff = diff;
                }
            });
            return idealValue;
        }
        else {
            throw new Error('Invalid step type.');
        }
    }
}
DejaRangeComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.3", ngImport: i0, type: DejaRangeComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i1.NgControl, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Component });
DejaRangeComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.1.3", type: DejaRangeComponent, selector: "deja-range", inputs: { step: "step", selected: "selected", disabled: "disabled", readOnly: "readOnly" }, outputs: { select: "select", errorFeedback: "errorFeedback" }, host: { listeners: { "window:resize": "onResize()" } }, queries: [{ propertyName: "rangeTemplate", first: true, predicate: ["rangeTemplate"], descendants: true }, { propertyName: "separatorTemplate", first: true, predicate: ["separatorTemplate"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<section id=\"container\">\n    <section *ngFor=\"let range of ranges; trackBy: trackBy; let index=index\" class=\"block\" [style.width.px]=\"range.$width\">\n        <span class=\"range\" (mousedown)=\"onSelect($event, index)\" [class.selected]=\"selected === index\">\n\t\t\t\t<span class=\"default-range\" *ngIf=\"!rangeTemplate\">{{range.min}} - {{range.max}}</span>\n        <ng-template [ngTemplateOutlet]=\"rangeTemplate\" [ngTemplateOutletContext]=\"{ $implicit : range, index: index, ranges: ranges }\"></ng-template>\n        </span>\n        <span class=\"separator\" *ngIf=\"ranges.length > index + 1\" (mousedown)=\"onMouseDown($event, index)\">\n\t\t\t\t<span class=\"default-separator\" *ngIf=\"!separatorTemplate\">\n\t\t\t\t\t<span class=\"default-separator-item\"></span>\n        </span>\n        <ng-template [ngTemplateOutlet]=\"separatorTemplate\" [ngTemplateOutletContext]=\"{ $implicit : range, index: index, ranges: ranges }\"></ng-template>\n        </span>\n    </section>\n</section>\n", styles: [":host{display:block}:host #container{height:100%;width:100%;display:flex;box-shadow:0 2px 1px -1px #0003,0 1px 1px #00000024,0 1px 3px #0000001f;height:32px}:host #container *{user-select:none}:host #container .block{display:inline-flex}:host #container .range{display:inline-flex;flex-grow:100;justify-content:center}:host #container .separator{display:inline-flex;flex-basis:auto;justify-content:center;z-index:5}:host #container:hover{box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f}:host #container .range{width:7px;cursor:pointer}:host #container .range .default-range{align-self:center;font-size:10pt;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}:host #container .separator{cursor:ew-resize}:host #container .separator .default-separator{width:7px;height:25.6px;margin-top:3.2px;display:flex;justify-content:center}:host #container .separator .default-separator .default-separator-item{height:100%;width:1px}:host #container .range.selected{animation-name:selected;animation-duration:175ms;animation-fill-mode:both;animation-timing-function:ease-in-out}:host #container .range:not(.selected){animation-name:not-selected;animation-duration:175ms;animation-fill-mode:both;animation-timing-function:ease-in-out}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.3", ngImport: i0, type: DejaRangeComponent, decorators: [{
            type: Component,
            args: [{ selector: 'deja-range', changeDetection: ChangeDetectionStrategy.OnPush, template: "<section id=\"container\">\n    <section *ngFor=\"let range of ranges; trackBy: trackBy; let index=index\" class=\"block\" [style.width.px]=\"range.$width\">\n        <span class=\"range\" (mousedown)=\"onSelect($event, index)\" [class.selected]=\"selected === index\">\n\t\t\t\t<span class=\"default-range\" *ngIf=\"!rangeTemplate\">{{range.min}} - {{range.max}}</span>\n        <ng-template [ngTemplateOutlet]=\"rangeTemplate\" [ngTemplateOutletContext]=\"{ $implicit : range, index: index, ranges: ranges }\"></ng-template>\n        </span>\n        <span class=\"separator\" *ngIf=\"ranges.length > index + 1\" (mousedown)=\"onMouseDown($event, index)\">\n\t\t\t\t<span class=\"default-separator\" *ngIf=\"!separatorTemplate\">\n\t\t\t\t\t<span class=\"default-separator-item\"></span>\n        </span>\n        <ng-template [ngTemplateOutlet]=\"separatorTemplate\" [ngTemplateOutletContext]=\"{ $implicit : range, index: index, ranges: ranges }\"></ng-template>\n        </span>\n    </section>\n</section>\n", styles: [":host{display:block}:host #container{height:100%;width:100%;display:flex;box-shadow:0 2px 1px -1px #0003,0 1px 1px #00000024,0 1px 3px #0000001f;height:32px}:host #container *{user-select:none}:host #container .block{display:inline-flex}:host #container .range{display:inline-flex;flex-grow:100;justify-content:center}:host #container .separator{display:inline-flex;flex-basis:auto;justify-content:center;z-index:5}:host #container:hover{box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f}:host #container .range{width:7px;cursor:pointer}:host #container .range .default-range{align-self:center;font-size:10pt;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}:host #container .separator{cursor:ew-resize}:host #container .separator .default-separator{width:7px;height:25.6px;margin-top:3.2px;display:flex;justify-content:center}:host #container .separator .default-separator .default-separator-item{height:100%;width:1px}:host #container .range.selected{animation-name:selected;animation-duration:175ms;animation-fill-mode:both;animation-timing-function:ease-in-out}:host #container .range:not(.selected){animation-name:not-selected;animation-duration:175ms;animation-fill-mode:both;animation-timing-function:ease-in-out}\n"] }]
        }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i1.NgControl, decorators: [{
                    type: Self
                }, {
                    type: Optional
                }] }]; }, propDecorators: { select: [{
                type: Output
            }], errorFeedback: [{
                type: Output
            }], step: [{
                type: Input
            }], selected: [{
                type: Input
            }], rangeTemplate: [{
                type: ContentChild,
                args: ['rangeTemplate']
            }], separatorTemplate: [{
                type: ContentChild,
                args: ['separatorTemplate']
            }], onResize: [{
                type: HostListener,
                args: ['window:resize', []]
            }], disabled: [{
                type: Input
            }], readOnly: [{
                type: Input
            }] } });

/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */
class DejaRangeModule {
}
DejaRangeModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.3", ngImport: i0, type: DejaRangeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
DejaRangeModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.1.3", ngImport: i0, type: DejaRangeModule, declarations: [DejaRangeComponent], imports: [CommonModule], exports: [DejaRangeComponent] });
DejaRangeModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.1.3", ngImport: i0, type: DejaRangeModule, imports: [CommonModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.3", ngImport: i0, type: DejaRangeModule, decorators: [{
            type: NgModule,
            args: [{
                    declarations: [DejaRangeComponent],
                    exports: [DejaRangeComponent],
                    imports: [
                        CommonModule
                    ]
                }]
        }] });

/**
 * Generated bundle index. Do not edit.
 */

export { DejaRangeComponent, DejaRangeModule, Range };
//# sourceMappingURL=deja-js-component-range.mjs.map
