<!--
  - MyTh Ahmed Faiz Copyright © 2016-2024 All rights reserved.
  - Email: mythpe@gmail.com
  - Mobile: +966590470092
  - Website: https://www.4myth.com
  - Github: https://github.com/mythpe
  -->

<script lang="ts" setup>

import { useField } from 'vee-validate'
import { MSelectProps as Props } from './models.d'
import { computed, reactive, ref, toValue } from 'vue'
import { useInputHelper } from '../../composables'
import { QField, QSelect, QSelectSlots } from 'quasar'

type P = {
  name: Props['name'];
  auto?: Props['auto'];
  col?: Props['col'];
  xs?: Props['xs'];
  sm?: Props['sm'];
  md?: Props['md'];
  lg?: Props['lg'];
  xl?: Props['xl'];
  label?: Props['label'];
  caption?: Props['caption'];
  hint?: Props['hint'];
  placeholder?: Props['placeholder'];
  help?: Props['help'];
  stackLabel?: Props['stackLabel'];
  required?: Props['required'];
  rules?: Props['rules'];
  viewMode?: Props['viewMode'];
  viewModeValue?: Props['viewModeValue'];
  autocomplete?: Props['autocomplete'];
  topLabel?: Props['topLabel'];
  behavior?: Props['behavior'];
  emitValue?: Props['emitValue'];
  useInput?: Props['useInput'];
  mapOptions?: Props['mapOptions'];
  loading?: Props['loading'];
  multiple?: Props['multiple'];
  options?: Props['options'];
  optionLabel?: Props['optionLabel'];
  optionValue?: Props['optionValue'];
  noFilter?: Props['noFilter'];
  hideEmptyList?: Props['hideEmptyList'];
  readonly?: Props['readonly'];
  useChips?: Props['useChips'];
  searchLength?: Props['searchLength'];
  axiosMode?: Props['axiosMode'];
  hideSelected?: Props['hideSelected'];
  onFilter?: Props['onFilter'];
  fieldOptions?: Props['fieldOptions'];
  clearable?: Props['clearable'];
}

const props = withDefaults(defineProps<P>(), {
  name: () => '',
  auto: undefined,
  col: undefined,
  xs: undefined,
  sm: undefined,
  md: undefined,
  lg: undefined,
  xl: undefined,
  label: undefined,
  caption: undefined,
  hint: undefined,
  placeholder: undefined,
  help: undefined,
  stackLabel: undefined,
  required: undefined,
  rules: undefined,
  viewMode: () => !1,
  viewModeValue: undefined,
  autocomplete: undefined,
  topLabel: undefined,
  behavior: undefined,
  emitValue: () => !0,
  useInput: () => !0,
  mapOptions: () => !0,
  loading: undefined,
  multiple: undefined,
  options: () => ([]),
  optionLabel: () => 'label',
  optionValue: () => 'value',
  noFilter: undefined,
  hideEmptyList: undefined,
  readonly: undefined,
  useChips: undefined,
  searchLength: () => 1,
  axiosMode: undefined,
  hideSelected: undefined,
  onFilter: undefined,
  fieldOptions: undefined,
  clearable: undefined
})
defineModel<Props['modelValue']>({ required: !1, default: undefined })

type Emits = {
  (e: 'model', value: {value: any; model: object | null}): void;
}
const emit = defineEmits<Emits>()

const helper = useInputHelper<P & Props>(() => props, 'select', () => ({ choose: !0 }))
const { hasTopLabel, getLabel, getPlaceholder, getAutocompleteAttribute, inputProps, getRules } = helper
const inputScope = useField<Props['modelValue']>(() => props.name, getRules, {
  syncVModel: !0,
  label: getLabel,
  ...toValue<any>(props.fieldOptions)
})
const { value, errorMessage, handleChange, handleBlur } = inputScope
const minLength = computed(() => parseInt(props.searchLength?.toString()))
const search = defineModel<string>('search', { required: !1, default: '' })
const getOptions = computed(() => {
  if (!props.axiosMode && !props.noFilter && search.value?.length >= minLength.value) {
    return props.options.filter((v: any) => {
      if (typeof v !== 'object') {
        return v.toString().toLowerCase().indexOf(search.value) > -1
      }
      let labelKey = 'label'
      if (props.optionLabel) {
        labelKey = typeof props.optionLabel === 'function' ? props.optionLabel(v) : props.optionLabel
      }
      let valueKey = 'value'
      if (props.optionValue) {
        valueKey = typeof props.optionValue === 'function' ? props.optionValue(v) : props.optionValue
      }
      return v[labelKey]?.toString().toLowerCase().indexOf(search.value) > -1 || v[valueKey]?.toString().toLowerCase().indexOf(search.value) > -1
    })
  }
  return props.options
})
const filterFn: Props['onFilter'] = (val: string, update, abortFn) => {
  if (props.onFilter) {
    return props.onFilter(val, update, abortFn)
  }
  update(() => void 0)
}
const onDoneOptions = () => {
  const e = input.value as QSelect
  e?.updateInputValue('', !0)
  e?.hidePopup()
}
const listeners = {
  blur: (v: any) => handleBlur(v, !0),
  'update:modelValue': (v: Props['modelValue']) => {
    handleChange(v, !!errorMessage.value)
    if (!props.emitValue) {
      emit('model', { value: v, model: v })
    } else {
      const k = typeof props.optionValue === 'function' ? props.optionValue(v) : props.optionValue
      const model = props.options.find(e => e[k] === v)
      emit('model', { value: v, model: model ?? null })
    }
  },
  inputValue: (v: string) => (search.value = v?.toString() || ''),
  filter: filterFn
}
const input = ref<InstanceType<typeof QSelect | typeof QField> | null>(null)
const scopes = reactive(inputScope)
defineExpose<typeof scopes & { input: typeof input, onDoneOptions: typeof onDoneOptions }>({ input, ...scopes, onDoneOptions })
defineOptions({ name: 'MSelect', inheritAttrs: !1 })
</script>

<template>
  <MCol
    :auto="auto"
    :class="[$attrs.class,{'m--input__required':getRules?.required!==undefined,'m--input__is-top-label':hasTopLabel,'m--input__error':!!errorMessage,'m--input__view':viewMode}]"
    :col="col"
    :lg="lg"
    :md="md"
    :name="name"
    :sm="sm"
    :xs="xs"
  >
    <slot
      name="top-input"
      v-bind="scopes"
    />
    <slot name="top-label">
      <MInputLabel
        v-if="hasTopLabel"
        :field="scopes"
      >
        <MHelpRow
          :text="help"
          tooltip
        />
      </MInputLabel>
    </slot>
    <slot name="caption">
      <div
        v-if="!!caption"
        class="m--input__caption"
      >
        {{ __(caption) }}
      </div>
    </slot>
    <component
      :is="viewMode ? QField : QSelect"
      ref="input"
      :behavior="$q.platform.is.ios === !0 ? 'dialog' : behavior"
      :emit-value="emitValue"
      :error="!!errorMessage"
      :error-message="errorMessage"
      :hide-selected="hideSelected !== undefined ? hideSelected : search.length > 0"
      :hint="__(hint)"
      :label="viewMode && hasTopLabel ? undefined : (loading ? undefined : getPlaceholder)"
      :model-value="value"
      :option-label="optionLabel"
      :option-value="optionValue"
      :options="getOptions"
      :readonly="readonly"
      v-bind="{
        ...$myth.options.select as any,
        ...( viewMode ? $myth.options.field : {} ),
        ...$attrs,
        ...(viewMode ? { stackLabel: !0 } : { stackLabel: hasTopLabel ? !1 : inputProps.stackLabel } ),
        useChips: inputProps.useChips === !0 && !multiple ? !1 : inputProps.useChips,
        multiple,
        mapOptions,
        loading,
        useInput,
        autocomplete:getAutocompleteAttribute,
        clearable: viewMode ? !1 : clearable
      }"
      v-on="listeners"
    >
      <template
        v-if="!hideEmptyList"
        #no-option
      >
        <slot name="no-option">
          <q-item :dense="inputProps.optionsDense">
            <q-item-section side>
              <template v-if="loading">
                <q-spinner color="primary" />
              </template>
              <template v-else-if="search?.length > 0">
                <q-icon
                  color="warning"
                  name="ion-ios-warning"
                />
              </template>
              <template v-else-if="!search?.length">
                <q-icon name="ion-ios-search" />
              </template>
              <template v-else>
                <q-icon name="ion-ios-information-circle-outline" />
              </template>
            </q-item-section>
            <q-item-section>
              <template v-if="loading">
                <q-skeleton
                  type="text"
                  width="70%"
                />
                <q-skeleton
                  type="text"
                  width="50%"
                />
              </template>
              <template v-else-if="!search?.length && useInput">
                {{ __('myth.select.typeToSearch') }}
              </template>
              <template v-else-if="search?.length > 0">
                {{ __('myth.select.noResult') }}
              </template>
              <template v-else>
                {{ __('myth.select.noData') }}
              </template>
            </q-item-section>
          </q-item>
        </slot>
      </template>
      <template
        v-if="loading"
        #selected
      >
        <q-skeleton width="60%" />
      </template>
      <template
        v-if="multiple"
        #before-options
      >
        <MContainer>
          <MRow class="items-center">
            <MBtn
              :label="__('done')"
              flat
              @click="onDoneOptions()"
            />
          </MRow>
        </MContainer>
      </template>

      <template
        v-for="(_,slot) in $slots as Readonly<QSelectSlots>"
        :key="slot"
        #[slot]="inputSlot"
      >
        <slot
          :name="slot"
          v-bind="inputSlot || {}"
        />
      </template>

      <template
        v-if="viewMode"
        #control
      >
        <slot name="control">
          <MInputFieldControl>
            {{ viewModeValue ?? value }}
          </MInputFieldControl>
        </slot>
      </template>
    </component>
    <slot
      name="help"
      v-bind="scopes"
    >
      <MHelpRow
        v-if="!hasTopLabel"
        :text="help"
      />
    </slot>
    <slot
      name="bottom-input"
      v-bind="scopes"
    />
  </MCol>
</template>
