<script setup>
import axios from 'axios';
import {
    onMounted,
    onUnmounted,
    computed,
    ref,
    watch,
    nextTick,
    useAttrs,
    toRef,
} from 'vue';
import { onBeforeRouteLeave, useRouter, useRoute } 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';
import { onBeforeSave } from 'src/filters';
const { push } = useRouter();
const quasar = useQuasar();
const state = useState();
const stateStore = useStateStore();
const { t } = useI18n();
const { validate, validations } = useValidator();
const { notify } = useNotify();
const route = useRoute();
const myForm = ref(null);
const $props = defineProps({
    url: {
        type: String,
        default: '',
    },
    model: {
        type: String,
        default: null,
    },
    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: onBeforeSave,
    },
    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"',
    },
    reload: {
        type: Boolean,
        default: true,
    },
    defaultTrim: {
        type: Boolean,
        default: true,
    },
    maxWidth: {
        type: [String, Boolean],
        default: '800px',
    },
    onDataSaved: {
        type: Function,
        default: () => {},
    },
    preventSubmit: {
        type: Boolean,
        default: false,
    },
});
const emit = defineEmits(['onFetch', 'onDataSaved', 'submit']);
const modelValue = computed(
    () => $props.model ?? `formModel_${route?.meta?.title ?? route.name}`,
).value;
const componentIsRendered = ref(false);
const arrayData = useArrayData(modelValue);
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 = computed(() => state.get(modelValue));
const formData = ref();
const defaultButtons = computed(() => ({
    save: {
        dataCy: 'saveDefaultBtn',
        color: 'primary',
        icon: 'save',
        label: 'globals.save',
        click: async (evt) => submitForm(evt),
        type: 'submit',
    },
    reset: {
        dataCy: 'resetDefaultBtn',
        color: 'primary',
        icon: 'restart_alt',
        label: 'globals.reset',
        click: () => reset(),
    },
    ...$props.defaultButtons,
}));

const submitForm = async (evt) => {
    const isFormValid = await myForm.value.validate();
    if (isFormValid) {
        await save(evt);
    }
};

onMounted(async () => {
    nextTick(() => (componentIsRendered.value = true));

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

    if (!$props.formInitialData) {
        if ($props.autoLoad && $props.url) await fetch();
        else if (arrayData.store.data)
            updateAndEmit('onFetch', { val: 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(
    originalData,
    (val) => {
        if (val) formData.value = JSON.parse(JSON.stringify(val));
    },
    { immediate: true },
);

watch(
    () => [$props.url, $props.filter],
    async () => {
        state.set(modelValue, null);
        reset();
        await fetch();
    },
);

onBeforeRouteLeave((to, from, next) => {
    if (hasChanges.value && $props.observeFormChanges)
        quasar.dialog({
            component: VnConfirm,
            componentProps: {
                title: t('globals.unsavedPopup.title'),
                message: t('globals.unsavedPopup.subtitle'),
                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(modelValue, originalData.value);
    if ($props.clearStoreOnUnmount) state.unset(modelValue);
});

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', { val: data });
    } catch (e) {
        state.set(modelValue, {});
        throw e;
    }
}

async function handleResponse(promise) {
    const [response] = await Promise.all([promise]);
    updateAndEmit('onDataSaved', {
        val: formData.value,
        res: response?.data,
        old: originalData.value,
    });
    if ($props.reload) await arrayData.fetch({});
    hasChanges.value = false;
}

async function create() {
    const promise = $props.saveFn
        ? $props.saveFn(formData.value)
        : axios.post($props.urlCreate, formData.value);

    await handleResponse(promise);
    notify('globals.dataCreated', 'positive');
}

async function update() {
    const body = $props.mapper
        ? $props.mapper(originalData.value, formData.value)
        : formData.value;
    const url = $props.urlUpdate || $props.url || arrayData.store.url;
    const promise = $props.saveFn ? $props.saveFn(body) : axios.patch(url, body);
    await handleResponse(promise);
}

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

    isLoading.value = true;
    try {
        formData.value = trimData(formData.value);
        if ($props.urlCreate) {
            await create();
        } else {
            await update();
        }
    } finally {
        isLoading.value = false;
    }
}

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

function reset() {
    formData.value = JSON.parse(JSON.stringify(originalData.value));
    updateAndEmit('onFetch', { val: 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, old } = { val: null, res: null, old: null }) {
    state.set(modelValue, val);
    if (!$props.url) arrayData.store.data = val;

    emit(evt, state.get(modelValue), res, old, formData);
}

function trimData(data) {
    if (!$props.defaultTrim) return data;
    for (const key in data) {
        if (typeof data[key] == 'string') data[key] = data[key].trim();
    }
    return data;
}

async function onKeyup(evt) {
    if (evt.key === 'Enter' && !$props.preventSubmit) {
        const input = evt.target;
        if (input.type == 'textarea' && evt.shiftKey) {
            let { selectionStart, selectionEnd } = input;
            input.value =
                input.value.substring(0, selectionStart) +
                '\n' +
                input.value.substring(selectionEnd);
            selectionStart = selectionEnd = selectionStart + 1;
            return;
        }
        await myForm.value.submit(evt);
    }
}

defineExpose({
    submitForm,
    myForm,
    save,
    isLoading,
    hasChanges,
    reset,
    fetch,
    formData,
});
</script>
<template>
    <div class="column items-center full-width">
        <QForm
            id="formModel"
            v-on="$attrs"
            ref="myForm"
            v-if="formData"
            @submit.prevent="save"
            @keyup.prevent="onKeyup"
            @reset="reset"
            class="full-width q-pa-md"
            :style="maxWidth ? 'max-width: ' + maxWidth : ''"
        >
            <QCard>
                <slot
                    v-if="formData"
                    name="form"
                    :data="formData"
                    :validate="validate"
                    :validations="validations()"
                    :filter="filter"
                />
                <SkeletonForm v-else />
            </QCard>
        </QForm>
    </div>
    <Teleport
        to="#st-actions"
        v-if="
            $props.defaultActions &&
            stateStore?.isSubToolbarShown() &&
            componentIsRendered
        "
    >
        <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="defaultButtons.reset.click"
                :disable="!hasChanges"
                :title="t(defaultButtons.reset.label)"
            />
            <QBtnDropdown
                data-cy="saveAndContinueDefaultBtn"
                v-if="$props.goTo"
                @click="saveAndGo"
                :label="
                    tMobile('globals.saveAndContinue') +
                    ' ' +
                    t('globals.' + $props.goTo.split('/').pop())
                "
                :title="
                    t('globals.saveAndContinue') +
                    ' ' +
                    t('globals.' + $props.goTo.split('/').pop())
                "
                :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
                data-cy="saveDefaultBtn"
                :label="tMobile('globals.save')"
                color="primary"
                icon="save"
                @click="defaultButtons.save.click"
                :disable="!hasChanges"
                :title="t(defaultButtons.save.label)"
            />
        </QBtnGroup>
    </Teleport>

    <QInnerLoading
        :showing="isLoading"
        :label="t('globals.pleaseWait')"
        color="primary"
        style="min-width: 100%"
    />
</template>
<style lang="scss" scoped>
.q-notifications {
    color: black;
}

.q-card {
    padding: 32px;
}
</style>
