import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, ElementRef, EventEmitter, forwardRef, HostBinding, Inject, Input, Output, QueryList, ViewChild, Renderer2, NgZone, } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { merge, Subject, BehaviorSubject, from } from 'rxjs';
import { startWith, switchMap, takeUntil, filter, map, finalize } from 'rxjs/operators';
import { Overlay, OverlayPositionBuilder } from '@angular/cdk/overlay';
import { FocusKeyManager, FocusMonitor } from '@angular/cdk/a11y';
import { ROptionComponent } from '../option/option.component';
import { R_SELECT_INJECTION_TOKEN } from './select-injection-tokens';
import { CdkPortal } from '@angular/cdk/portal';
import { RTrigger, RTriggerStrategyBuilderService } from '../../../cdk/overlay/overlay-trigger';
import { rSelectAnimations } from './select.animations';
export class RFocusKeyManager extends FocusKeyManager {
}
export class RFocusKeyManagerFactoryService {
    create(items) {
        return new RFocusKeyManager(items);
    }
}
export class RSelectLabelComponent {
}
RSelectLabelComponent.decorators = [
    { type: Component, args: [{
                selector: 'r-select-label',
                template: '<ng-content></ng-content>'
            },] }
];
export function rSelectFormFieldControlConfigFactory() {
    const config = {};
    config.supportsSuffix = false;
    return config;
}
export class RSelectComponent {
    /*
     * @docs-private
     **/
    // status$ = new BehaviorSubject<RComponentOrCustomStatus>(this.status);
    /*
     * @docs-private
     **/
    // size$ = new BehaviorSubject<RComponentSize>(this.size);
    /*
     * @docs-private
     **/
    // focused$ = new BehaviorSubject<boolean>(false);
    /*
     * @docs-private
     **/
    // disabled$ = new BehaviorSubject<boolean>(this.disabled);
    /*
     * @docs-private
     **/
    // fullWidth$ = new BehaviorSubject<boolean>(this.fullWidth);
    constructor(document, overlay, hostRef, positionBuilder, cd, triggerStrategyBuilder, focusKeyManagerFactoryService, focusMonitor, renderer, zone) {
        this.document = document;
        this.overlay = overlay;
        this.hostRef = hostRef;
        this.positionBuilder = positionBuilder;
        this.cd = cd;
        this.triggerStrategyBuilder = triggerStrategyBuilder;
        this.focusKeyManagerFactoryService = focusKeyManagerFactoryService;
        this.focusMonitor = focusMonitor;
        this.renderer = renderer;
        this.zone = zone;
        this.focused$ = new BehaviorSubject(false);
        /**
         * Select size, available sizes:
         * `tiny`, `small`, `medium` (default), `large`, `giant`
         */
        this.size = 'medium';
        /**
         * Select status (adds specific styles):
         * `basic`, `primary`, `info`, `success`, `warning`, `danger`, `control`
         */
        this.status = 'basic';
        /**
         * Select shapes: `rectangle` (default), `round`, `semi-round`
         */
        this.shape = 'rectangle';
        /**
         * Select appearances: `outline` (default), `filled`, `hero`
         */
        this.appearance = 'outline';
        this._fullWidth = false;
        /**
         * Renders select placeholder if nothing selected.
         */
        this.placeholder = '';
        this._compareWith = (v1, v2) => v1 === v2;
        this._multiple = false;
        /**
         * Determines options overlay offset (in pixels).
         **/
        this.optionsOverlayOffset = 8;
        /**
         * Determines options overlay scroll strategy.
         **/
        this.scrollStrategy = 'block';
        /**
         * Will be emitted when selected value changes.
         */
        this.selectedChange = new EventEmitter();
        /**
         * List of selected options.
         */
        this.selectionModel = [];
        /**
         * Current overlay position because of we have to toggle overlayPosition
         * in [ngClass] direction and this directive can use only string.
         */
        this.overlayPosition = '';
        this.alive = true;
        this.destroy$ = new Subject();
        /**
         * Function passed through control value accessor to propagate changes.
         */
        this.onChange = () => { };
        this.onTouched = () => { };
    }
    /**
     * Adds `outline` styles
     */
    get outline() {
        return this.appearance === 'outline';
    }
    set outline(value) {
        if (Boolean(value)) {
            this.appearance = 'outline';
        }
    }
    /**
     * Adds `filled` styles
     */
    get filled() {
        return this.appearance === 'filled';
    }
    set filled(value) {
        if (Boolean(value)) {
            this.appearance = 'filled';
        }
    }
    /**
     * Adds `hero` styles
     */
    get hero() {
        return this.appearance === 'hero';
    }
    set hero(value) {
        if (Boolean(value)) {
            this.appearance = 'hero';
        }
    }
    /**
     * Disables the select
     */
    get disabled() {
        return !!this._disabled;
    }
    set disabled(value) {
        this._disabled = Boolean(value);
    }
    /**
     * If set element will fill its container
     */
    get fullWidth() {
        return this._fullWidth;
    }
    set fullWidth(value) {
        this._fullWidth = Boolean(value);
    }
    /**
     * A function to compare option value with selected value.
     * By default, values are compared with strict equality (`===`).
     */
    get compareWith() {
        return this._compareWith;
    }
    set compareWith(fn) {
        if (typeof fn !== 'function') {
            return;
        }
        this._compareWith = fn;
        if (this.selectionModel.length && this.canSelectValue()) {
            this.setSelection(this.selected);
        }
    }
    /**
     * Accepts selected item or array of selected items.
     */
    set selected(value) {
        this.writeValue(value);
    }
    get selected() {
        return this.multiple
            ? this.selectionModel.map(o => o.value)
            : this.selectionModel[0].value;
    }
    /**
     * Gives capability just write `multiple` over the element.
     */
    get multiple() {
        return this._multiple;
    }
    set multiple(value) {
        this._multiple = Boolean(value);
    }
    /**
     * Determines is select opened.
     */
    get isOpen() {
        return this.ref && this.ref.hasAttached();
    }
    /**
     * Determines is select hidden.
     */
    get isHidden() {
        return !this.isOpen;
    }
    /**
     * Returns width of the select button.
     */
    get hostWidth() {
        return this.button.nativeElement.getBoundingClientRect().width;
    }
    get selectButtonClasses() {
        const classes = [];
        if (!this.selectionModel.length) {
            classes.push('placeholder');
        }
        if (!this.selectionModel.length && !this.placeholder) {
            classes.push('empty');
        }
        if (this.isOpen) {
            classes.push(this.overlayPosition);
        }
        return classes;
    }
    /**
     * Content rendered in the label.
     */
    get selectionView() {
        if (this.selectionModel.length > 1) {
            return this.selectionModel.map((option) => option.content).join(', ');
        }
        return this.selectionModel[0].content;
    }
    ngAfterContentInit() {
        this.options.changes
            .pipe(startWith(this.options), filter(() => this.queue != null && this.canSelectValue()), 
        // Call 'writeValue' when current change detection run is finished.
        // When writing is finished, change detection starts again, since
        // microtasks queue is empty.
        // Prevents ExpressionChangedAfterItHasBeenCheckedError.
        switchMap((options) => from(Promise.resolve(options))), takeUntil(this.destroy$))
            .subscribe(() => this.writeValue(this.queue));
    }
    ngAfterViewInit() {
        this.triggerStrategy = this.createTriggerStrategy();
        this.subscribeOnButtonFocus();
        this.subscribeOnTriggers();
        this.subscribeOnOptionClick();
        // TODO: #2254
        this.zone.runOutsideAngular(() => setTimeout(() => {
            this.renderer.addClass(this.hostRef.nativeElement, 'r-transition');
        }));
    }
    ngOnDestroy() {
        this.alive = false;
        this.destroy$.next();
        this.destroy$.complete();
        if (this.ref) {
            this.ref.dispose();
        }
        if (this.triggerStrategy) {
            this.triggerStrategy.destroy();
        }
    }
    show() {
        if (this.isHidden) {
            this.attachToOverlay();
            this.setActiveOption();
            this.cd.markForCheck();
        }
    }
    hide() {
        if (this.isOpen) {
            this.ref.detach();
            this.ref.dispose();
            this.ref = null;
            this.cd.markForCheck();
        }
    }
    registerOnChange(fn) {
        this.onChange = fn;
    }
    registerOnTouched(fn) {
        this.onTouched = fn;
    }
    setDisabledState(isDisabled) {
        this.disabled = isDisabled;
        this.cd.markForCheck();
    }
    writeValue(value) {
        if (!this.alive) {
            return;
        }
        if (this.canSelectValue()) {
            this.setSelection(value);
            if (this.selectionModel.length) {
                this.queue = null;
            }
        }
        else {
            this.queue = value;
        }
    }
    /**
     * Selects option or clear all selected options if value is null.
     */
    handleOptionClick(option) {
        this.queue = null;
        if (option.value == null) {
            this.reset();
        }
        else {
            this.selectOption(option);
        }
        this.cd.markForCheck();
    }
    /**
     * Deselect all selected options.
     */
    reset() {
        this.selectionModel.forEach((option) => option.deselect());
        this.selectionModel = [];
        this.hide();
        this.button.nativeElement.focus();
        this.emitSelected(this.multiple ? [] : null);
    }
    /**
     * Determines how to select option as multiple or single.
     */
    selectOption(option) {
        if (this.multiple) {
            this.handleMultipleSelect(option);
        }
        else {
            this.handleSingleSelect(option);
        }
    }
    /**
     * Select single option.
     */
    handleSingleSelect(option) {
        const selected = this.selectionModel.pop();
        if (selected && !this._compareWith(selected.value, option.value)) {
            selected.deselect();
        }
        this.selectionModel = [option];
        option.select();
        this.hide();
        this.button.nativeElement.focus();
        this.emitSelected(option.value);
    }
    /**
     * Select for multiple options.
     */
    handleMultipleSelect(option) {
        if (option.selected) {
            this.selectionModel = this.selectionModel.filter(s => !this._compareWith(s.value, option.value));
            option.deselect();
        }
        else {
            this.selectionModel.push(option);
            option.select();
        }
        this.emitSelected(this.selectionModel.map((opt) => opt.value));
    }
    attachToOverlay() {
        if (!this.ref) {
            this.createOverlay();
            this.createKeyManager();
            this.subscribeOnOverlayKeys();
        }
        this.ref.attach(this.portal);
    }
    setActiveOption() {
        if (this.selectionModel.length) {
            this.keyManager.setActiveItem(this.selectionModel[0]);
        }
        else {
            this.keyManager.setFirstItemActive();
        }
    }
    createOverlay() {
        const scrollStrategy = this.createScrollStrategy();
        this.positionStrategy = this.createPositionStrategy();
        this.ref = this.overlay.create({
            positionStrategy: this.positionStrategy,
            scrollStrategy,
            panelClass: this.optionsPanelClass,
        });
    }
    createKeyManager() {
        new FocusKeyManager(this.options);
        this.keyManager = this.focusKeyManagerFactoryService.create(this.options).withTypeAhead(200);
    }
    createPositionStrategy() {
        return this.overlay.position()
            .flexibleConnectedTo(this.hostRef.nativeElement)
            .withPush(false)
            .withPositions([{
                originX: 'start',
                originY: 'bottom',
                overlayX: 'start',
                overlayY: 'top'
            }, {
                originX: 'start',
                originY: 'top',
                overlayX: 'start',
                overlayY: 'bottom'
            }]);
    }
    createScrollStrategy() {
        return this.overlay.scrollStrategies[this.scrollStrategy]();
    }
    createTriggerStrategy() {
        return this.triggerStrategyBuilder
            .trigger(RTrigger.CLICK)
            .host(this.hostRef.nativeElement)
            .container(() => this.getContainer())
            .build();
    }
    subscribeOnTriggers() {
        this.triggerStrategy.show$.subscribe(() => this.show());
        this.triggerStrategy.hide$
            .pipe(filter(() => this.isOpen))
            .subscribe(($event) => {
            this.hide();
            if (!this.isClickedWithinComponent($event)) {
                this.onTouched();
            }
        });
    }
    subscribeOnOptionClick() {
        /**
         * If the user changes provided options list in the runtime we have to handle this
         * and resubscribe on options selection changes event.
         * Otherwise, the user will not be able to select new options.
         */
        this.options.changes
            .pipe(startWith(this.options), switchMap((options) => {
            return merge(...options.map(option => option.click));
        }), takeUntil(this.destroy$))
            .subscribe((clickedOption) => this.handleOptionClick(clickedOption));
    }
    subscribeOnOverlayKeys() {
        this.ref.keydownEvents()
            .pipe(filter(() => this.isOpen), takeUntil(this.destroy$))
            .subscribe((event) => {
            if (event.key === 'escape') {
                this.button.nativeElement.focus();
                this.hide();
            }
            else {
                this.keyManager.onKeydown(event);
            }
        });
        this.keyManager.tabOut
            .pipe(takeUntil(this.destroy$))
            .subscribe(() => {
            this.hide();
            this.onTouched();
        });
    }
    subscribeOnButtonFocus() {
        this.focusMonitor.monitor(this.button)
            .pipe(map(origin => !!origin), finalize(() => this.focusMonitor.stopMonitoring(this.button)), takeUntil(this.destroy$))
            .subscribe(this.focused$);
    }
    getContainer() {
        return this.ref && this.ref.hasAttached() && {
            location: {
                nativeElement: this.ref.overlayElement,
            },
        };
    }
    /**
     * Propagate selected value.
     */
    emitSelected(selected) {
        this.onChange(selected);
        this.selectedChange.emit(selected);
    }
    /**
     * Set selected value in model.
     */
    setSelection(value) {
        const isArray = Array.isArray(value);
        if (this.multiple && !isArray) {
            throw new Error('Can\'t assign single value if select is marked as multiple');
        }
        if (!this.multiple && isArray) {
            throw new Error('Can\'t assign array if select is not marked as multiple');
        }
        const previouslySelectedOptions = this.selectionModel;
        this.selectionModel = [];
        if (isArray) {
            value.forEach(option => this.selectValue(option));
        }
        else {
            this.selectValue(value);
        }
        // find options which were selected before and trigger deselect
        previouslySelectedOptions
            .filter((option) => !this.selectionModel.includes(option))
            .forEach((option) => option.deselect());
        this.cd.markForCheck();
    }
    /**
     * Selects value.
     */
    selectValue(value) {
        const corresponding = this.options.find((option) => this._compareWith(option.value, value));
        if (corresponding) {
            corresponding.select();
            this.selectionModel.push(corresponding);
        }
    }
    /**
     * Sets touched if focus moved outside of button and overlay,
     * ignoring the case when focus moved to options overlay.
     */
    trySetTouched() {
        if (this.isHidden) {
            this.onTouched();
        }
    }
    isClickedWithinComponent($event) {
        return this.hostRef.nativeElement === $event.target || this.hostRef.nativeElement.contains($event.target);
    }
    canSelectValue() {
        return !!(this.options && this.options.length);
    }
    get tiny() {
        return this.size === 'tiny';
    }
    get small() {
        return this.size === 'small';
    }
    get medium() {
        return this.size === 'medium';
    }
    get large() {
        return this.size === 'large';
    }
    get giant() {
        return this.size === 'giant';
    }
    get primary() {
        return this.status === 'primary';
    }
    get info() {
        return this.status === 'info';
    }
    get success() {
        return this.status === 'success';
    }
    get warning() {
        return this.status === 'warning';
    }
    get danger() {
        return this.status === 'danger';
    }
    get basic() {
        return this.status === 'basic';
    }
    get control() {
        return this.status === 'control';
    }
    get rectangle() {
        return this.shape === 'rectangle';
    }
    get round() {
        return this.shape === 'round';
    }
    get semiRound() {
        return this.shape === 'semi-round';
    }
}
RSelectComponent.decorators = [
    { type: Component, args: [{
                selector: 'r-select',
                template: "<button [disabled]=\"disabled\" (blur)=\"trySetTouched()\" (keydown.arrowDown)=\"show()\" (keydown.arrowUp)=\"show()\"\r\n  class=\"select-button\" type=\"button\" #selectButton>\r\n\r\n  <ng-container *ngIf=\"selectionModel.length; else placeholderTemplate\">\r\n    <ng-container *ngIf=\"customLabel; else defaultSelectionTemplate\">\r\n      <ng-content select=\"r-select-label\"></ng-content>\r\n    </ng-container>\r\n\r\n    <ng-template #defaultSelectionTemplate>{{ selectionView }}</ng-template>\r\n  </ng-container>\r\n\r\n  <ng-template #placeholderTemplate>{{ placeholder }}</ng-template>\r\n\r\n  <r-icon icon=\"chevron-down-outline\" pack=\"r-components-essentials\" (click)=\"disabled && $event.stopPropagation()\"\r\n    aria-hidden=\"true\">\r\n  </r-icon>\r\n</button>\r\n\r\n<r-option-list [@transformPanel]=\"isOpen ? (multiple ? 'showing-multiple' : 'showing') : 'void'\" *cdkPortal\r\n  [size]=\"size\" [style.width.px]=\"hostWidth\" [ngClass]=\"optionsListClass\">\r\n  <ng-content select=\"r-option, r-option-group\"></ng-content>\r\n</r-option-list>",
                changeDetection: ChangeDetectionStrategy.OnPush,
                animations: [rSelectAnimations.transformPanel, rSelectAnimations.transformPanelWrap],
                providers: [
                    {
                        provide: NG_VALUE_ACCESSOR,
                        useExisting: forwardRef(() => RSelectComponent),
                        multi: true,
                    },
                    { provide: R_SELECT_INJECTION_TOKEN, useExisting: RSelectComponent },
                    RFocusKeyManagerFactoryService
                ],
                styles: [":host{display:inline-block}:host r-icon{transition-duration:.15s;transition-property:transform;transition-timing-function:ease-in}:host.open r-icon{transform:rotate(180deg)}:host .select-button{display:flex;align-items:center;justify-content:space-between;min-width:205px}"]
            },] }
];
RSelectComponent.ctorParameters = () => [
    { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
    { type: Overlay },
    { type: ElementRef },
    { type: OverlayPositionBuilder },
    { type: ChangeDetectorRef },
    { type: RTriggerStrategyBuilderService },
    { type: RFocusKeyManagerFactoryService },
    { type: FocusMonitor },
    { type: Renderer2 },
    { type: NgZone }
];
RSelectComponent.propDecorators = {
    size: [{ type: Input }],
    status: [{ type: Input }],
    shape: [{ type: Input }],
    appearance: [{ type: Input }],
    optionsListClass: [{ type: Input }],
    optionsPanelClass: [{ type: Input }],
    outline: [{ type: Input }, { type: HostBinding, args: ['class.appearance-outline',] }],
    filled: [{ type: Input }, { type: HostBinding, args: ['class.appearance-filled',] }],
    hero: [{ type: Input }, { type: HostBinding, args: ['class.appearance-hero',] }],
    disabled: [{ type: Input }],
    fullWidth: [{ type: Input }, { type: HostBinding, args: ['class.full-width',] }],
    placeholder: [{ type: Input }],
    compareWith: [{ type: Input }],
    selected: [{ type: Input }],
    multiple: [{ type: Input }],
    optionsOverlayOffset: [{ type: Input }],
    scrollStrategy: [{ type: Input }],
    selectedChange: [{ type: Output }],
    options: [{ type: ContentChildren, args: [ROptionComponent, { descendants: true },] }],
    customLabel: [{ type: ContentChild, args: [RSelectLabelComponent,] }],
    portal: [{ type: ViewChild, args: [CdkPortal,] }],
    button: [{ type: ViewChild, args: ['selectButton', { read: ElementRef },] }],
    isOpen: [{ type: HostBinding, args: ['class.open',] }],
    tiny: [{ type: HostBinding, args: ['class.size-tiny',] }],
    small: [{ type: HostBinding, args: ['class.size-small',] }],
    medium: [{ type: HostBinding, args: ['class.size-medium',] }],
    large: [{ type: HostBinding, args: ['class.size-large',] }],
    giant: [{ type: HostBinding, args: ['class.size-giant',] }],
    primary: [{ type: HostBinding, args: ['class.status-primary',] }],
    info: [{ type: HostBinding, args: ['class.status-info',] }],
    success: [{ type: HostBinding, args: ['class.status-success',] }],
    warning: [{ type: HostBinding, args: ['class.status-warning',] }],
    danger: [{ type: HostBinding, args: ['class.status-danger',] }],
    basic: [{ type: HostBinding, args: ['class.status-basic',] }],
    control: [{ type: HostBinding, args: ['class.status-control',] }],
    rectangle: [{ type: HostBinding, args: ['class.shape-rectangle',] }],
    round: [{ type: HostBinding, args: ['class.shape-round',] }],
    semiRound: [{ type: HostBinding, args: ['class.shape-semi-round',] }]
};
//# sourceMappingURL=select.component.js.map