<script setup>
import { ref, inject, onMounted, computed, Teleport, watch } from 'vue';
import { useI18n } from 'vue-i18n';

import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia';
import useNotify from 'src/composables/useNotify.js';

const props = defineProps({
    title: {
        type: String,
        default: ''
    },
    table: {
        type: String,
        default: ''
    },
    schema: {
        type: String,
        default: ''
    },
    // Objeto con los datos iniciales del form, si este objeto es definido, no se ejecuta la query fetch
    formInitialData: {
        type: Object,
        default: () => {}
    },
    autoLoad: {
        type: Boolean,
        default: true
    },
    defaultActions: {
        type: Boolean,
        default: true
    },
    showBottomActions: {
        type: Boolean,
        default: false
    },
    saveFn: {
        type: Function,
        default: null
    },
    separationBetweenInputs: {
        type: String,
        default: 'xs'
    },
    url: {
        type: String,
        default: ''
    },
    urlUpdate: {
        type: String,
        default: null
    },
    urlCreate: {
        type: String,
        default: null
    },
    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
    },
    filter: {
        type: Object,
        default: null
    }
});

const emit = defineEmits(['onDataSaved', 'onDataFetched']);
const api = inject('api');
const { t } = useI18n();
const { notify } = useNotify();
const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore);

const isLoading = ref(false);
const formData = ref({});
const formModelRef = ref(null);
const hasChanges = ref(!props.observeFormChanges);
const isResetting = ref(false);
const originalData = ref(null);

const separationBetweenInputs = computed(() => {
    return `q-gutter-y-${props.separationBetweenInputs}`;
});

const onSubmitSuccess = () => {
    emit('onDataSaved');
    notify(t('dataSaved'), 'positive');
};

onMounted(async () => {
    if (!props.formInitialData) {
        if (props.autoLoad && props.url) await fetch();
        originalData.value = { ...formData.value };
    } else {
        formData.value = { ...props.formInitialData };
        originalData.value = { ...props.formInitialData };
    }

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

async function fetch() {
    try {
        let { data } = await api.get(props.url, {
            params: { filter: JSON.stringify(props.filter) }
        });
        if (Array.isArray(data)) data = data[0] ?? {};
        formData.value = { ...data };
        emit('onDataFetched', formData.value);
    } catch (e) {
        throw e;
    }
}

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

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

    isLoading.value = true;
    try {
        const body = props.mapper
            ? props.mapper(formData.value, originalData.value)
            : formData.value;
        const method = props.urlCreate ? 'post' : 'patch';
        const url = props.urlCreate || props.urlUpdate || props.url;

        await Promise.resolve(
            props.saveFn ? props.saveFn(body) : api[method](url, body)
        );

        onSubmitSuccess();
        hasChanges.value = false;
    } finally {
        isLoading.value = false;
    }
}

defineExpose({
    formData,
    submitForm
});
</script>

<template>
    <QCard class="form-container" v-bind="$attrs">
        <QForm ref="formModelRef" class="form" :class="separationBetweenInputs">
            <span v-if="title" class="text-h6 text-bold">
                {{ title }}
            </span>
            <slot name="form" :data="formData" />
            <slot name="extraForm" :data="formData" />
            <component
                v-if="isHeaderMounted"
                :is="showBottomActions ? 'div' : Teleport"
                to="#actions"
                class="flex row justify-end q-gutter-x-sm"
                :class="{ 'q-mt-md': showBottomActions }"
            >
                <QBtn
                    v-if="defaultActions && showBottomActions"
                    :label="t('cancel')"
                    :icon="showBottomActions ? undefined : 'check'"
                    rounded
                    no-caps
                    flat
                    v-close-popup
                >
                    <QTooltip>{{ t('cancel') }}</QTooltip>
                </QBtn>
                <QBtn
                    v-if="defaultActions"
                    :label="t('save')"
                    :icon="showBottomActions ? undefined : 'check'"
                    rounded
                    no-caps
                    flat
                    :loading="isLoading"
                    :disabled="!showBottomActions && !hasChanges"
                    @click="submitForm()"
                    data-cy="formModelDefaultSaveButton"
                >
                    <QTooltip>{{ t('save') }}</QTooltip>
                </QBtn>
                <slot name="actions" :data="formData" />
            </component>
        </QForm>
    </QCard>
</template>

<style lang="scss" scoped>
.form-container {
    width: 100%;
    height: max-content;
    padding: 32px;
    max-width: 544px;
    display: flex;
    justify-content: center;
}

.form {
    display: flex;
    flex-direction: column;
    width: 100%;
}
</style>
