<script setup>
import axios from 'axios';
import { onMounted, onUnmounted, computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { useState } from 'src/composables/useState';
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';

const quasar = useQuasar();
const state = useState();
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: null,
    },
    defaultSave: {
        type: Boolean,
        default: true,
    },
    defaultReset: {
        type: Boolean,
        default: true,
    },
    defaultRemove: {
        type: Boolean,
        default: true,
    },
    selected: {
        type: Object,
        default: null,
    },
});

const isLoading = ref(false);
const hasChanges = ref(false);
const originalData = ref();
const vnPaginateRef = ref();
const formData = computed(() => state.get($props.model));
const formUrl = computed(() => $props.url);

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

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

onMounted(async () => {
    await fetch();
});

onUnmounted(() => {
    state.unset($props.model);
});

function tMobile(...args) {
    if (!quasar.platform.is.mobile) return t(...args);
}
async function fetch(data) {
    if (data && Array.isArray(data)) {
        let $index = 0;
        data.map((d) => (d.$index = $index++));
    }

    state.set($props.model, data);
    originalData.value = data && JSON.parse(JSON.stringify(data));

    watch(formData.value, () => (hasChanges.value = true));

    emit('onFetch', state.get($props.model));
}

function reset() {
    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) {
    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));
    hasChanges.value = false;
    isLoading.value = false;
    if (changes.creates?.length) await vnPaginateRef.value.fetch();
}

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)
        );
        state.set($props.model, newData);
        const changes = getChanges();
        if (!changes.creates?.length && !changes.updates?.length)
            hasChanges.value = false;
    }
    if (ids.length) {
        quasar
            .dialog({
                component: VnConfirm,
                componentProps: {
                    title: t('confirmDeletion'),
                    message: t('confirmDeletionMessage'),
                    newData,
                    ids,
                },
            })
            .onOk(async () => {
                await saveChanges({ deletes: ids });
                newData = newData.filter((form) => !ids.some((id) => id == form[pk]));
                state.set($props.model, 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 = {};
    for (let key in obj1) {
        if (obj2[key] && obj1[key] !== obj2[key]) {
            diff[key] = obj2[key];
        }
    }
    for (let key in obj2) {
        if (obj1[key] === undefined && 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;
}

watch(formUrl, async () => {
    originalData.value = null;
    reset();
    fetch();
});
</script>
<template>
    <VnPaginate
        @submit="onSubmit"
        @reset="reset"
        :url="url"
        v-bind="$attrs"
        @on-fetch="fetch"
        class="q-pa-md"
        :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 class="q-gutter-x-sm">
            <slot name="moreActions" />
            <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')"
                color="primary"
                icon="save"
                @click="onSubmit"
                :disable="!hasChanges"
                :title="t('globals.save')"
                v-if="$props.defaultSave"
            />
        </QBtnGroup>
    </Teleport>
    <QInnerLoading
        :showing="isLoading"
        :label="t && t('globals.pleaseWait')"
        color="primary"
    />
</template>

<i18n>
    {
        "en": {
            "confirmDeletion": "Confirm deletion",
            "confirmDeletionMessage": "Are you sure you want to delete this?"
        },
        "es": {
            "confirmDeletion": "Confirmar eliminación",
            "confirmDeletionMessage": "Seguro que quieres eliminar?"
        }
    }
</i18n>
