<script setup>
import axios from 'axios';
import { computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { useValidator } from 'src/composables/useValidator';
import { useStateStore } from 'stores/useStateStore';
import VnPaginate from 'components/ui/VnPaginate.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import SkeletonTable from 'components/ui/SkeletonTable.vue';
import { tMobile } from 'src/composables/tMobile';

const quasar = useQuasar();
const stateStore = useStateStore();
const { t } = useI18n();
const { validate } = useValidator();

const $props = defineProps({
    model: {
        type: String,
        default: '',
    },
    url: {
        type: String,
        default: '',
    },
    saveUrl: {
        type: String,
        default: null,
    },
    primaryKey: {
        type: String,
        default: 'id',
    },
    dataRequired: {
        type: Object,
        default: () => {},
    },
    defaultSave: {
        type: Boolean,
        default: true,
    },
    defaultReset: {
        type: Boolean,
        default: true,
    },
    defaultRemove: {
        type: Boolean,
        default: true,
    },
    selected: {
        type: Object,
        default: null,
    },
    saveFn: {
        type: Function,
        default: null,
    },
});

const isLoading = ref(false);
const hasChanges = ref(false);
const originalData = ref();
const vnPaginateRef = ref();
const formData = ref();
const saveButtonRef = ref(null);
const formUrl = computed(() => $props.url);

const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);

defineExpose({
    reload,
    insert,
    remove,
    onSubmit,
    reset,
    hasChanges,
    saveChanges,
});

async function fetch(data) {
    if (data && Array.isArray(data)) {
        let $index = 0;
        data.map((d) => (d.$index = $index++));
    }

    originalData.value = data && JSON.parse(JSON.stringify(data));
    formData.value = data && JSON.parse(JSON.stringify(data));
    watch(formData, () => (hasChanges.value = true), { deep: true });

    emit('onFetch', data);
    return data;
}

async function reset() {
    await fetch(originalData.value);
    hasChanges.value = false;
}
// eslint-disable-next-line vue/no-dupe-keys
function filter(value, update, filterOptions) {
    update(
        () => {
            const { options, filterFn, field } = filterOptions;

            options.value = filterFn(options, value, field);
        },
        (ref) => {
            ref.setOptionIndex(-1);
            ref.moveOptionSelection(1, true);
        }
    );
}

async function onSubmit() {
    if (!hasChanges.value) {
        return quasar.notify({
            type: 'negative',
            message: t('globals.noChanges'),
        });
    }
    isLoading.value = true;
    await saveChanges();
}

async function saveChanges(data) {
    if ($props.saveFn) return $props.saveFn(data, getChanges);
    const changes = data || getChanges();
    try {
        await axios.post($props.saveUrl || $props.url + '/crud', changes);
    } catch (e) {
        return (isLoading.value = false);
    }
    originalData.value = JSON.parse(JSON.stringify(formData.value));
    if (changes.creates?.length) await vnPaginateRef.value.fetch();

    hasChanges.value = false;
    isLoading.value = false;
    emit('saveChanges', data);
    quasar.notify({
        type: 'positive',
        message: t('globals.dataSaved'),
    });
}

async function insert() {
    const $index = formData.value.length
        ? formData.value[formData.value.length - 1].$index + 1
        : 0;
    formData.value.push(Object.assign({ $index }, $props.dataRequired));
    hasChanges.value = true;
}

async function remove(data) {
    if (!data.length)
        return quasar.notify({
            type: 'warning',
            message: t('globals.noChanges'),
        });

    const pk = $props.primaryKey;
    let ids = data.map((d) => d[pk]).filter(Boolean);
    let preRemove = data.map((d) => (d[pk] ? null : d.$index)).filter(Boolean);
    let newData = formData.value;

    if (preRemove.length) {
        newData = newData.filter(
            (form) => !preRemove.some((index) => index == form.$index)
        );
        const changes = getChanges();
        if (!changes.creates?.length && !changes.updates?.length)
            hasChanges.value = false;
        fetch(newData);
    }
    if (ids.length) {
        quasar
            .dialog({
                component: VnConfirm,
                componentProps: {
                    title: t('globals.confirmDeletion'),
                    message: t('globals.confirmDeletionMessage'),
                    newData,
                    ids,
                },
            })
            .onOk(async () => {
                await saveChanges({ deletes: ids });
                newData = newData.filter((form) => !ids.some((id) => id == form[pk]));
                fetch(newData);
            });
    }
    emit('update:selected', []);
}

function getChanges() {
    const updates = [];
    const creates = [];

    const pk = $props.primaryKey;
    for (const [i, row] of formData.value.entries()) {
        if (!row[pk]) {
            creates.push(row);
        } else if (originalData.value) {
            const data = getDifferences(originalData.value[i], row);
            if (!isEmpty(data)) {
                updates.push({
                    data,
                    where: { [pk]: row[pk] },
                });
            }
        }
    }
    const changes = { updates, creates };

    for (let prop in changes) {
        if (changes[prop].length === 0) changes[prop] = undefined;
    }

    return changes;
}

function getDifferences(obj1, obj2) {
    let diff = {};
    delete obj1.$index;
    delete obj2.$index;

    for (let key in obj1) {
        if (obj2[key] && JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) {
            diff[key] = obj2[key];
        }
    }
    for (let key in obj2) {
        if (
            obj1[key] === undefined ||
            JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])
        ) {
            diff[key] = obj2[key];
        }
    }

    return diff;
}

function isEmpty(obj) {
    if (obj == null) return true;
    if (obj === undefined) return true;
    if (Object.keys(obj).length === 0) return true;

    if (obj.length > 0) return false;
}

async function reload() {
    vnPaginateRef.value.fetch();
}

watch(formUrl, async () => {
    originalData.value = null;
    reset();
});
</script>
<template>
    <VnPaginate
        :url="url"
        v-bind="$attrs"
        @on-fetch="fetch"
        :skeleton="false"
        ref="vnPaginateRef"
    >
        <template #body v-if="formData">
            <slot
                name="body"
                :rows="formData"
                :validate="validate"
                :filter="filter"
            ></slot>
        </template>
    </VnPaginate>
    <SkeletonTable v-if="!formData" />
    <Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
        <QBtnGroup push style="column-gap: 10px">
            <slot name="moreBeforeActions" />
            <QBtn
                :label="tMobile('globals.remove')"
                color="primary"
                icon="delete"
                flat
                @click="remove(selected)"
                :disable="!selected?.length"
                :title="t('globals.remove')"
                v-if="$props.defaultRemove"
            />
            <QBtn
                :label="tMobile('globals.reset')"
                color="primary"
                icon="restart_alt"
                flat
                @click="reset"
                :disable="!hasChanges"
                :title="t('globals.reset')"
                v-if="$props.defaultReset"
            />
            <QBtn
                :label="tMobile('globals.save')"
                ref="saveButtonRef"
                color="primary"
                icon="save"
                @click="onSubmit"
                :disable="!hasChanges"
                :title="t('globals.save')"
                v-if="$props.defaultSave"
            />
            <slot name="moreAfterActions" />
        </QBtnGroup>
    </Teleport>
    <QInnerLoading
        :showing="isLoading"
        :label="t && t('globals.pleaseWait')"
        color="primary"
    />
</template>
