import {SelectorCallback} from "./callbacks";
import {DeferredPromise} from "../DeferredPromise";
import {IDOMDeferredSelectorParameters} from "./parameters/IDOMDeferredSelectorParameters";
import {setImmediate} from "delay-me";

export class DOMDeferredSelector<T extends Element = Element>{

    protected readonly selector: SelectorCallback<T>;
    protected readonly parameters: IDOMDeferredSelectorParameters;
    private _deferred: DeferredPromise<T, never>;

    private _is_activated: boolean = false;
    public get isActivated(): boolean{ return this._is_activated; }

    private _is_cancelled: boolean = false;
    public get isCancelled(): boolean{ return this._is_cancelled; }

    protected _repeats_left: number = null;
    protected _interval_ref: number = null;

    /**
     * @param {SelectorCallback<T>} selector
     * @param {IDOMDeferredSelectorParameters} parameters
     * @returns {DOMDeferredSelector<T>}
     */
    public static use<T extends Element = Element>(selector: SelectorCallback<T>, parameters: IDOMDeferredSelectorParameters): DOMDeferredSelector<T>{
        const deferred_selector = new this(selector, parameters);
        deferred_selector.activate();
        return deferred_selector;
    }

    /**
     * @param {SelectorCallback<T>} selector
     * @param {IDOMDeferredSelectorParameters} parameters
     */
    public constructor(selector: SelectorCallback<T>, parameters: IDOMDeferredSelectorParameters) {
        this.selector = selector;
        this.parameters = parameters;
        this._deferred = new DeferredPromise<T, never>();

        this._repeats_left = this.parameters.repeats;

        this.awaitElementProcess = this.awaitElementProcess.bind(this);
    }

    /**
     * @protected
     */
    public activate(): void{
        if(this._is_activated || this._is_cancelled)
            return;

        this._is_activated = true;
        this._is_cancelled = false;

        setImmediate(this.awaitElementProcess);
    }

    /**
     * Reactivates DOMDeferredSelector so it can be activated once again
     */
    public reactivate(): void{
        if(this._is_activated || !this._is_cancelled)
            return;

        this._is_activated = false;
        this._is_cancelled = false;

        if(this._interval_ref !== null){
            clearInterval(this._interval_ref);
            this._interval_ref = null;
        }

        this._deferred = new DeferredPromise<T, never>();
        this._repeats_left = this.parameters.repeats;
    }

    /**
     * @returns {Promise<T | null>}
     */
    public promise(): Promise<T|null>{
        return this._deferred.promise;
    }

    /**
     * Cancels deferred selection and resolves promise with null result
     */
    public cancel(): void{
        if(!this._is_activated || this._is_cancelled)
            return;

        clearInterval(this._interval_ref);
        this._interval_ref = null;

        this._is_activated = false;
        this._is_cancelled = true;
    }

    /**
     * @protected
     */
    protected awaitElementProcess(): void{
        if(this._is_cancelled){
            clearInterval(this._interval_ref);
            this._interval_ref = null;
            return this._deferred.resolve(null);
        }

        this._repeats_left--;
        const $element = this.selector.apply(null, []);
        if($element !== null){
            clearInterval(this._interval_ref);
            this._interval_ref = null;
            return this._deferred.resolve($element);
        }

        if(this._repeats_left === 0 || this._is_cancelled){
            clearInterval(this._interval_ref);
            this._interval_ref = null;
            return this._deferred.resolve(null);
        }

        if(this._interval_ref === null && !this._is_cancelled)
            this._interval_ref = setInterval(this.awaitElementProcess, this.parameters.interval) as unknown as number;
    }

}
