<template>
  <view :class="`wd-picker ${disabled ? 'is-disabled' : ''} ${size ? 'is-' + size : ''}  ${cell.border.value ? 'is-border' : ''} ${alignRight ? 'is-align-right' : ''
    } ${error ? 'is-error' : ''} ${customClass}`">
    <!--文案-->
    <view class="wd-picker__field" @click="showPopup">
      <slot v-if="useDefaultSlot"></slot>
      <view v-else class="wd-picker__cell">
        <view v-if="label || useLabelSlot"
          :class="`wd-picker__label ${customLabelClass}  ${isRequired ? 'is-required' : ''}`"
          :style="labelWidth ? 'min-width:' + labelWidth + ';max-width:' + labelWidth + ';' : ''">
          <template v-if="label">{{ label }}</template>
          <slot v-else name="label"></slot>
        </view>
        <view class="wd-picker__body">
          <view class="wd-picker__value-wraper">
            <view
              :class="`wd-picker__value ${ellipsis && 'is-ellipsis'} ${customValueClass} ${showValue ? '' : 'wd-picker__placeholder'}`">
              {{ showValue ? showValue : placeholder }}
            </view>
            <wd-icon v-if="!disabled && !readonly" custom-class="wd-picker__arrow" name="arrow-right" />
          </view>
          <view v-if="errorMessage" class="wd-picker__error-message">{{ errorMessage }}</view>
        </view>
      </view>
    </view>
    <!--弹出层，picker-view 在隐藏时修改值，会触发多次change事件，从而导致所有列选中第一项，因此picker在关闭时不隐藏 -->
    <wd-popup v-model="popupShow" position="bottom" :hide-when-close="false" :close-on-click-modal="closeOnClickModal"
      :z-index="zIndex" :safe-area-inset-bottom="safeAreaInsetBottom" @close="onCancel" custom-class="wd-picker__popup">
      <view class="wd-picker__wraper">
        <!--toolBar-->
        <view class="wd-picker__toolbar" @touchmove="noop">
          <!--取消按钮-->
          <view class="wd-picker__action wd-picker__action--cancel" @click="onCancel">
            {{ cancelButtonText }}
          </view>
          <!--标题-->
          <view v-if="title" class="wd-picker__title">{{ title }}</view>
          <!--确定按钮-->
          <view :class="`wd-picker__action ${isLoading ? 'is-loading' : ''}`" @click="onConfirm">
            {{ confirmButtonText }}
          </view>
        </view>
        <!--pickerView-->
        <wd-picker-view ref="pickerViewWd" :custom-class="customViewClass" v-model="pickerValue" :columns="displayColumns"
          :loading="isLoading" :loading-color="loadingColor" :columns-height="columnsHeight" :value-key="valueKey"
          :label-key="labelKey" @change="pickerViewChange" @pickstart="onPickStart" @pickend="onPickEnd"
          :column-change="columnChange" />
      </view>
    </wd-popup>
  </view>
</template>

<script lang="ts">
export default {
  name: 'wd-picker',
  options: {
    virtualHost: true,
    addGlobalClass: true,
    styleIsolation: 'shared'
  }
}
</script>

<script lang="ts" setup>
import { getCurrentInstance, onBeforeMount, ref, watch, computed, onMounted, nextTick } from 'vue'
import { deepClone, defaultDisplayFormat, getType, isDef } from '../common/util'
import { useCell } from '../composables/useCell'
import { type ColumnItem, formatArray } from '../wd-picker-view/type'
import { FORM_KEY, type FormItemRule } from '../wd-form/types'
import { useParent } from '../composables/useParent'

interface Props {
  customClass?: string
  customLabelClass?: string
  customValueClass?: string
  customViewClass?: string
  // 选择器左侧文案
  label?: string
  // 选择器占位符
  placeholder?: string
  // 禁用
  disabled?: boolean
  // 只读
  readonly?: boolean
  loading?: boolean
  loadingColor?: string
  /* popup */
  // 弹出层标题
  title?: string
  // 取消按钮文案
  cancelButtonText?: string
  // 确认按钮文案
  confirmButtonText?: string
  // 是否必填
  required?: boolean
  size?: string
  labelWidth?: string
  useDefaultSlot?: boolean
  useLabelSlot?: boolean
  error?: boolean
  alignRight?: boolean
  // eslint-disable-next-line @typescript-eslint/ban-types
  beforeConfirm?: Function
  closeOnClickModal?: boolean
  safeAreaInsetBottom?: boolean
  ellipsis?: boolean

  // 选项总高度
  columnsHeight?: number
  // 选项对象中，value对应的 key
  valueKey?: string
  // 选项对象中，展示的文本对应的 key
  labelKey?: string
  // 初始值
  modelValue?: string | number | Array<string | number>
  // 选择器数据
  columns?: Array<string | number | ColumnItem | Array<string | number | ColumnItem>>
  // 多级联动
  // eslint-disable-next-line @typescript-eslint/ban-types
  columnChange?: Function
  // 外部展示格式化函数
  // eslint-disable-next-line @typescript-eslint/ban-types
  displayFormat?: Function
  // 自定义层级
  zIndex?: number
  prop?: string
  rules?: FormItemRule[]
}

const props = withDefaults(defineProps<Props>(), {
  modelValue: '',
  customClass: '',
  customViewClass: '',
  customLabelClass: '',
  customValueClass: '',
  // 选择器占位符
  placeholder: '请选择',
  // 禁用
  disabled: false,
  // 只读
  readonly: false,
  loading: false,
  loadingColor: '#4D80F0',
  // 取消按钮文案
  cancelButtonText: '取消',
  // 确认按钮文案
  confirmButtonText: '完成',
  // 是否必填
  required: false,
  useDefaultSlot: false,
  useLabelSlot: false,
  error: false,
  alignRight: false,
  closeOnClickModal: true,
  safeAreaInsetBottom: true,
  ellipsis: false,
  columnsHeight: 217,
  valueKey: 'value',
  labelKey: 'label',
  columns: () => [],
  zIndex: 15,
  rules: () => []
})

const pickerViewWd = ref<any>(null)
const cell = useCell()

const innerLoading = ref<boolean>(false) // 内部控制是否loading

// 弹出层是否显示
const popupShow = ref<boolean>(false)
// 选定后展示的选中项
const showValue = ref<string>('')
const pickerValue = ref<string | number | boolean | (string | number | boolean)[]>('')
const displayColumns = ref<Array<string | number | ColumnItem | Array<string | number | ColumnItem>>>([]) // 传入 pickerView 的columns
const resetColumns = ref<Array<string | number | ColumnItem | Array<string | number | ColumnItem>>>([]) // 保存之前的 columns，当取消时，将数据源回滚，避免多级联动数据源不正确的情况
const isPicking = ref<boolean>(false) // 判断pickview是否还在滑动中
const hasConfirmed = ref<boolean>(false) // 判断用户是否点击了确认按钮

const isLoading = computed(() => {
  return props.loading || innerLoading.value
})

watch(
  () => props.displayFormat,
  (fn) => {
    if (fn && getType(fn) !== 'function') {
      console.error('The type of displayFormat must be Function')
    }
    if (pickerViewWd.value && pickerViewWd.value.selectedIndex && pickerViewWd.value.selectedIndex.length !== 0) {
      if (props.modelValue) {
        setShowValue(pickerViewWd.value.getSelects())
      } else {
        showValue.value = ''
      }
    }
  },
  {
    immediate: true,
    deep: true
  }
)

watch(
  () => props.modelValue,
  (newValue) => {
    pickerValue.value = newValue
    // 获取初始选中项,并展示初始选中文案

    if (isDef(newValue)) {
      if (pickerViewWd.value && pickerViewWd.value.getSelects) {
        nextTick(() => {
          setShowValue(pickerViewWd.value!.getSelects())
        })
      } else {
        setShowValue(getSelects(props.modelValue))
      }
    } else {
      showValue.value = ''
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.columns,
  (newValue) => {
    displayColumns.value = newValue
    resetColumns.value = newValue
    // 获取初始选中项,并展示初始选中文案
    if (props.modelValue) {
      if (pickerViewWd.value && pickerViewWd.value.getSelects) {
        nextTick(() => {
          setShowValue(pickerViewWd.value!.getSelects())
        })
      } else {
        setShowValue(getSelects(props.modelValue))
      }
    } else {
      showValue.value = ''
    }
  },
  {
    deep: true,
    immediate: true
  }
)

watch(
  () => props.columnChange,
  (newValue) => {
    if (newValue && getType(newValue) !== 'function') {
      console.error('The type of columnChange must be Function')
    }
  },
  {
    deep: true,
    immediate: true
  }
)

const { parent: form } = useParent(FORM_KEY)

// 表单校验错误信息
const errorMessage = computed(() => {
  if (form && props.prop && form.errorMessages && form.errorMessages[props.prop]) {
    return form.errorMessages[props.prop]
  } else {
    return ''
  }
})

// 是否展示必填
const isRequired = computed(() => {
  let formRequired = false
  if (form && form.rules) {
    const rules = form.rules
    for (const key in rules) {
      if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
        formRequired = rules[key].some((rule: FormItemRule) => rule.required)
      }
    }
  }
  return props.required || props.rules.some((rule) => rule.required) || formRequired
})

const { proxy } = getCurrentInstance() as any

const emit = defineEmits(['confirm', 'open', 'cancel', 'update:modelValue'])

onMounted(() => {
  props.modelValue && setShowValue(getSelects(props.modelValue))
  if (props.modelValue && pickerViewWd.value && pickerViewWd.value.getSelects) {
    setShowValue(pickerViewWd.value!.getSelects())
  }
})

onBeforeMount(() => {
  displayColumns.value = deepClone(props.columns)
  resetColumns.value = deepClone(props.columns)
})

/**
 * @description 根据传入的value，picker组件获取当前cell展示值。
 * @param {String|Number|Array<String|Number|Array<any>>}value
 */
function getSelects(value) {
  const formatColumns = formatArray(props.columns, props.valueKey, props.labelKey)
  if (props.columns.length === 0) return

  // 使其默认选中首项
  if (value === '' || value === null || value === undefined || (getType(value) === 'array' && value.length === 0)) {
    value = formatColumns.map((col) => {
      return col[0][props.valueKey]
    })
  }
  const valueType = getType(value)
  const type = ['string', 'number', 'boolean', 'array']
  if (type.indexOf(valueType) === -1) return []
  /**
   * 1.单key转为Array<key>
   * 2.根据formatColumns的长度截取Array<String>，保证下面的遍历不溢出
   * 3.根据每列的key值找到选项中value为此key的下标并记录
   */
  value = value instanceof Array ? value : [value]
  value = value.slice(0, formatColumns.length)

  if (value.length === 0) {
    value = formatColumns.map(() => 0)
  }
  let selected: number[] = []
  value.forEach((target, col) => {
    let row = formatColumns[col].findIndex((row) => {
      return row[props.valueKey].toString() === target.toString()
    })
    row = row === -1 ? 0 : row
    selected.push(row)
  })

  const selects = selected.map((row, col) => formatColumns[col][row])
  // 单列选择器，则返回单项
  if (selects.length === 1) {
    return selects[0]
  }
  return selects
}

// 对外暴露方法，打开弹框
function open() {
  showPopup()
}
// 对外暴露方法，关闭弹框
function close() {
  onCancel()
}
/**
 * @description 展示popup，小程序有个bug，在picker-view弹出时设置value，会触发change事件，而且会将picker-view的value多次触发change重置为第一项
 */
function showPopup() {
  if (props.disabled || props.readonly) return

  emit('open')
  popupShow.value = true
  pickerValue.value = props.modelValue
  displayColumns.value = resetColumns.value
}
/**
 * @description 点击取消按钮触发。关闭popup，触发cancel事件。
 */
function onCancel() {
  popupShow.value = false
  emit('cancel')
}
/**
 * @description 点击确定按钮触发。展示选中值，触发cancel事件。
 */
function onConfirm() {
  if (isLoading.value) return

  // 如果当前还在滑动且未停止下来，则锁住先不确认，等滑完再自动确认，避免pickview值未更新
  if (isPicking.value) {
    hasConfirmed.value = true
    return
  }

  const { beforeConfirm } = props
  if (beforeConfirm && getType(beforeConfirm) === 'function') {
    beforeConfirm(
      pickerValue.value,
      (isPass) => {
        isPass && handleConfirm()
      },
      proxy.$.exposed
    )
  } else {
    handleConfirm()
  }
}
function handleConfirm() {
  if (isLoading.value || props.disabled) {
    popupShow.value = false
    return
  }

  const selects = pickerViewWd.value!.getSelects()
  const values = pickerViewWd.value!.getValues()
  // 获取当前的数据源，并设置给 resetColumns，用于取消时可以回退数据源
  const columns = pickerViewWd.value!.getColumnsData()
  popupShow.value = false
  resetColumns.value = deepClone(columns)
  emit('update:modelValue', values)
  setShowValue(selects)
  emit('confirm', {
    value: values,
    selectedItems: selects
  })
}
/**
 * @description 初始change事件
 * @param event
 */
function pickerViewChange({ value }) {
  pickerValue.value = value
}
/**
 * @description 设置展示值
 * @param {Array<String>} items
 */
function setShowValue(items) {
  // 避免值为空时调用自定义展示函数
  if ((items instanceof Array && !items.length) || !items) return

  const { valueKey, labelKey } = props
  showValue.value = (props.displayFormat || defaultDisplayFormat)(items, { valueKey, labelKey })
}
function noop() { }
function onPickStart() {
  isPicking.value = true
}
function onPickEnd() {
  isPicking.value = false

  if (hasConfirmed.value) {
    hasConfirmed.value = false
    onConfirm()
  }
}

/**
 * 外部设置是否loading
 * @param loading 是否loading
 */
function setLoading(loading: boolean) {
  innerLoading.value = loading
}

defineExpose({
  close,
  open,
  setLoading
})
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>
