import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { EMPTY, fromEvent as observableFromEvent, merge as observableMerge, Subject } from 'rxjs';
import { debounceTime, delay, filter, map, repeat, share, switchMap, takeUntil, takeWhile } from 'rxjs/operators';
import * as i0 from "@angular/core";
import * as i1 from "@angular/common";
export var RTrigger;
(function (RTrigger) {
    RTrigger["NOOP"] = "noop";
    RTrigger["CLICK"] = "click";
    RTrigger["HOVER"] = "hover";
    RTrigger["HINT"] = "hint";
    RTrigger["FOCUS"] = "focus";
})(RTrigger || (RTrigger = {}));
/**
 * TODO maybe we have to use renderer.listen instead of observableFromEvent?
 * Renderer provides capability use it in service worker, ssr and so on.
 * */
export class RTriggerStrategyBase {
    constructor(document, host, container) {
        this.document = document;
        this.host = host;
        this.container = container;
        this.destroyed$ = new Subject();
    }
    destroy() {
        this.destroyed$.next();
    }
    isNotOnHostOrContainer(event) {
        return !this.isOnHost(event) && !this.isOnContainer(event);
    }
    isOnHostOrContainer(event) {
        return this.isOnHost(event) || this.isOnContainer(event);
    }
    isOnHost({ target }) {
        return this.host.contains(target);
    }
    isOnContainer({ target }) {
        return this.container() && this.container().location.nativeElement.contains(target);
    }
}
/**
 * Creates show and hide event streams.
 * Fires toggle event when the click was performed on the host element.
 * Fires close event when the click was performed on the document but
 * not on the host or container.
 * */
export class RClickTriggerStrategy extends RTriggerStrategyBase {
    constructor() {
        super(...arguments);
        // since we should track click for both SHOW and HIDE event we firstly need to track the click and the state
        // of the container and then later on decide should we hide it or show
        // if we track the click & state separately this will case a behavior when the container is getting shown
        // and then hidden right away
        this.click$ = observableFromEvent(this.document, 'click')
            .pipe(map((event) => [!this.container() && this.isOnHost(event), event]), share(), takeUntil(this.destroyed$));
        this.show$ = this.click$
            .pipe(filter(([shouldShow]) => shouldShow), map(([, event]) => event), takeUntil(this.destroyed$));
        this.hide$ = this.click$
            .pipe(filter(([shouldShow, event]) => !shouldShow && !this.isOnContainer(event)), map(([, event]) => event), takeUntil(this.destroyed$));
    }
}
/**
 * Creates show and hide event streams.
 * Fires open event when a mouse hovers over the host element and stay over at least 100 milliseconds.
 * Fires close event when the mouse leaves the host element and stops out of the host and popover container.
 * */
export class RHoverTriggerStrategy extends RTriggerStrategyBase {
    constructor() {
        super(...arguments);
        this.show$ = observableFromEvent(this.host, 'mouseenter')
            .pipe(filter(() => !this.container()), 
        // this `delay & takeUntil & repeat` operators combination is a synonym for `conditional debounce`
        // meaning that if one event occurs in some time after the initial one we won't react to it
        delay(100), 
        // tslint:disable-next-line:rxjs-no-unsafe-takeuntil
        takeUntil(observableFromEvent(this.host, 'mouseleave')), repeat(), takeUntil(this.destroyed$));
        this.hide$ = observableFromEvent(this.host, 'mouseleave')
            .pipe(switchMap(() => observableFromEvent(this.document, 'mousemove')
            .pipe(debounceTime(100), takeWhile(() => !!this.container()), filter(event => this.isNotOnHostOrContainer(event)))), takeUntil(this.destroyed$));
    }
}
/**
 * Creates show and hide event streams.
 * Fires open event when a mouse hovers over the host element and stay over at least 100 milliseconds.
 * Fires close event when the mouse leaves the host element.
 * */
export class RHintTriggerStrategy extends RTriggerStrategyBase {
    constructor() {
        super(...arguments);
        this.show$ = observableFromEvent(this.host, 'mouseenter')
            .pipe(
        // this `delay & takeUntil & repeat` operators combination is a synonym for `conditional debounce`
        // meaning that if one event occurs in some time after the initial one we won't react to it
        delay(100), 
        // tslint:disable-next-line:rxjs-no-unsafe-takeuntil
        takeUntil(observableFromEvent(this.host, 'mouseleave')), repeat(), takeUntil(this.destroyed$));
        this.hide$ = observableFromEvent(this.host, 'mouseleave')
            .pipe(takeUntil(this.destroyed$));
    }
}
/**
 * Creates show and hide event streams.
 * Fires open event when a focus is on the host element and stay over at least 100 milliseconds.
 * Fires close event when the focus leaves the host element.
 * */
export class RFocusTriggerStrategy extends RTriggerStrategyBase {
    constructor() {
        super(...arguments);
        this.focusOut$ = observableFromEvent(this.host, 'focusout')
            .pipe(switchMap(() => observableFromEvent(this.document, 'focusin')
            .pipe(takeWhile(() => !!this.container()), filter(event => this.isNotOnHostOrContainer(event)))), takeUntil(this.destroyed$));
        this.clickIn$ = observableFromEvent(this.host, 'click')
            .pipe(filter(() => !this.container()), takeUntil(this.destroyed$));
        this.clickOut$ = observableFromEvent(this.document, 'click')
            .pipe(filter(() => !!this.container()), filter(event => this.isNotOnHostOrContainer(event)), takeUntil(this.destroyed$));
        this.tabKeyPress$ = observableFromEvent(this.document, 'keydown')
            .pipe(filter((event) => event.keyCode === 9), filter(() => !!this.container()), takeUntil(this.destroyed$));
        this.show$ = observableMerge(observableFromEvent(this.host, 'focusin'), this.clickIn$)
            .pipe(filter(() => !this.container()), debounceTime(100), 
        // tslint:disable-next-line:rxjs-no-unsafe-takeuntil
        takeUntil(observableFromEvent(this.host, 'focusout')), repeat(), takeUntil(this.destroyed$));
        this.hide$ = observableMerge(this.focusOut$, this.tabKeyPress$, this.clickOut$)
            .pipe(takeUntil(this.destroyed$));
    }
}
/**
 * Creates empty show and hide event streams.
 * */
export class RNoopTriggerStrategy extends RTriggerStrategyBase {
    constructor() {
        super(...arguments);
        this.show$ = EMPTY;
        this.hide$ = EMPTY;
    }
}
export class RTriggerStrategyBuilderService {
    constructor(_document) {
        this._document = _document;
    }
    trigger(trigger) {
        this._trigger = trigger;
        return this;
    }
    host(host) {
        this._host = host;
        return this;
    }
    container(container) {
        this._container = container;
        return this;
    }
    build() {
        switch (this._trigger) {
            case RTrigger.CLICK:
                return new RClickTriggerStrategy(this._document, this._host, this._container);
            case RTrigger.HINT:
                return new RHintTriggerStrategy(this._document, this._host, this._container);
            case RTrigger.HOVER:
                return new RHoverTriggerStrategy(this._document, this._host, this._container);
            case RTrigger.FOCUS:
                return new RFocusTriggerStrategy(this._document, this._host, this._container);
            case RTrigger.NOOP:
                return new RNoopTriggerStrategy(this._document, this._host, this._container);
            default:
                throw new Error('Trigger have to be provided');
        }
    }
}
RTriggerStrategyBuilderService.ɵprov = i0.ɵɵdefineInjectable({ factory: function RTriggerStrategyBuilderService_Factory() { return new RTriggerStrategyBuilderService(i0.ɵɵinject(i1.DOCUMENT)); }, token: RTriggerStrategyBuilderService, providedIn: "root" });
RTriggerStrategyBuilderService.decorators = [
    { type: Injectable, args: [{ providedIn: 'root' },] }
];
RTriggerStrategyBuilderService.ctorParameters = () => [
    { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
];
//# sourceMappingURL=overlay-trigger.js.map