import * as i2 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { Component, ChangeDetectionStrategy, Self, Optional, Input, ContentChild, ViewChild, NgModule } from '@angular/core';
import * as i1 from '@angular/forms';
import { FormsModule } from '@angular/forms';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { Destroy } from '@deja-js/component/core';
import { Circle, Position } from '@deja-js/component/core/graphics';
import { fromEvent, filter, debounceTime, switchMap, Subject, mergeWith, take, tap, takeUntil, sampleTime } from 'rxjs';

/*
 *  @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
 */
var ClockwiseFactorEnum;
(function (ClockwiseFactorEnum) {
    // eslint-disable-next-line @typescript-eslint/naming-convention
    ClockwiseFactorEnum[ClockwiseFactorEnum["clockwise"] = -1] = "clockwise";
    // eslint-disable-next-line @typescript-eslint/naming-convention
    ClockwiseFactorEnum[ClockwiseFactorEnum["counterClockwise"] = 1] = "counterClockwise";
})(ClockwiseFactorEnum || (ClockwiseFactorEnum = {}));
/**
 * Circular-picker component for Angular
 */
class DejaCircularPickerComponent extends Destroy {
    /**
     * Constructor.
     * Create MouseDown & mouseMove Observables needed inside this control.
     */
    constructor(elementRef, changeDetectorRef, control) {
        super();
        this.changeDetectorRef = changeDetectorRef;
        this.control = control;
        /** ClockwiseFactor allows user to choose rotation direction of picker */
        this.clockwiseFactor = ClockwiseFactorEnum.clockwise;
        /** Diameter of circular picker in pixels. Default 310px */
        this.fullDiameter = 310;
        /** Diameter of labels on circular picker */
        this.labelsDiameter = 43;
        this._outerLabels = false;
        this._disabled = false;
        this.twoPi = Math.PI * 2;
        this._radius = 0;
        this.configs = [];
        this._circularValues = [];
        // ************* End of ControlValueAccessor Implementation **************
        this.onChangeCallback = (_a) => undefined;
        this.onTouchedCallback = () => undefined;
        const element = elementRef.nativeElement;
        if (this.control) {
            this.control.valueAccessor = this;
        }
        const mouseUpEvent$ = fromEvent(element.ownerDocument, 'mouseup');
        const mouseDownEvent$ = fromEvent(element, 'mousedown');
        mouseDownEvent$.pipe(filter(event => !this.disabled && event.buttons === 1), debounceTime(100), filter(mouseEvent => {
            this.clickedTime = Date.now();
            const cursorElement = this.getHtmlElement(mouseEvent.target, 'cursor');
            const valueElement = this.getHtmlElement(mouseEvent.target, 'value');
            if (cursorElement) {
                this.cursorElement = cursorElement;
            }
            else if (valueElement) {
                this.value = +valueElement.getAttribute('value');
            }
            return !!cursorElement || !!valueElement;
        }), switchMap(() => {
            const moveUp$ = new Subject();
            // eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
            if (!element.ownerDocument.body.className.match(/noselect/)) {
                element.ownerDocument.body.classList.add('noselect');
            }
            const cancelMouse$ = moveUp$.pipe(mergeWith(mouseUpEvent$), take(1), tap(() => {
                delete this.cursorElement;
                delete this.clickedTime;
                element.ownerDocument.body.className = element.ownerDocument.body.className.replace(/\bnoselect\b/, '');
            }));
            const pickerElem = this.picker.nativeElement;
            const clientRect = pickerElem.getBoundingClientRect();
            const mouseMoveEvent$ = fromEvent(element.ownerDocument, 'mousemove');
            return mouseMoveEvent$.pipe(
            // eslint-disable-next-line rxjs/no-unsafe-takeuntil
            takeUntil(cancelMouse$), sampleTime(10), tap(moveEvent => {
                if (moveEvent.buttons !== 1) {
                    moveUp$.next();
                    return;
                }
                let circle = Circle.fromOuterRect(clientRect);
                let contains = false;
                if (this.outerLabels) {
                    circle = circle.inflate(this.labelsDiameter);
                    // eslint-disable-next-line no-loops/no-loops
                    for (const conf of this.configs) {
                        contains = circle.containsPoint(new Position(moveEvent.pageX, moveEvent.pageY));
                        if (contains) {
                            this.selectedConfig = conf;
                            break;
                        }
                        else {
                            circle = circle.inflate(this.labelsDiameter);
                        }
                    }
                }
                else {
                    const x = this.labelsDiameter * (this.configs.length - 1);
                    circle = circle.inflate(-x);
                    // eslint-disable-next-line no-loops/no-loops
                    for (let i = this.configs.length; i > 0; i--) {
                        contains = circle.containsPoint(new Position(moveEvent.pageX, moveEvent.pageY));
                        if (contains) {
                            this.selectedConfig = this.configs[i - 1];
                            break;
                        }
                        else {
                            circle = circle.inflate(this.labelsDiameter);
                        }
                    }
                }
                const newValue = this.pointToValue(moveEvent.pageX - clientRect.left, moveEvent.pageY - clientRect.top, this.selectedConfig);
                if (newValue !== this.value) {
                    this.value = newValue;
                }
            }));
        }), takeUntil(this.destroyed$)).subscribe();
    }
    /**
     * Allows user to choose labels position. If outerLabels is true, labels will come outside the circular picker
     *
     * With outerLabels = true, and ranges.length > 1, labels will go outwards.
     * With outerLabels = false, and ranges.length > 1, labels will go inwards.
     */
    set outerLabels(value) {
        this._outerLabels = coerceBooleanProperty(value) || null;
    }
    get outerLabels() {
        return this._outerLabels;
    }
    /** disabled property setter. Can be string or empty so you can use it like : <circular-picker disabled></circular-picker> */
    set disabled(value) {
        this._disabled = coerceBooleanProperty(value);
        this.changeDetectorRef.markForCheck();
    }
    /** To get disabled attribute. */
    get disabled() {
        return this._disabled;
    }
    get cursorHand() {
        return this._cursorHand;
    }
    get cursor() {
        return this._cursor;
    }
    get radius() {
        return this._radius;
    }
    get circularValues() {
        return this._circularValues;
    }
    /**
     * Init circular-picker configuration
     */
    ngOnInit() {
        /* max - width */
        const diameter = this.fullDiameter - this.labelsDiameter; // Material standard button size
        this._radius = diameter / 2;
        this.ranges.forEach(range => {
            range.interval = (range.interval) ? range.interval : 1;
            range.labelInterval = (range.labelInterval) ? range.labelInterval : 1;
            range.beginOffset = (range.beginOffset) ? range.beginOffset : Math.PI / 2;
            this.configs.push({
                range: range,
                stepAngle: this.twoPi / Math.floor((range.max - range.min + 1) / range.interval),
                steps: Math.floor((range.max - range.min + 1) / range.interval)
            });
        });
        this.selectedConfig = this.configs[0];
        this.bind();
        this.updateCursor();
    }
    // ************* ControlValueAccessor Implementation **************
    /** set accessor including call the onchange callback */
    set value(v) {
        if (v !== this._value) {
            this.writeValue(v);
            this.onChangeCallback(v);
        }
    }
    /** get accessor */
    get value() {
        return this._value;
    }
    /** From ControlValueAccessor interface */
    writeValue(value) {
        if (value !== this._value) {
            this._value = value;
            this.updateCursor();
        }
    }
    /** From ControlValueAccessor interface */
    registerOnChange(fn) {
        this.onChangeCallback = fn;
    }
    /** From ControlValueAccessor interface */
    registerOnTouched(fn) {
        this.onTouchedCallback = fn;
    }
    setDisabledState(isDisabled) {
        this.disabled = isDisabled;
    }
    /**
     * Take a point in parameter and return corresponding value
     *
     * @param x xPos of point
     * @param y yPos of point
     * @param config config where the point is located
     *
     * @return value
     */
    pointToValue(x, y, config) {
        const angleAtPoint = this.pointToAngle(x - this._radius, y - this._radius, config);
        let circleSegmentIndexAtPoint = config.steps - Math.ceil(angleAtPoint / config.stepAngle);
        // By having pointToAngle() to compute using a half step below the actual angle,
        // we can use Math.ceil() to get the upper circleSegmentIndex. We're working in
        // counter-clockwise direction, thus we finally get a -1 index when we're just
        // below the first circle segment. We can now simply wrap it to the 0 index,
        // resulting in the user-expected behavior.
        if (circleSegmentIndexAtPoint < 0) {
            circleSegmentIndexAtPoint = config.steps;
        }
        return config.range.min + circleSegmentIndexAtPoint * config.range.interval;
    }
    /**
     * Take velue and returns its position
     * @param value value to check
     * @param radiusOffset Radius to ckeck (if outerLabels are on, the radius offset = fullDiameter + labelsDiameter)
     * @param config the config where to check
     *
     * @return the position of value
     */
    valueToPoint(value, radiusOffset, config) {
        const position = new Position();
        const valueAngle = this.valueToAngle(value, config);
        position.left = this._radius + (this._radius + radiusOffset) * Math.cos(valueAngle);
        position.top = this._radius - (this._radius + radiusOffset) * Math.sin(valueAngle); // y axis is reversed in display
        return position;
    }
    pointToAngle(x, y, config) {
        return (-Math.atan2(y, x) // Math.atan2() returns between -Ï€ and +Ï€, but in inverted trigonometrical order...
            - config.range.beginOffset // Correct the configured offset to compute in 'natural' trigonometrical circle
            - (config.stepAngle / 2) // Remove half a step angle to match value from both sides
            + this.twoPi // We want the returned value to be between 0 and 2Ï€ => (x + 2Ï€) % 2Ï€
        ) % this.twoPi;
    }
    valueToAngle(value, config) {
        const circleSegmentIndex = Math.floor((value - config.range.min) / config.range.interval);
        return (circleSegmentIndex * config.stepAngle * this.clockwiseFactor) + config.range.beginOffset;
    }
    bind() {
        this._circularValues = [];
        this.configs.forEach((config, configNumber) => {
            // eslint-disable-next-line no-loops/no-loops
            for (let i = config.range.min; i <= config.range.max; i += (config.range.labelInterval * config.range.interval)) {
                const val = { value: i };
                const labelRadius = this.labelsDiameter / 2;
                const configOffset = this.labelsDiameter * configNumber;
                const labelPosition = this.valueToPoint(i, (this.outerLabels ? labelRadius + configOffset : -labelRadius - configOffset), config);
                val.position = new Position((labelPosition.left - labelRadius), (labelPosition.top - labelRadius));
                this._circularValues.push(val);
            }
        });
    }
    updateCursor() {
        if (!this._circularValues || !this._circularValues.length) {
            return;
        }
        if (this._value === undefined || this._value === null) {
            this._value = this._circularValues[0].value;
        }
        this.selectedConfig = this.configs.find((conf) => this._value >= conf.range.min && this._value <= conf.range.max);
        if (!this.selectedConfig) {
            this.selectedConfig = this.configs[0];
        }
        const selectedConfigIndex = this.configs.indexOf(this.selectedConfig);
        const cursorRadius = this.labelsDiameter / 2;
        const cursorCenter = this.valueToPoint(this._value, (this.outerLabels ? cursorRadius + (this.labelsDiameter * selectedConfigIndex) : -cursorRadius - (this.labelsDiameter * selectedConfigIndex)), this.selectedConfig);
        this._cursor = {
            position: new Position((cursorCenter.left - cursorRadius), (cursorCenter.top - cursorRadius)),
            value: this._value
        };
        this._cursorHand = {
            angle: this.valueToAngle(this._value, this.selectedConfig),
            width: (this.outerLabels) ? this._radius + (this.labelsDiameter * selectedConfigIndex) : this._radius - this.labelsDiameter - (this.labelsDiameter * selectedConfigIndex)
        };
        this.changeDetectorRef.markForCheck();
    }
    getHtmlElement(element, attr) {
        let parentElement = element;
        // eslint-disable-next-line no-loops/no-loops
        while (parentElement && !parentElement.hasAttribute(attr)) {
            parentElement = parentElement.parentElement;
        }
        if (!parentElement) {
            return undefined;
        }
        return parentElement;
    }
}
DejaCircularPickerComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: DejaCircularPickerComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }, { token: i1.NgControl, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Component });
DejaCircularPickerComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.1", type: DejaCircularPickerComponent, selector: "deja-circular-picker", inputs: { clockwiseFactor: "clockwiseFactor", fullDiameter: "fullDiameter", labelsDiameter: "labelsDiameter", ranges: "ranges", outerLabels: "outerLabels", disabled: "disabled" }, queries: [{ propertyName: "labelTemplate", first: true, predicate: ["labelTemplate"], descendants: true }, { propertyName: "cursorTemplate", first: true, predicate: ["cursorTemplate"], descendants: true }], viewQueries: [{ propertyName: "picker", first: true, predicate: ["picker"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "\t<div class=\"circular-picker\" #picker [style.width.px]=\"radius*2\" [style.height.px]=\"radius*2\" [class.disabled]=\"disabled\">\n\t    <!--FOR TEST ONLY-->\n\t    <!-- div *ngIf=\"circle\" class=\"test\"\n\t\t\t\t[style.position]=\"'fixed'\"\n\t\t\t\t[style.background]=\"'red'\"\n\t\t\t\t[style.opacity]=\"0.6\"\n\t\t\t\t[style.top.px]=\"circle.center.top - circle.radius\"\n\t\t\t\t[style.left.px]=\"circle.center.left - circle.radius\"\n\t\t\t\t[style.width.px]=\"circle.radius * 2\"\n\t\t\t\t[style.height.px]=\"circle.radius * 2\"\n        ></div-->\n\t    <div class=\"cursor-container\">\n\t        <div class=\"cursor\" cursor *ngIf=\"cursor.value !== undefined\" [style.top.px]=\"cursor.position.top\" [style.left.px]=\"cursor.position.left\">\n\t            <ng-template *ngIf=\"cursorTemplate else valueTemplate\" [ngTemplateOutlet]=\"cursorTemplate\" [ngTemplateOutletContext]=\"{ $implicit: cursor.value }\"></ng-template>\n\t            <ng-template #valueTemplate>\n\t                <span>{{cursor.value}}</span>\n\t            </ng-template>\n\t        </div>\n\t        <div class=\"cursorHand\" [ngStyle]=\"{'transform':'translateY(-50%) rotate('+(-cursorHand.angle)+'rad)'}\" [style.width.px]=\"cursorHand.width+2\"></div>\n\t    </div>\n\t    <div class=\"value\" [attr.value]=\"val.value\" *ngFor=\"let val of circularValues; let index=index\" [style.top.px]=\"val.position.top\" [style.left.px]=\"val.position.left\">\n\t        <ng-template *ngIf=\"labelTemplate\" [ngTemplateOutlet]=\"labelTemplate\" [ngTemplateOutletContext]=\"{ $implicit: val.value, index: index }\"></ng-template>\n\t        <span *ngIf=\"!labelTemplate\">{{val.value}}</span>\n\t    </div>\n\t</div>", styles: [":host{-webkit-touch-callout:none;user-select:none;position:relative}:host .circular-picker{border-radius:50%;position:relative}:host .circular-picker .value{border-radius:50%;cursor:pointer;height:43px;line-height:43px;position:absolute;text-align:center;width:43px}:host .circular-picker .cursor-container .cursor{border-radius:50%;cursor:pointer;height:43px;line-height:43px;position:absolute;text-align:center;width:43px;z-index:5}:host .circular-picker .cursor-container .cursorHand{border-radius:2px;height:4px;left:50%;position:absolute;top:50%;transform-origin:left center}\n"], directives: [{ type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }, { type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: DejaCircularPickerComponent, decorators: [{
            type: Component,
            args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'deja-circular-picker', template: "\t<div class=\"circular-picker\" #picker [style.width.px]=\"radius*2\" [style.height.px]=\"radius*2\" [class.disabled]=\"disabled\">\n\t    <!--FOR TEST ONLY-->\n\t    <!-- div *ngIf=\"circle\" class=\"test\"\n\t\t\t\t[style.position]=\"'fixed'\"\n\t\t\t\t[style.background]=\"'red'\"\n\t\t\t\t[style.opacity]=\"0.6\"\n\t\t\t\t[style.top.px]=\"circle.center.top - circle.radius\"\n\t\t\t\t[style.left.px]=\"circle.center.left - circle.radius\"\n\t\t\t\t[style.width.px]=\"circle.radius * 2\"\n\t\t\t\t[style.height.px]=\"circle.radius * 2\"\n        ></div-->\n\t    <div class=\"cursor-container\">\n\t        <div class=\"cursor\" cursor *ngIf=\"cursor.value !== undefined\" [style.top.px]=\"cursor.position.top\" [style.left.px]=\"cursor.position.left\">\n\t            <ng-template *ngIf=\"cursorTemplate else valueTemplate\" [ngTemplateOutlet]=\"cursorTemplate\" [ngTemplateOutletContext]=\"{ $implicit: cursor.value }\"></ng-template>\n\t            <ng-template #valueTemplate>\n\t                <span>{{cursor.value}}</span>\n\t            </ng-template>\n\t        </div>\n\t        <div class=\"cursorHand\" [ngStyle]=\"{'transform':'translateY(-50%) rotate('+(-cursorHand.angle)+'rad)'}\" [style.width.px]=\"cursorHand.width+2\"></div>\n\t    </div>\n\t    <div class=\"value\" [attr.value]=\"val.value\" *ngFor=\"let val of circularValues; let index=index\" [style.top.px]=\"val.position.top\" [style.left.px]=\"val.position.left\">\n\t        <ng-template *ngIf=\"labelTemplate\" [ngTemplateOutlet]=\"labelTemplate\" [ngTemplateOutletContext]=\"{ $implicit: val.value, index: index }\"></ng-template>\n\t        <span *ngIf=\"!labelTemplate\">{{val.value}}</span>\n\t    </div>\n\t</div>", styles: [":host{-webkit-touch-callout:none;user-select:none;position:relative}:host .circular-picker{border-radius:50%;position:relative}:host .circular-picker .value{border-radius:50%;cursor:pointer;height:43px;line-height:43px;position:absolute;text-align:center;width:43px}:host .circular-picker .cursor-container .cursor{border-radius:50%;cursor:pointer;height:43px;line-height:43px;position:absolute;text-align:center;width:43px;z-index:5}:host .circular-picker .cursor-container .cursorHand{border-radius:2px;height:4px;left:50%;position:absolute;top:50%;transform-origin:left center}\n"] }]
        }], ctorParameters: function () {
        return [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }, { type: i1.NgControl, decorators: [{
                        type: Self
                    }, {
                        type: Optional
                    }] }];
    }, propDecorators: { clockwiseFactor: [{
                type: Input
            }], fullDiameter: [{
                type: Input
            }], labelsDiameter: [{
                type: Input
            }], ranges: [{
                type: Input
            }], labelTemplate: [{
                type: ContentChild,
                args: ['labelTemplate']
            }], cursorTemplate: [{
                type: ContentChild,
                args: ['cursorTemplate']
            }], picker: [{
                type: ViewChild,
                args: ['picker', { static: true }]
            }], outerLabels: [{
                type: Input
            }], disabled: [{
                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 DejaCircularPickerModule {
}
DejaCircularPickerModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: DejaCircularPickerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
DejaCircularPickerModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: DejaCircularPickerModule, declarations: [DejaCircularPickerComponent], imports: [CommonModule,
        FormsModule], exports: [DejaCircularPickerComponent] });
DejaCircularPickerModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: DejaCircularPickerModule, imports: [[
            CommonModule,
            FormsModule
        ]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: DejaCircularPickerModule, decorators: [{
            type: NgModule,
            args: [{
                    declarations: [DejaCircularPickerComponent],
                    exports: [DejaCircularPickerComponent],
                    imports: [
                        CommonModule,
                        FormsModule
                    ]
                }]
        }] });

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

export { ClockwiseFactorEnum, DejaCircularPickerComponent, DejaCircularPickerModule };
//# sourceMappingURL=deja-js-component-circular-picker.mjs.map
