import { onMounted, ref, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import axios from 'axios';
import { useArrayDataStore } from 'stores/useArrayDataStore';
import { buildFilter } from 'filters/filterPanel';

const arrayDataStore = useArrayDataStore();

export function useArrayData(key, userOptions) {
    if (!key) throw new Error('ArrayData: A key is required to use this composable');

    if (!arrayDataStore.get(key)) {
        arrayDataStore.set(key);
    }

    const store = arrayDataStore.get(key);
    const hasMoreData = ref(false);
    const router = useRouter();
    const route = useRoute();
    let canceller = null;

    const page = ref(1);

    onMounted(() => {
        setOptions();

        const query = route.query;
        if (query.params) {
            store.userParams = JSON.parse(query.params);
        }
    });

    if (key && userOptions) {
        setOptions();
    }

    function setOptions() {
        const allowedOptions = [
            'url',
            'filter',
            'where',
            'order',
            'limit',
            'skip',
            'userParams',
            'userFilter',
            'exprBuilder',
        ];
        if (typeof userOptions === 'object') {
            for (const option in userOptions) {
                const isEmpty = userOptions[option] == null || userOptions[option] === '';
                if (isEmpty || !allowedOptions.includes(option)) continue;

                if (Object.prototype.hasOwnProperty.call(store, option)) {
                    store[option] = userOptions[option];
                }
            }
        }
    }

    async function fetch({ append = false, updateRouter = true }) {
        if (!store.url) return;

        cancelRequest();
        canceller = new AbortController();

        const filter = {
            order: store.order,
            limit: store.limit,
            skip: store.skip,
        };

        let exprFilter;
        let userParams = { ...store.userParams };
        if (store?.exprBuilder) {
            const where = buildFilter(userParams, (param, value) => {
                const res = store.exprBuilder(param, value);
                if (res) delete userParams[param];
                return res;
            });
            exprFilter = where ? { where } : null;
        }

        Object.assign(filter, store.userFilter, exprFilter);
        Object.assign(store.filter, filter);
        const params = {
            filter: JSON.stringify(store.filter),
        };

        Object.assign(params, userParams);

        store.isLoading = true;
        store.currentFilter = params;
        const response = await axios.get(store.url, {
            signal: canceller.signal,
            params,
        });

        const { limit } = filter;

        hasMoreData.value = response.data.length === limit;

        if (append) {
            if (!store.data) store.data = [];
            for (const row of response.data) store.data.push(row);
        } else {
            store.data = response.data;
            if (!document.querySelectorAll('[role="dialog"]'))
                updateRouter && updateStateParams();
        }

        store.isLoading = false;

        canceller = null;
        return response;
    }

    function destroy() {
        if (arrayDataStore.get(key)) {
            arrayDataStore.clear(key);
        }
    }

    function cancelRequest() {
        if (canceller) {
            canceller.abort();
            canceller = null;
        }
    }

    async function applyFilter({ filter, params }) {
        if (filter) store.userFilter = filter;
        store.filter = {};
        if (params) store.userParams = Object.assign({}, params);

        await fetch({ append: false });
    }

    async function addFilter({ filter, params }) {
        if (filter) store.userFilter = Object.assign(store.userFilter, filter);

        let userParams = Object.assign({}, store.userParams, params);
        userParams = sanitizerParams(userParams, store?.exprBuilder);

        store.userParams = userParams;
        store.skip = 0;
        store.filter.skip = 0;
        await fetch({ append: false });
        return { filter, params };
    }

    function sanitizerParams(params, exprBuilder) {
        for (const param in params) {
            if (params[param] === '' || params[param] === null) {
                delete store.userParams[param];
                delete params[param];
                if (store.filter?.where) {
                    delete store.filter.where[
                        Object.keys(exprBuilder ? exprBuilder(param) : param)[0]
                    ];
                    if (Object.keys(store.filter.where).length === 0) {
                        delete store.filter.where;
                    }
                }
            }
        }
        return params;
    }

    async function loadMore() {
        if (!hasMoreData.value) return;

        store.skip = store.limit * page.value;
        page.value += 1;

        await fetch({ append: true });
    }

    async function refresh() {
        if (Object.values(store.userParams).length) await fetch({ append: false });
    }

    function updateStateParams() {
        const query = {};
        if (store.order) query.order = store.order;
        if (store.limit) query.limit = store.limit;
        if (store.skip) query.skip = store.skip;
        if (store.userParams && Object.keys(store.userParams).length !== 0)
            query.params = JSON.stringify(store.userParams);

        if (router)
            router.replace({
                path: route.path,
                query: query,
            });
    }

    const totalRows = computed(() => (store.data && store.data.length) || 0);
    const isLoading = computed(() => store.isLoading || false);

    return {
        fetch,
        applyFilter,
        addFilter,
        refresh,
        destroy,
        loadMore,
        store,
        hasMoreData,
        totalRows,
        updateStateParams,
        isLoading,
    };
}
