import { AfterContentInit, Directive, ElementRef, OnChanges, OnDestroy, SimpleChanges } from "@angular/core";
const baseTimerDelay = 10;

@Directive({
    selector: "[autofocus]",
    inputs: [
        "shouldFocusElement: autofocus",
        "timerDelay: autofocusDelay"
    ]
})
export class AutofocusDirective implements AfterContentInit, OnChanges, OnDestroy {
    shouldFocusElement: any;
    timerDelay: number;
    private readonly elementRef: ElementRef;
    private timer: any;

    // I initialize the autofocus directive.
    constructor(elementRef: ElementRef) {
        this.elementRef = elementRef;
        this.shouldFocusElement = "";
        this.timer = null;
        this.timerDelay = baseTimerDelay;
    }

    // ---
    // PUBLIC METHODS.
    // ---
    // I get called once after the contents have been fully initialized.
    ngAfterContentInit(): void {
        // Because this directive can act on the stand-only "autofocus" attribute or
        // the more specialized "autofocus" property, we need to check to see if the
        // "shouldFocusElement" input property is the empty string. This will signify
        // that the focus it not being data-driven and should be performed automatically.
        if (this.shouldFocusElement === "") {
            this.startFocusWorkflow();
        }
    }

    // I get called every time the input bindings are updated.
    ngOnChanges(changes: SimpleChanges): void {

        // If the timer delay is being passed-in as a string (ie., someone is using
        // attribute-input syntax, not property-input syntax), let's coalesce the
        // attribute to a numeric value so that our type-annotations are consistent.
        // ReSharper disable once TsResolvedFromInaccessibleModule
        if (changes.timerDelay && (typeof (this.timerDelay) !== "number")) {

            // If the coalesce fails, just fall-back to a sane value.
            if (isNaN(this.timerDelay = +this.timerDelay)) {
                this.timerDelay = baseTimerDelay;
            }

        }

        // If the focus input is being data-driven, then we either need to start the
        // focus work flow if focus is required; or, clear any existing work flow if focus
        // is no longer required (so that we don't steal focus from another element).
        // ReSharper disable once TsResolvedFromInaccessibleModule
        if (changes.shouldFocusElement) {
            (this.shouldFocusElement)
                ? this.startFocusWorkflow()
                : this.stopFocusWorkflow()
                ;
        }
    }

    // I get called once when the directive is being unmounted.
    ngOnDestroy(): void {
        this.stopFocusWorkflow();
    }

    // ---
    // PRIVATE METHODS.
    // ---
    // I start the timer-based work flow that will focus the current element.
    private startFocusWorkflow(): void {
        // If there is already a timer running for this element, just let it play out -
        // resetting it at this point will only push-out the time at which the focus is
        // applied to the element.
        if (this.timer) {
            return;
        }

        this.timer = setTimeout(
            (): void => {

                this.timer = null;
                // ReSharper disable once TsResolvedFromInaccessibleModule
                this.elementRef.nativeElement.focus();

            },
            this.timerDelay
        );
    }

    // I stop the timer-based work flow, preventing focus from taking place.
    private stopFocusWorkflow(): void {
        clearTimeout(this.timer);
        this.timer = null;
    }
}