
import {filter, take, debounceTime, switchMap} from 'rxjs/operators';
import { Subject ,  Observable } from 'rxjs';
import { Type } from '@angular/core';
import { Store } from '@ngrx/store';

import { Hypermedia } from '../interfaces';
import { ReflectiveModelBinderFactory, ReflectiveQueryMatcherFactory } from 'first-npm-package-nicule/models';
import { HypermediaState } from '../reducers';
import { FetchHypermedia } from '../actions';

export class ResourceSelector {
    private refresh$: Subject<undefined> = new Subject();
    private downloadProgress$: Observable<number>;
    private uploadProgress$: Observable<number>;
    private hypermedia$: Observable<Hypermedia>;
    private interceptors = {};

    constructor(private path: string,
                private scope: string,
                private authorize: boolean,
                private tokenOverride: string,
                private store: Store<{ hypermedia: HypermediaState }>,
                private modelBinderFactory: ReflectiveModelBinderFactory,
                private queryMatcherFactory: ReflectiveQueryMatcherFactory
    ) {
        this.hypermedia$ = store.select('hypermedia', 'hypermedia', scope, path, 'data').pipe(
            filter(data => data !== undefined));
        this.uploadProgress$ = store.select('hypermedia', 'hypermedia', scope, path, 'upload');
        this.downloadProgress$ = store.select('hypermedia', 'hypermedia', scope, path, 'download');
    }

    downloadProgress(): Observable<number> {
        return this.downloadProgress$;
    }

    uploadProgress(): Observable<number> {
        return this.uploadProgress$;
    }

    refresh(success: (response?: Hypermedia) => void = () => { }): void {
        const subscription = this.hypermedia$
            .pipe(take(1))
            .subscribe(response => success(response));

        this.fetch();
    }

    fetch(): void {
        this.store.dispatch(new FetchHypermedia({
            path: this.path,
            scope: this.scope,
            authorize: this.authorize,
            tokenOverride: this.tokenOverride,
            interceptors: this.interceptors
        }));
    }

    intercept(statusCode: string | number, interceptor: (hypermedia: Hypermedia) => void): void {
        this.interceptors[statusCode] = interceptor;
    }

    as<T>(type: Type<T>, noFetch = false): Observable<T> {
        const modelBinder = this.modelBinderFactory.make(type);
        const queryMatcher = this.queryMatcherFactory.make(type);

        return this.hypermedia$
            .onSubscribe(() => {
                if (!noFetch) {
                    this.fetch();
                }
            })
            .pipe(
                debounceTime(100),
                switchMap(hypermedia => {
                    if (queryMatcher.matches(hypermedia)) {
                        return [modelBinder.bind(hypermedia)];
                    }

                    return [undefined];
                })
            )
    }
}
