import { CommonModule } from '@angular/common';
import { InjectionToken, EventEmitter, isDevMode, NgZone, ElementRef, ChangeDetectorRef, Attribute, Optional, Self, Inject, Input, ViewChild, ContentChildren, Output, Component, ViewEncapsulation, ChangeDetectionStrategy, NgModule } from '@angular/core';
import { NtOptionComponent, NT_OPTION_PARENT_COMPONENT, fadeOut, fadeIn, NtOptionModule, NtPseudoInputModule } from '@ng-tangram/components/core';
import { BOTTOM_LEFT, TOP_LEFT, NtOverlayComponent, NtOverlayModule } from '@ng-tangram/components/overlay';
import { Subject, defer, merge } from 'rxjs';
import { take, switchMap, startWith, takeUntil, filter } from 'rxjs/operators';
import { trigger, transition } from '@angular/animations';
import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { SelectionModel } from '@angular/cdk/collections';
import { DOWN_ARROW, UP_ARROW, HOME, END, ENTER, SPACE, TAB, LEFT_ARROW, RIGHT_ARROW } from '@angular/cdk/keycodes';
import { CdkOverlayOrigin } from '@angular/cdk/overlay';
import { NgControl } from '@angular/forms';
import { NtFormFieldControl } from '@ng-tangram/components/forms';

const DEFAULT_SELECT_ICONS = {
    caret: 'fa fa-chevron-down',
    clear: 'fa fa-times'
};
const NT_SELECT_ICONS = new InjectionToken('nt-select-icons');

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 __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var NtSelectComponent_1;
function getNtSelectDynamicMultipleError() {
    return Error('Cannot change `multiple` mode of select after initialization.');
}
function getNtSelectNonArrayValueError() {
    return Error('Value must be an array in multiple-selection mode.');
}
function getNtSelectNonFunctionValueError() {
    return Error('`compareWith` must be a function.');
}
class NtSelectChange {
    constructor(source, value) {
        this.source = source;
        this.value = value;
    }
}
let NtSelectComponent = NtSelectComponent_1 = class NtSelectComponent extends NtFormFieldControl {
    constructor(_ngZone, _elementRef, _changeDetectorRef, tabIndex, ngControl, icons) {
        super();
        this._ngZone = _ngZone;
        this._elementRef = _elementRef;
        this._changeDetectorRef = _changeDetectorRef;
        this.ngControl = ngControl;
        this.icons = icons;
        this._destroy = new Subject();
        this._positionPairs = [BOTTOM_LEFT, TOP_LEFT];
        this._compareWith = (o1, o2) => o1 === o2;
        this._onChange = () => { };
        this._onTouched = () => { };
        this._focused = false;
        this._disabled = false;
        this._required = false;
        this._state = '';
        this._clearable = false;
        this._multiple = false;
        this.filterNotFound = 'Not Found';
        this._placeholder = '';
        this.afterOpen = new EventEmitter();
        this.afterClosed = new EventEmitter();
        this.beforeOpen = new EventEmitter();
        this.beforeClosed = new EventEmitter();
        this.positionChange = new EventEmitter();
        this.selectionChange = new EventEmitter();
        this.valueChange = new EventEmitter();
        this.optionSelectionChanges = defer(() => {
            if (this.options) {
                return merge(...this.options.map(option => option.selectionChange));
            }
            return this._ngZone.onStable
                .asObservable()
                .pipe(take(1), switchMap(() => this.optionSelectionChanges));
        });
        if (this.ngControl) {
            this.ngControl.valueAccessor = this;
        }
        this.origin = new CdkOverlayOrigin(this._elementRef);
        this.icons = Object.assign(Object.assign({}, DEFAULT_SELECT_ICONS), icons);
        this.tabIndex = parseInt(tabIndex) || 0;
    }
    get value() { return this._value; }
    get displayValue() {
        if (this.empty) {
            return '';
        }
        if (this._multiple) {
            const selectedOptions = this._selectionModel.selected.map(option => option.label);
            return selectedOptions.join(', ');
        }
        return this._selectionModel.selected[0].label;
    }
    get empty() { return !this._selectionModel || this._selectionModel.isEmpty(); }
    get optionEmpty() {
        return this.options ? this.options.filter(option => !option.hidden).length === 0 : false;
    }
    get focused() { return this._focused; }
    set disabled(value) { this._disabled = coerceBooleanProperty(value); }
    get disabled() { return this._disabled; }
    get required() { return this._required; }
    set required(value) { this._required = coerceBooleanProperty(value); }
    get state() { return this._state; }
    get width() { return this._width; }
    set clearable(value) { this._clearable = coerceBooleanProperty(value); }
    get clearable() { return this._clearable; }
    set multiple(value) { this._multiple = coerceBooleanProperty(value); }
    get multiple() { return this._multiple; }
    get filter() { return this._filter; }
    set filter(value) {
        if (typeof value === 'function') {
            this._filter = value;
        }
        else if (coerceBooleanProperty(value)) {
            this._filter = (keyword, option) => {
                const regex = new RegExp(keyword.toLowerCase(), 'g');
                return regex.test(option.label.toLowerCase());
            };
        }
    }
    get selected() {
        return this.multiple ? this._selectionModel.selected : this._selectionModel.selected[0];
    }
    get hasSelectedValue() {
        return this._selectionModel.selected.length > 0;
    }
    set placeholder(value) { this._placeholder = value; }
    get placeholder() {
        if (this.empty) {
            return this._placeholder;
        }
        return this.filter ? this.displayValue : this._placeholder;
    }
    set compareWith(fn) {
        if (typeof fn !== 'function') {
            throw getNtSelectNonFunctionValueError();
        }
        else {
            this._compareWith = fn;
            if (this._selectionModel) {
                this._initializeSelection();
            }
        }
    }
    ngOnInit() {
        this._selectionModel = new SelectionModel(this.multiple, undefined, false);
    }
    ngAfterContentInit() {
        this._initKeyManager();
        this.options.changes.pipe(startWith(null), takeUntil(this._destroy)).subscribe(() => {
            this._resetOptions();
            this._initializeSelection();
        });
    }
    ngOnDestroy() {
        this._destroy.next();
        this._destroy.complete();
    }
    onResize() {
        this._width = this._elementRef.nativeElement.clientWidth;
    }
    _onSearch(event) {
        if (this.overlay.opened && this.filter && !this.disabled) {
            const target = event.target;
            this.options.forEach(option => option.hidden = !this.filter(target.value, option));
        }
    }
    _beforeOpen(event) {
        this._state = 'folded';
        this.onResize();
        this._scrollActiveOptionIntoView();
        this._highlightCorrectOption();
        this.beforeOpen.next(event);
    }
    _beforeClosed(event) {
        this._state = 'closed';
        this._onTouched();
        this.beforeClosed.next(event);
    }
    _afterOpen(event) {
        this.afterOpen.next(event);
    }
    _afterClosed(event) {
        this._resetFilterResult();
        this.afterClosed.next(event);
    }
    _positionChange(change) {
        this.positionChange.next(change);
    }
    _onInputClick(event) {
        if (!this.disabled) {
            this.overlay.toggle();
        }
        event.stopPropagation();
    }
    _onInputFocus() {
        this._focused = true;
        this.overlay.markOpen();
    }
    _onInputBlur() {
        this._focused = false;
    }
    _onClear(event) {
        if (this.value !== null && !this.disabled) {
            this._clearSelection();
            this._value = null;
            this.valueChange.emit(null);
            this._onChange(null);
            this.selectionChange.emit(new NtSelectChange(this, null));
            this._changeDetectorRef.markForCheck();
        }
        event.stopPropagation();
    }
    focus() {
        if (!this.disabled) {
            if (typeof this.filter === 'function') {
                this.inputElement.nativeElement.focus();
            }
            else {
                this.overlay.open();
            }
        }
    }
    writeValue(value) {
        if (this.options) {
            this._setSelectionByValue(value);
        }
    }
    registerOnChange(fn) {
        this._onChange = fn;
    }
    registerOnTouched(fn) {
        this._onTouched = fn;
    }
    setDisabledState(isDisabled) {
        this._disabled = isDisabled;
    }
    _handleKeydown(event) {
        if (!this.disabled) {
            this.overlay.opened ? this._handleOpenKeydown(event) : this._handleClosedKeydown(event);
        }
    }
    _handleOpenKeydown(event) {
        const keyCode = event.keyCode;
        const isArrowKey = keyCode === DOWN_ARROW || keyCode === UP_ARROW;
        const manager = this._keyManager;
        if (keyCode === HOME || keyCode === END) {
            event.preventDefault();
            keyCode === HOME ? manager.setFirstItemActive() : manager.setLastItemActive();
        }
        else if (isArrowKey && event.altKey) {
            event.preventDefault();
            this.overlay.close();
        }
        else if ((keyCode === ENTER || keyCode === SPACE) && manager.activeItem) {
            event.preventDefault();
            manager.activeItem.selectViaInteraction();
            if (keyCode === ENTER && this.multiple) {
                this.overlay.close();
            }
        }
        else if (keyCode === TAB) {
            this.overlay.close();
        }
        else {
            const previouslyFocusedIndex = manager.activeItemIndex;
            manager.onKeydown(event);
            if (this._multiple && isArrowKey && event.shiftKey && manager.activeItem &&
                manager.activeItemIndex !== previouslyFocusedIndex) {
                manager.activeItem.selectViaInteraction();
            }
        }
    }
    _handleClosedKeydown(event) {
        const keyCode = event.keyCode;
        const isArrowKey = keyCode === DOWN_ARROW || keyCode === UP_ARROW ||
            keyCode === LEFT_ARROW || keyCode === RIGHT_ARROW;
        const isOpenKey = keyCode === ENTER || keyCode === SPACE;
        if (isOpenKey || ((this.multiple || event.altKey) && isArrowKey)) {
            event.preventDefault();
            this.overlay.open();
        }
        else if (!this.multiple) {
            this._keyManager.onKeydown(event);
        }
    }
    _resetOptions() {
        const changedOrDestroyed = merge(this.options.changes, this._destroy);
        this.optionSelectionChanges
            .pipe(takeUntil(changedOrDestroyed), filter(event => event.isUserInput))
            .subscribe(event => {
            this._onSelect(event.source);
            if (!this.multiple && this.overlay.opened) {
                this.overlay.close();
            }
        });
        merge(...this.options.map(option => option.stateChanges))
            .pipe(takeUntil(changedOrDestroyed))
            .subscribe(() => {
            this._changeDetectorRef.markForCheck();
        });
        this._resetFilterResult();
    }
    _initKeyManager() {
        this._keyManager = new ActiveDescendantKeyManager(this.options)
            .withTypeAhead()
            .withVerticalOrientation();
        this._keyManager.tabOut.pipe(takeUntil(this._destroy)).subscribe(() => {
            this.focus();
            this.overlay.close();
        });
        this._keyManager.change.pipe(takeUntil(this._destroy)).subscribe(() => {
            if (this.overlay.opened && this.paneElement) {
                this._scrollActiveOptionIntoView();
            }
            else if (!this.overlay.opened && !this.multiple && this._keyManager.activeItem) {
                this._keyManager.activeItem.selectViaInteraction();
            }
        });
    }
    _resetFilterResult() {
        this.options.forEach(option => option.hidden = false);
    }
    _initializeSelection() {
        Promise.resolve().then(() => {
            var _a;
            this._setSelectionByValue(((_a = this.ngControl) === null || _a === void 0 ? void 0 : _a.value) || this.value);
        });
    }
    _setSelectionByValue(value, isUserInput = false) {
        if (this.multiple && value) {
            if (!Array.isArray(value)) {
                throw getNtSelectNonArrayValueError();
            }
            this._clearSelection();
            value.forEach((currentValue) => this._selectValue(currentValue, isUserInput));
            this._sortValues();
        }
        else {
            this._clearSelection();
            const correspondingOption = this._selectValue(value, isUserInput);
            if (correspondingOption) {
                this._keyManager.setActiveItem(this.options.toArray().indexOf(correspondingOption));
            }
        }
        this._changeDetectorRef.markForCheck();
    }
    _selectValue(value, isUserInput = false) {
        const correspondingOption = this.options.find((option) => {
            try {
                return option.value !== null && this._compareWith(option.value, value);
            }
            catch (error) {
                if (isDevMode()) {
                    console.warn(error);
                }
                return false;
            }
        });
        if (correspondingOption) {
            isUserInput ? correspondingOption.selectViaInteraction() : correspondingOption.select();
            this._selectionModel.select(correspondingOption);
        }
        return correspondingOption;
    }
    _propagateChanges(fallbackValue) {
        let valueToEmit = null;
        if (this.multiple) {
            valueToEmit = this.selected.map(option => option.value);
        }
        else {
            valueToEmit = this.selected ? this.selected.value : fallbackValue;
        }
        this._value = valueToEmit;
        this.valueChange.emit(valueToEmit);
        this._onChange(valueToEmit);
        this.selectionChange.emit(new NtSelectChange(this, valueToEmit));
        this._changeDetectorRef.markForCheck();
    }
    _clearSelection(skip) {
        this._selectionModel.clear();
        this.options.forEach(option => {
            if (option !== skip) {
                option.deselect();
            }
        });
    }
    _sortValues() {
        if (this.multiple) {
            this._selectionModel.clear();
            this.options.forEach(option => {
                if (option.selected) {
                    this._selectionModel.select(option);
                }
            });
        }
    }
    _onSelect(option) {
        const wasSelected = this._selectionModel.isSelected(option);
        if (this.multiple) {
            this._selectionModel.toggle(option);
            wasSelected ? option.deselect() : option.select();
            this._keyManager.setActiveItem(this.options.toArray().indexOf(option));
            this._sortValues();
        }
        else {
            this._clearSelection(option.value == null ? undefined : option);
            if (option.value == null) {
                this._propagateChanges(option.value);
            }
            else {
                this._selectionModel.select(option);
            }
        }
        if (wasSelected !== this._selectionModel.isSelected(option)) {
            this._propagateChanges();
        }
    }
    _highlightCorrectOption() {
        if (this._keyManager) {
            if (this.empty) {
                this._keyManager.setFirstItemActive();
            }
            else {
                const selectIndex = this.options.toArray().indexOf(this._selectionModel.selected[0]);
                this._keyManager.setActiveItem(selectIndex);
            }
        }
    }
    _scrollActiveOptionIntoView() {
        const activeOption = this._keyManager.activeItem || this.options.first;
        if (!activeOption) {
            return;
        }
        const panelScrollTop = this.paneElement.nativeElement.scrollTop;
        const panelScrollBottom = panelScrollTop + this.paneElement.nativeElement.offsetHeight;
        if (panelScrollBottom === 0) {
            this.paneElement.nativeElement.scrollTop = activeOption.getOffsetTop();
        }
        else if (activeOption.getOffsetTop() < panelScrollTop) {
            this.paneElement.nativeElement.scrollTop = activeOption.getOffsetTop();
        }
        else if (activeOption.getOffsetTop() + activeOption.getOffsetHeight() >= panelScrollBottom) {
            const scrollOffset = this.paneElement.nativeElement.offsetHeight - activeOption.getOffsetHeight();
            this.paneElement.nativeElement.scrollTop = activeOption.getOffsetTop() - scrollOffset;
        }
    }
};
NtSelectComponent.ctorParameters = () => [
    { type: NgZone },
    { type: ElementRef },
    { type: ChangeDetectorRef },
    { type: String, decorators: [{ type: Attribute, args: ['tabindex',] }] },
    { type: NgControl, decorators: [{ type: Optional }, { type: Self }] },
    { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [NT_SELECT_ICONS,] }] }
];
__decorate([
    Input()
], NtSelectComponent.prototype, "disabled", null);
__decorate([
    Input()
], NtSelectComponent.prototype, "required", null);
__decorate([
    Input()
], NtSelectComponent.prototype, "clearable", null);
__decorate([
    Input()
], NtSelectComponent.prototype, "multiple", null);
__decorate([
    Input()
], NtSelectComponent.prototype, "filter", null);
__decorate([
    Input()
], NtSelectComponent.prototype, "filterNotFound", void 0);
__decorate([
    Input()
], NtSelectComponent.prototype, "placeholder", null);
__decorate([
    ViewChild('inputElement', { static: false })
], NtSelectComponent.prototype, "inputElement", void 0);
__decorate([
    ViewChild('paneElement', { static: true })
], NtSelectComponent.prototype, "paneElement", void 0);
__decorate([
    ViewChild(NtOverlayComponent, { static: true })
], NtSelectComponent.prototype, "overlay", void 0);
__decorate([
    ContentChildren(NtOptionComponent)
], NtSelectComponent.prototype, "options", void 0);
__decorate([
    Output()
], NtSelectComponent.prototype, "afterOpen", void 0);
__decorate([
    Output()
], NtSelectComponent.prototype, "afterClosed", void 0);
__decorate([
    Output()
], NtSelectComponent.prototype, "beforeOpen", void 0);
__decorate([
    Output()
], NtSelectComponent.prototype, "beforeClosed", void 0);
__decorate([
    Output()
], NtSelectComponent.prototype, "positionChange", void 0);
__decorate([
    Output()
], NtSelectComponent.prototype, "selectionChange", void 0);
__decorate([
    Output()
], NtSelectComponent.prototype, "valueChange", void 0);
__decorate([
    Input()
], NtSelectComponent.prototype, "compareWith", null);
NtSelectComponent = NtSelectComponent_1 = __decorate([
    Component({
        selector: 'nt-select',
        template: "<nt-pseudo-input\n  *ngIf=\"!filter\"\n  [style.margin.px]=\"0\"\n  [attr.tabindex]=\"tabIndex\"\n  [value]=\"displayValue\"\n  [placeholder]=\"placeholder\"\n  [disabled]=\"disabled\"\n  (click)=\"_onInputClick($event)\">\n  <nt-pseudo-caret *ngIf=\"!clearable else clearCaret\">\n    <span [class]=\"icons.caret\"></span>\n  </nt-pseudo-caret>\n</nt-pseudo-input>\n<div *ngIf=\"filter\" class=\"nt-pseudo-input only-caret\">\n  <input #inputElement type=\"text\"\n    [style.margin.px]=\"0\"\n    [placeholder]=\"placeholder\"\n    [disabled]=\"disabled\"\n    [value]=\"focused && overlay.opened ? '' : displayValue\"\n    (focus)=\"_onInputFocus()\"\n    (blur)=\"_onInputBlur()\"\n    (keyup)=\"_onSearch($event)\">\n  <nt-pseudo-caret *ngIf=\"!clearable else clearCaret\">\n    <span [class]=\"icons.caret\"></span>\n  </nt-pseudo-caret>\n</div>\n<nt-overlay\n  overlayClass=\"nt-select-overlay\"\n  [origin]=\"origin\"\n  [positionPairs]=\"_positionPairs\"\n  (afterOpen)=\"_afterOpen($event)\"\n  (afterClosed)=\"_afterClosed($event)\"\n  (beforeOpen)=\"_beforeOpen($event)\"\n  (beforeClosed)=\"_beforeClosed($event)\"\n  (positionChange)=\"_positionChange($event)\"\n  (keydownEvents)=\"_handleKeydown($event)\">\n  <div #paneElement\n    class=\"nt-select-pane\"\n    [class.folded]=\"state === 'folded'\"\n    [style.width.px]=\"width\">\n    <ng-content select=\"nt-option\"></ng-content>\n    <div class=\"filter-empty\" [hidden]=\"!optionEmpty\">\n      {{ filterNotFound }}\n    </div>\n  </div>\n</nt-overlay>\n\n<ng-template #clearCaret>\n  <nt-pseudo-caret *ngIf=\"!hasSelectedValue\">\n    <span [class]=\"icons.caret\"></span>\n  </nt-pseudo-caret>\n  <nt-pseudo-caret class=\"nt-select-clear\" (click)=\"_onClear($event)\" *ngIf=\"hasSelectedValue && !disabled\">\n    <span [class]=\"icons.clear\"></span>\n  </nt-pseudo-caret>\n</ng-template>\n",
        providers: [
            { provide: NT_OPTION_PARENT_COMPONENT, useExisting: NtSelectComponent_1 },
            { provide: NtFormFieldControl, useExisting: NtSelectComponent_1 }
        ],
        encapsulation: ViewEncapsulation.None,
        changeDetection: ChangeDetectionStrategy.OnPush,
        animations: [
            trigger('fade', [
                transition('* => void', fadeOut(.15)),
                transition('void => *', fadeIn(.15))
            ])
        ],
        host: {
            'class': 'nt-select',
            '(window:resize)': 'onResize()'
        }
    }),
    __param(3, Attribute('tabindex')),
    __param(4, Optional()), __param(4, Self()),
    __param(5, Optional()), __param(5, Inject(NT_SELECT_ICONS))
], NtSelectComponent);

var __decorate$1 = (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;
};
let NtSelectModule = class NtSelectModule {
};
NtSelectModule = __decorate$1([
    NgModule({
        imports: [
            CommonModule,
            NtOverlayModule,
            NtOptionModule,
            NtPseudoInputModule
        ],
        exports: [NtSelectComponent, NtOptionModule],
        declarations: [NtSelectComponent]
    })
], NtSelectModule);

export { DEFAULT_SELECT_ICONS, NT_SELECT_ICONS, NtSelectChange, NtSelectComponent, NtSelectModule, getNtSelectDynamicMultipleError, getNtSelectNonArrayValueError, getNtSelectNonFunctionValueError };
//# sourceMappingURL=select.js.map
