<script setup>
import axios from 'axios';
import { onMounted, onUnmounted, computed, ref, watch, nextTick } from 'vue';
import { onBeforeRouteLeave, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { useState } from 'src/composables/useState';
import { useStateStore } from 'stores/useStateStore';
import { useValidator } from 'src/composables/useValidator';
import useNotify from 'src/composables/useNotify.js';
import SkeletonForm from 'components/ui/SkeletonForm.vue';
import VnConfirm from './ui/VnConfirm.vue';
import { tMobile } from 'src/composables/tMobile';
import { useArrayData } from 'src/composables/useArrayData';

const { push } = useRouter();
const quasar = useQuasar();
const state = useState();
const stateStore = useStateStore();
const { t } = useI18n();
const { validate } = useValidator();
const { notify } = useNotify();

const $props = defineProps({
    url: {
        type: String,
        default: '',
    },
    model: {
        type: String,
        default: '',
    },
    filter: {
        type: Object,
        default: null,
    },
    urlUpdate: {
        type: String,
        default: null,
    },
    urlCreate: {
        type: String,
        default: null,
    },
    defaultActions: {
        type: Boolean,
        default: true,
    },
    defaultButtons: {
        type: Object,
        default: () => {},
    },
    autoLoad: {
        type: Boolean,
        default: false,
    },
    formInitialData: {
        type: Object,
        default: () => {},
    },
    observeFormChanges: {
        type: Boolean,
        default: true,
        description:
            'Esto se usa principalmente para permitir guardar sin hacer cambios (Útil para la feature de clonar ya que en este caso queremos poder guardar de primeras)',
    },
    mapper: {
        type: Function,
        default: null,
    },
    clearStoreOnUnmount: {
        type: Boolean,
        default: true,
    },
    saveFn: {
        type: Function,
        default: null,
    },
    goTo: {
        type: String,
        default: '',
        description: 'It is used for redirect on click "save and continue"',
    },
});

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

const componentIsRendered = ref(false);
const arrayData = useArrayData($props.model);
const isLoading = ref(false);
// Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas
const isResetting = ref(false);
const hasChanges = ref(!$props.observeFormChanges);
const originalData = ref({});
const formData = computed(() => state.get($props.model));
const formUrl = computed(() => $props.url);
const defaultButtons = computed(() => ({
    save: {
        color: 'primary',
        icon: 'save',
        label: 'globals.save',
    },
    reset: {
        color: 'primary',
        icon: 'restart_alt',
        label: 'globals.reset',
    },
    ...$props.defaultButtons,
}));

onMounted(async () => {
    originalData.value = JSON.parse(JSON.stringify($props.formInitialData ?? {}));

    nextTick(() => (componentIsRendered.value = true));

    // Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla
    state.set($props.model, $props.formInitialData);

    if ($props.autoLoad && !$props.formInitialData && $props.url) await fetch();
    else if (arrayData.store.data) updateAndEmit('onFetch', arrayData.store.data);

    if ($props.observeFormChanges) {
        watch(
            () => formData.value,
            (newVal, oldVal) => {
                if (!oldVal) return;
                hasChanges.value =
                    !isResetting.value &&
                    JSON.stringify(newVal) !== JSON.stringify(originalData.value);
                isResetting.value = false;
            },
            { deep: true }
        );
    }
});

if (!$props.url)
    watch(
        () => arrayData.store.data,
        (val) => updateAndEmit('onFetch', val)
    );

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

onBeforeRouteLeave((to, from, next) => {
    if (hasChanges.value && $props.observeFormChanges)
        quasar.dialog({
            component: VnConfirm,
            componentProps: {
                title: t('Unsaved changes will be lost'),
                message: t('Are you sure exit without saving?'),
                promise: () => next(),
            },
        });
    else next();
});

onUnmounted(() => {
    // Restauramos los datos originales en el store si se realizaron cambios en el formulario pero no se guardaron, evitando modificaciones erróneas.
    if (hasChanges.value) return state.set($props.model, originalData.value);
    if ($props.clearStoreOnUnmount) state.unset($props.model);
});

async function fetch() {
    try {
        let { data } = await axios.get($props.url, {
            params: { filter: JSON.stringify($props.filter) },
        });
        if (Array.isArray(data)) data = data[0] ?? {};

        updateAndEmit('onFetch', data);
    } catch (e) {
        state.set($props.model, {});
        originalData.value = {};
    }
}

async function save() {
    if ($props.observeFormChanges && !hasChanges.value)
        return notify('globals.noChanges', 'negative');

    isLoading.value = true;
    try {
        const body = $props.mapper ? $props.mapper(formData.value) : formData.value;
        const method = $props.urlCreate ? 'post' : 'patch';
        const url =
            $props.urlCreate || $props.urlUpdate || $props.url || arrayData.store.url;
        let response;

        if ($props.saveFn) response = await $props.saveFn(body);
        else response = await axios[method](url, body);

        if ($props.urlCreate) notify('globals.dataCreated', 'positive');

        updateAndEmit('onDataSaved', formData.value, response?.data);
    } catch (err) {
        console.error(err);
        notify('errors.writeRequest', 'negative');
    } finally {
        hasChanges.value = false;
        isLoading.value = false;
    }
}

async function saveAndGo() {
    await save();
    push({ path: $props.goTo });
}

function reset() {
    updateAndEmit('onFetch', originalData.value);
    if ($props.observeFormChanges) {
        hasChanges.value = false;
        isResetting.value = true;
    }
}

// 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);
        }
    );
}

function updateAndEmit(evt, val, res) {
    state.set($props.model, val);
    originalData.value = val && JSON.parse(JSON.stringify(val));
    if (!$props.url) arrayData.store.data = val;

    emit(evt, state.get($props.model), res);
}

defineExpose({ save, isLoading, hasChanges });
</script>
<template>
    <div class="column items-center full-width">
        <QForm
            v-if="formData"
            @submit="save"
            @reset="reset"
            class="q-pa-md"
            id="formModel"
        >
            <QCard>
                <slot
                    name="form"
                    :data="formData"
                    :validate="validate"
                    :filter="filter"
                />
            </QCard>
        </QForm>
    </div>
    <Teleport
        to="#st-actions"
        v-if="stateStore?.isSubToolbarShown() && componentIsRendered"
    >
        <div v-if="$props.defaultActions">
            <QBtnGroup push class="q-gutter-x-sm">
                <slot name="moreActions" />
                <QBtn
                    :label="tMobile(defaultButtons.reset.label)"
                    :color="defaultButtons.reset.color"
                    :icon="defaultButtons.reset.icon"
                    flat
                    @click="reset"
                    :disable="!hasChanges"
                    :title="t(defaultButtons.reset.label)"
                />
                <QBtnDropdown
                    v-if="$props.goTo"
                    @click="saveAndGo"
                    :label="tMobile('globals.saveAndContinue')"
                    :title="t('globals.saveAndContinue')"
                    :disable="!hasChanges"
                    color="primary"
                    icon="save"
                    split
                >
                    <QList>
                        <QItem
                            clickable
                            v-close-popup
                            @click="save"
                            :title="t('globals.save')"
                        >
                            <QItemSection>
                                <QItemLabel>
                                    <QIcon
                                        name="save"
                                        color="white"
                                        class="q-mr-sm"
                                        size="sm"
                                    />
                                    {{ t('globals.save').toUpperCase() }}
                                </QItemLabel>
                            </QItemSection>
                        </QItem>
                    </QList>
                </QBtnDropdown>
                <QBtn
                    v-else
                    :label="tMobile('globals.save')"
                    color="primary"
                    icon="save"
                    @click="save"
                    :disable="!hasChanges"
                    :title="t(defaultButtons.save.label)"
                />
            </QBtnGroup>
        </div>
    </Teleport>
    <SkeletonForm v-if="!formData" />
    <QInnerLoading
        :showing="isLoading"
        :label="t('globals.pleaseWait')"
        color="primary"
        style="min-width: 100%"
    />
</template>
<style lang="scss" scoped>
.q-notifications {
    color: black;
}
#formModel {
    max-width: 800px;
    width: 100%;
}

.q-card {
    padding: 32px;
}
</style>
<i18n>
es:
    Unsaved changes will be lost: Los cambios que no haya guardado se perderán
    Are you sure exit without saving?: ¿Seguro que quiere salir sin guardar?
</i18n>
