<template>
    <view
        class="ca-switch"
        :class="[{ 'is-active': isChecked, 'is-loading': loading, 'is-disabled': disabled }, `is-${shape}`]"
        :style="[switchStyle]" @click="handleClick()">
        <!-- 球的轨道 -->
        <view class="ca-switch__chute" :style="[chuteStyle]">
            <!-- 球 -->
            <view class="ca-switch__slide" :style="[slideStyle]">
                <text v-if="loading" class="ca-switch__loading" :style="[loadingStyle]" />
            </view>
            <!-- 选中文字 -->
            <text
                v-if="onText"
                class="ca-switch__text is-on"
                :style="[textStyle_, trendsTextStyle(true), textStyle]">
                {{ onText }}
            </text>
            <!-- 未选中文字 -->
            <text
                v-if="offText"
                class="ca-switch__text is-off"
                :style="[textStyle_, trendsTextStyle(false), textStyle]">
                {{ offText }}
            </text>
        </view>
    </view>
</template>

<script>
import props from './props'
import { defineComponent, computed, watch, ref, getCurrentInstance, nextTick } from 'vue'

export default defineComponent({
    name: 'CaSwitch',
    props,
    emits: ['click', 'update:modelValue', 'change', 'asyncChange'],
    setup(props, { emit }) {
        // 组件宽度
        const switchWidth = ref(0)
        const { proxy } = getCurrentInstance()
        // 球跟轨道之间的间隙
        const spaceSize = parseInt(addUnit(props.space))
        // 滑块大小
        const switchHeight = parseInt(addUnit(props.size))
        // 球大小
        const slideSize = switchHeight - 2 * spaceSize
        // 文本的宽度（要取最大值）
        const textMaxWidth = ref(0)
        // 双向绑定
        const switchValue = ref(props.offVal)

        const options = {
            immediate: true,
            deep: true
        }
        watch(() => props.modelValue, (value) => {
            switchValue.value = value
        }, options)
        watch(switchValue, (value) => {
            emit('update:modelValue', value)
            emit('change', value)
        }, options)

        // 是否激活
        const isChecked = computed(() => switchValue.value === props.onVal)
        // 文字颜色（on,off）
        const textStyle_ = computed(() => {
            return {
                fontSize: addUnit(props.textSize),
                minWidth: `${textMaxWidth.value}px`,
                zIndex: `${props.textDisplay ? 3 : 1}`
            }
        })
        // 文字颜色
        const trendsTextStyle = computed(() => {
            return (isOn) => {
                return isOn
                    ? {
                        opacity: props.textDisplay || isChecked.value ? 1 : 0,
                        color: props.textDisplay ? (isChecked.value ? props.textColor : props.textSelColor) : (isChecked.value ? props.textSelColor : props.textColor)
                    }
                    : {
                        opacity: props.textDisplay || !isChecked.value ? 1 : 0,
                        color: props.textDisplay ? (isChecked.value ? props.textSelColor : props.textColor) : (isChecked.value ? props.textSelColor : props.textColor)
                    }
            }
        })

        // switch外层样式
        const switchStyle = computed(() => {
            return {
                width: `${spaceSize > 0 ? switchWidth.value : switchWidth.value - 2 * spaceSize}px`,
                height: `${spaceSize > 0 ? switchHeight.value : switchHeight.value - 2 * spaceSize}px`,
                opacity: switchWidth.value ? props.disabled ? 0.6 : 1 : 0
            }
        })

        // 滑槽样式
        const chuteStyle = computed(() => {
            const chuteColor = isChecked.value ? props.chuteSelColor : props.chuteColor
            const chuteStyle = {
                height: `${switchHeight}px`,
                width: `${switchWidth.value}px`,
                [chuteColor.includes('linear-gradient') ? 'background' : 'backgroundColor']: chuteColor,
                borderRadius: props.shape === 'circle' ? `${switchHeight}px` : '6rpx',
                padding: spaceSize > 0 ? `${spaceSize}px` : 0,
                opacity: switchWidth.value ? 1 : 0
            }
            return chuteStyle
        })

        // 球样式
        const slideStyle = computed(() => {
            const slideStyle = {
                width: `${props.textDisplay ? textMaxWidth.value : slideSize}px`,
                borderRadius: props.shape === 'circle' ? `${Math.ceil(slideSize / 2)}px` : '4rpx',
                height: `${slideSize}px`,
                transform: `translateX(${isChecked.value ? textMaxWidth.value : spaceSize > 0 ? 0 : spaceSize}px)`,
                boxShadow: props.chuteShadow
            }
            return slideStyle
        })

        const loadingStyle = computed(() => {
            const size = addUnit(props.loadingSize)
            return {
                width: size,
                height: size,
                color: props.loadingColor || (isChecked.value ? props.chuteSelColor : props.chuteColor)
            }
        })

        watch(() => [props.onText, props.offText], () => {
            getSwitchWidth()
        }, {
            immediate: true
        })

        // 获取switch高度
        async function getSwitchWidth() {
            const slideRect = await getRect('.ca-switch__text')
            const textInfo = slideRect ? ([].concat(slideRect)).map(o => o.width) : []
            // 文字最小宽度
            textMaxWidth.value = Math.ceil(Math.max(...textInfo, slideSize * 0.8))
            if (props.textDisplay) {
                switchWidth.value = (textMaxWidth.value + spaceSize) * 2
            } else {
                switchWidth.value = slideSize + textMaxWidth.value + spaceSize * 2
            }
        }
        // 状态切换
        async function handleClick() {
            if (props.readonly || props.loading) return
            if (props.disabled) {
                props.disabledText && uni.showToast({ title: props.disabledText })
                return
            }
            const res = isChecked.value ? props.offVal : props.onVal
            if (props.async) {
                emit('asyncChange', res)
                return
            }
            switchValue.value = res
        }
        /**
         * 单位转换
         * value [String, Number] 要转换的值
         * unit [String] 默认单位
         * expUnit [String] 期望单位
        */
        function addUnit(value = 'auto', unit = 'rpx', expUnit = 'px') {
            if (!value) return `0${unit}`
            value = isNaN(value) ? value : `${value}${unit}`
            if (expUnit === 'px' && /upx|rpx/.test(value)) {
            // 所有数值（含单位）
                const strArr = value.replace(/\s+/g, ' ').split(' ')
                return strArr.map(o => {
                    const matcher = o.match(/^(-?\d+)(.*)/)
                    return `${/upx|rpx/.test(matcher[2]) ? uni.upx2px(matcher[1]) : matcher[1]}${expUnit}`
                }).join(' ')
            }
            return value
        }
        /**
          * @description: 获取节点信息
          * @param {String} selecter 要查询节点的id或者class名称
          * @param {Object} fields 需要查询返回的额外节点信息
          * @return {Promise} 返回需要查询的节点信息
          */
        function getRect(selecter, fields) {
            function formatReturn(data) {
                return Array.isArray(data) ? (data.length > 1 ? data : data[0]) : data
            }
            return new Promise((resolve, reject) => {
                nextTick(() => {
                    const querySelect = uni.createSelectorQuery()
                    // #ifndef MP-ALIPAY
                    querySelect.in(proxy)
                    // #endif
                    querySelect.selectAll(selecter)
                        .fields(
                            {
                                size: true,
                                rect: true,
                                ...fields
                            },
                            data => {
                                resolve(formatReturn(data))
                            }
                        )
                        .exec()
                })
            })
        }

        return {
            chuteStyle,
            isChecked,
            textStyle_,
            slideStyle,
            slideSize,
            switchStyle,
            loadingStyle,
            trendsTextStyle,
            handleClick
        }
    }
})
</script>

<style scoped>
.ca-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.3s linear;
  will-change: opacity, width, height;
  box-sizing: border-box;
}
.ca-switch__chute {
  transition: opacity 0.3s linear, background-color 0.3s linear;
  will-change: opacity, width, height;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
}
.ca-switch__slide {
  background-color: #fff;
  border-radius: 100%;
  transition: transform 0.2s cubic-bezier(0.65, 0.05, 0.36, 1);
  transform: translateX(0);
  will-change: transform;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
}
.ca-switch__loading{
  color: rgb(41, 121, 255);
  width: 30rpx;
  height: 30rpx;
  border-radius: 100%;
  animation: rotate 1s linear infinite;
  box-sizing: border-box;
}
.ca-switch__loading::after,.ca-switch__loading::before{
  content: '';
  color: inherit;
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  border-radius: 100%;
  border: 2px solid currentColor;
  box-sizing: border-box;
}
.ca-switch__loading::before{
  z-index: 1;
  opacity: 0.15;
}
.ca-switch__loading::after{
  z-index: 2;
  border-color: currentColor transparent transparent;
}
.ca-switch__text {
  font-size: 24rpx;
  color: #ffffff;
  position: absolute;
  z-index: 1;
  opacity: 0;
  top: 0;
  bottom: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: opacity 0.1s linear;
  white-space: nowrap;
  padding: 0 12rpx;
  line-height: 1;
  box-sizing: border-box;
}
.ca-switch__text.is-on{
  left: 0;
}
.ca-switch__text.is-off{
  right: 0;
}
/* 解决视觉偏差问题 */
.ca-switch.is-circle .is-on{
  padding: 0 12rpx 0 18rpx;
}
/* 解决视觉偏差问题 */
.ca-switch.is-circle .is-off{
  padding: 0 18rpx 0 12rpx;
}

.ca-switch.is-disabled .ca-switch__chute {
  cursor: no-drop;
  pointer-events: none;
}

@keyframes rotate{
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
      -webkit-transform: rotate(1turn);
      transform: rotate(1turn);
  }
}
</style>
