<script setup>
import { ref, toRefs, computed, watch, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'src/components/FetchData.vue';
const emit = defineEmits(['update:modelValue', 'update:options']);

const $props = defineProps({
    modelValue: {
        type: [String, Number, Object],
        default: null,
    },
    options: {
        type: Array,
        default: () => [],
    },
    optionLabel: {
        type: [String],
        default: 'name',
    },
    optionValue: {
        type: String,
        default: 'id',
    },
    optionFilter: {
        type: String,
        default: null,
    },
    url: {
        type: String,
        default: '',
    },
    filterOptions: {
        type: [Array],
        default: () => [],
    },
    isClearable: {
        type: Boolean,
        default: true,
    },
    defaultFilter: {
        type: Boolean,
        default: true,
    },
    fields: {
        type: Array,
        default: null,
    },
    where: {
        type: Object,
        default: null,
    },
    sortBy: {
        type: String,
        default: null,
    },
    limit: {
        type: [Number, String],
        default: '30',
    },
    focusOnMount: {
        type: Boolean,
        default: false,
    },
    useLike: {
        type: Boolean,
        default: true,
    },
});

const { t } = useI18n();
const requiredFieldRule = (val) => val ?? t('globals.fieldRequired');

const { optionLabel, optionValue, optionFilter, options, modelValue } = toRefs($props);
const myOptions = ref([]);
const myOptionsOriginal = ref([]);
const vnSelectRef = ref();
const dataRef = ref();

const value = computed({
    get() {
        return $props.modelValue;
    },
    set(value) {
        emit('update:modelValue', value);
    },
});

function setOptions(data) {
    myOptions.value = JSON.parse(JSON.stringify(data));
    myOptionsOriginal.value = JSON.parse(JSON.stringify(data));
}
onMounted(() => {
    setOptions(options.value);
    if ($props.url && $props.modelValue) fetchFilter($props.modelValue);
});

function filter(val, options) {
    const search = val.toString().toLowerCase();

    if (!search) return options;

    return options.filter((row) => {
        if ($props.filterOptions.length) {
            return $props.filterOptions.some((prop) => {
                const propValue = String(row[prop]).toLowerCase();
                return propValue.includes(search);
            });
        }

        const id = row.id;
        const optionLabel = String(row[$props.optionLabel]).toLowerCase();

        return id == search || optionLabel.includes(search);
    });
}

async function fetchFilter(val) {
    if (!$props.url || !dataRef.value) return;

    const { fields, sortBy, limit } = $props;
    let key = optionFilter.value ?? optionLabel.value;

    if (new RegExp(/\d/g).test(val)) key = optionValue.value;

    const defaultWhere = $props.useLike
        ? { [key]: { like: `%${val}%` } }
        : { [key]: val };
    const where = { ...defaultWhere, ...$props.where };
    const fetchOptions = { where, order: sortBy, limit };
    if (fields) fetchOptions.fields = fields;
    return dataRef.value.fetch(fetchOptions);
}

async function filterHandler(val, update) {
    if (!$props.defaultFilter) return update();
    let newOptions;
    if ($props.url) {
        newOptions = await fetchFilter(val);
    } else newOptions = filter(val, myOptionsOriginal.value);
    update(
        () => {
            myOptions.value = newOptions;
        },
        (ref) => {
            if (val !== '' && ref.options.length > 0) {
                ref.setOptionIndex(-1);
                ref.moveOptionSelection(1, true);
            }
        }
    );
}

watch(options, (newValue) => {
    setOptions(newValue);
});

watch(modelValue, (newValue) => {
    if (!myOptions.value.some((option) => option[optionValue.value] == newValue))
        fetchFilter(newValue);
});

onMounted(async () => {
    if ($props.focusOnMount) setTimeout(() => vnSelectRef.value.showPopup(), 300);
});
</script>

<template>
    <FetchData
        ref="dataRef"
        :url="$props.url"
        @on-fetch="(data) => setOptions(data)"
        :where="where || { [optionValue]: value }"
        :limit="limit"
        :sort-by="sortBy"
        :fields="fields"
    />
    <QSelect
        v-model="value"
        :options="myOptions"
        :option-label="optionLabel"
        :option-value="optionValue"
        v-bind="$attrs"
        emit-value
        map-options
        use-input
        @filter="filterHandler"
        hide-selected
        fill-input
        ref="vnSelectRef"
        lazy-rules
        :class="{ required: $attrs.required }"
        :rules="$attrs.required ? [requiredFieldRule] : null"
        virtual-scroll-slice-size="options.length"
    >
        <template v-if="isClearable" #append>
            <QIcon
                v-show="value"
                name="close"
                @click.stop="value = null"
                class="cursor-pointer"
                size="xs"
            />
        </template>
        <template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
            <slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
        </template>
    </QSelect>
</template>

<style scoped lang="scss">
.q-field--outlined {
    max-width: 100%;
}
</style>
