import { onMounted, 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 = useRoute().meta.moduleName, 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 route = useRoute();
    const router = useRouter();
    let canceller = null;

    onMounted(() => {
        setOptions();
        reset(['skip']);

        const query = route.query;
        const searchUrl = store.searchUrl;
        if (query[searchUrl]) {
            const params = JSON.parse(query[searchUrl]);
            const filter = params?.filter && JSON.parse(params?.filter ?? '{}');
            delete params.filter;

            store.userParams = { ...store.userParams, ...params };
            store.userFilter = { ...filter, ...store.userFilter };
            if (filter?.order) store.order = filter.order;
        }
    });

    if (key && userOptions) setOptions();

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

                if (Object.hasOwn(store, option)) {
                    const defaultOpts = userOptions[option];
                    store[option] = userOptions.keepOpts?.includes(option)
                        ? Object.assign(defaultOpts, store[option])
                        : defaultOpts;
                }
            }
        }
    }

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

        cancelRequest();
        canceller = new AbortController();

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

        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);
        let where;
        if (filter?.where || store.filter?.where)
            where = Object.assign(filter?.where ?? {}, store.filter?.where ?? {});
        Object.assign(filter, store.filter);
        filter.where = where;
        const params = { filter };

        Object.assign(params, userParams);
        params.filter.skip = store.skip;
        if (store.order && store.order.length) params.filter.order = store.order;
        else delete params.filter.order;

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

        const { limit } = filter;
        store.hasMoreData = limit && 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"][aria-modal="true"]').length)
                updateRouter && updateStateParams();
        }

        store.isLoading = false;

        canceller = null;
        return response;
    }

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

    function deleteOption(option) {
        delete store[option];
    }

    function reset(opts = []) {
        if (arrayDataStore.get(key)) arrayDataStore.reset(key, opts);
    }

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

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

        const response = await fetch({});
        return response;
    }

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

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

        store.userParams = userParams;
        reset(['skip', 'filter.skip', 'page']);

        await fetch({});
        return { filter, params };
    }

    async function addFilterWhere(where) {
        const storedFilter = { ...store.filter };
        if (!storedFilter?.where) storedFilter.where = {};
        where = { ...storedFilter.where, ...where };
        await addFilter({ filter: { where } });
    }

    async function addOrder(field, direction = 'ASC') {
        const newOrder = field + ' ' + direction;
        let order = store.order || [];
        if (typeof order == 'string') order = [order];

        let index = order.findIndex((o) => o.split(' ')[0] === field);
        if (index > -1) {
            order[index] = newOrder;
        } else {
            index = order.length;
            order.push(newOrder);
        }

        store.order = order;
        reset(['skip', 'filter.skip', 'page']);
        fetch({});
        index++;

        return { index, order };
    }

    async function deleteOrder(field) {
        let order = store.order ?? [];
        if (typeof order == 'string') order = [order];

        const index = order.findIndex((o) => o.split(' ')[0] === field);
        if (index > -1) order.splice(index, 1);

        store.order = order;
        fetch({});
    }

    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) {
                    let key;
                    if (exprBuilder) {
                        const result = exprBuilder(param);
                        if (result !== undefined && result !== null)
                            key = Object.keys(result);
                    } else {
                        if (typeof param === 'object' && param !== null)
                            key = Object.keys(param);
                    }
                    if (key && key[0]) {
                        delete store.filter.where[key[0]];
                        if (Object.keys(store.filter.where).length === 0) {
                            delete store.filter.where;
                        }
                    }
                }
            }
        }
        return params;
    }

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

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

        await fetch({ append: true });
    }

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

    function updateStateParams() {
        if (!route) return;
        const newUrl = { path: route.path, query: { ...(route.query ?? {}) } };
        newUrl.query[store.searchUrl] = JSON.stringify(store.currentFilter);

        if (store.navigate) {
            const { customRouteRedirectName, searchText } = store.navigate;
            if (customRouteRedirectName)
                return router.push({
                    name: customRouteRedirectName,
                    params: { id: searchText },
                });
            const { matched: matches } = router.currentRoute.value;
            const { path } = matches.at(-1);

            const to =
                store?.data?.length === 1
                    ? path.replace(/\/(list|:id)|-list/, `/${store.data[0].id}`)
                    : path.replace(/:id.*/, '');

            if (route.path != to) {
                const pushUrl = { path: to };
                if (to.endsWith('/list') || to.endsWith('/'))
                    pushUrl.query = newUrl.query;
                destroy();
                return router.push(pushUrl);
            }
        }

        router.replace(newUrl);
    }

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

    return {
        fetch,
        applyFilter,
        addFilter,
        addFilterWhere,
        addOrder,
        deleteOrder,
        refresh,
        destroy,
        loadMore,
        store,
        totalRows,
        updateStateParams,
        isLoading,
        deleteOption,
        reset,
    };
}
