<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 SkeletonForm from 'components/ui/SkeletonForm.vue';

const quasar = useQuasar();
const { t } = useI18n();
const state = useState();
const { validate } = useValidator();

const $props = defineProps({
    url: {
        type: String,
        default: '',
    },
    model: {
        type: String,
        default: '',
    },
    filter: {
        type: Object,
        default: null,
    },
    urlUpdate: {
        type: String,
        default: null,
    },
    crud: {
        type: Boolean,
        default: null,
    },
    primaryKey: {
        type: String,
        default: 'id',
    },
    dataRequired: {
        type: Object,
        default: null,
    },
});

const emit = defineEmits(['onFetch']);

defineExpose({
    save,
    insert,
    remove,
});

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

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

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

async function fetch() {
    const { data } = await axios.get($props.url, {
        params: { filter: $props.filter },
    });

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

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

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

async function save() {
    if (!hasChanges.value) {
        return quasar.notify({
            type: 'negative',
            message: t('globals.noChanges'),
        });
    }
    isLoading.value = true;
    await axios.patch($props.urlUpdate || $props.url, formData.value);

    originalData.value = JSON.parse(JSON.stringify(formData.value));
    hasChanges.value = false;
    isLoading.value = false;
}

function reset() {
    state.set($props.model, originalData.value);
    hasChanges.value = false;
}
// eslint-disable-next-line vue/no-dupe-keys
function filter(value, update, filterOptions) {
    update(
        () => {
            const { options, filterFn } = filterOptions;

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

async function crudSave() {
    if (!hasChanges.value) {
        return quasar.notify({
            type: 'negative',
            message: t('globals.noChanges'),
        });
    }
    isLoading.value = true;
    const changes = getChanges();
    await saveChanges(changes);
    if (changes.creates?.length) await fetch();
}

async function saveChanges(changes) {
    try {
        await axios.post($props.urlUpdate || $props.url + '/crud', changes);
    } catch (e) {
        quasar.notify({
            type: 'negative',
            message: 'Some fields are invalid',
        });
    }

    originalData.value = JSON.parse(JSON.stringify(formData.value));
    hasChanges.value = false;
    isLoading.value = false;
}

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

// function addRemove(ids) {
//     for (let id of ids) {
//         const index = removed.value.indexOf(id);
//         if (index > -1) {
//             removed.value = removed.value.slice(index, 1);
//             continue;
//         }
//         removed.value.push(id);
//     }
// }

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.$index).filter(Boolean);
    let newData = formData.value;

    // addRemove(ids);
    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) {
        await saveChanges({ deletes: ids });
        newData = newData.filter((form) => !ids.some((id) => id == form[pk]));
        state.set($props.model, newData);
    }
}

async function onSubmit() {
    if ($props.crud) return crudSave();
    return save();
}

watch(formUrl, async () => {
    originalData.value = null;
    reset();
    fetch();
});

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;
}
</script>
<template>
    <QBanner v-if="hasChanges" class="text-white bg-warning">
        <QIcon name="warning" size="md" class="q-mr-md" />
        <span>{{ t('globals.changesToSave') }}</span>
    </QBanner>
    <QForm v-if="formData" @submit="onSubmit" @reset="reset" class="q-pa-md">
        <slot name="form" :data="formData" :validate="validate" :filter="filter"></slot>
        <div class="q-mt-lg">
            <slot name="actions">
                <QBtn
                    :label="t('globals.save')"
                    type="submit"
                    color="primary"
                    :disable="!hasChanges"
                />
                <QBtn
                    :label="t('globals.reset')"
                    type="reset"
                    class="q-ml-sm"
                    color="primary"
                    flat
                    :disable="!hasChanges"
                />
            </slot>
        </div>
    </QForm>
    <SkeletonForm v-if="!formData" />
    <QInnerLoading
        :showing="isLoading"
        :label="t('globals.pleaseWait')"
        color="primary"
    />
</template>
