import hash from "object-hash";

import { LastQueue } from "@/utils/lastQueue";

import { HasAttributesMixin } from "./utils";

/**
 * Builds a provider that gets its result from a single promise-based query function and
 * caches the result of lookup for subsequent instantiations.
 *
 * @param   {Function}  lookup  async function that loads the result, parameters will be an object
 *                              whose properties are the attributes assigned to the provider component
 * @param   {Function}  stopRefresh function that will be called with result of lookup.
 *                                  If function returns true refresh will be stopped.
 * @return  {VueComponentOptions} Vue component options definition
 */
export const SingleQueryProvider = (lookup, stopRefresh = (result) => false) => {
    const promiseCache = new Map();
    return {
        mixins: [HasAttributesMixin],
        props: {
            useCache: {
                type: Boolean,
                default: false,
            },
            autoRefresh: {
                type: Boolean,
                default: false,
            },
            autoTime: {
                type: Number,
                default: 3000,
            },
        },
        data() {
            return {
                result: null,
                error: null,
                timeoutId: null,
            };
        },
        created() {
            this.queue = new LastQueue(this.autoTime);
        },
        computed: {
            loading() {
                return this.result === null;
            },
            cacheKey() {
                return hash(this.$attrs || {});
            },
        },
        mounted() {
            this.doQuery();
        },
        destroyed() {
            if (this.timeoutId) {
                clearTimeout(this.timeoutId);
            }
        },
        render() {
            return (
                this.$scopedSlots.default &&
                this.$scopedSlots.default({
                    loading: this.loading,
                    result: this.result,
                    error: this.error,
                })
            );
        },
        methods: {
            update(attributes) {
                for (var attrname in attributes) {
                    this.attributes[attrname] = attributes[attrname];
                }
                this.doQuery();
            },
            doQuery() {
                let lookupPromise;
                if (this.useCache) {
                    lookupPromise = promiseCache.get(this.cacheKey);
                    if (!lookupPromise) {
                        lookupPromise = lookup(this.$attrs);
                        promiseCache.set(this.cacheKey, lookupPromise);
                    }
                } else {
                    lookupPromise = this.queue.enqueue(lookup, this.attributes);
                }
                lookupPromise.then(
                    (result) => {
                        this.result = result;
                        this.$emit("update:result", result);
                        this.error = null;
                        if (this.autoRefresh && !stopRefresh(result)) {
                            this.timeoutId = setTimeout(() => {
                                this.doQuery();
                            }, this.autoTime);
                        }
                    },
                    (err) => {
                        this.result = {};
                        this.error = err;
                        this.$emit("error", err);
                        console.debug("Failed to fulfill promise.", err);
                    },
                );
            },
        },
    };
};
