import { Observable } from 'rxjs';
import { publishReplay, refCount, tap } from 'rxjs/operators';

let allCachesResetSeq = 0;


/**
 * Clears all memorized data for memorizeObservable calls. All calls made to those functions after
 * clearing will result in the fetch func being called again.
 *
 * You must call this function after you've modified a memorized resource, or else some components of
 * the UI may have a stale view of the resouce.
 */
export function resetAllMemorizationCaches(): void {
    allCachesResetSeq++;
}

/**
 * Creates a function that memorizes the observable result of func.
 * If the Observable errors, the value will not be cached.
 *
 * @param resolver If resolver provided, it determines the cache key for storing the result based on
 * the first argument provided to the memorized function.
 */
export function memorizeObservable<P, T> (
    func: (params: P) => Observable<T>,
    resolver?: (params: P) => string,
): (params: P, force?: boolean) => Observable<T> {
    const cache = new Map<string, Observable<T>>();
    let cacheResetSeq = allCachesResetSeq;
    return (params: P, force = false) => {
        // Reset cache if resetAllMemorizationCaches was called.
        if (cacheResetSeq < allCachesResetSeq) {
            cache.clear();
            cacheResetSeq = allCachesResetSeq;
        }

        const key = resolver ? resolver(params) : params.toString();
        const hit = cache.get(key);
        if (!force && hit) {
            return hit;
        }
        const obs = func(params).pipe(
            publishReplay(),
            refCount(),
            tap(undefined as any, e => {
                cache.delete(key);
            }),
        );
        cache.set(key, obs);
        return obs;
    };
}
