<script setup lang="ts">
import {computed, ref, watchEffect} from 'vue';

interface Option {
  label: string // 选项名
  value: string | number // 选项值
  disabled?: boolean // 是否禁用选项
}

interface Props {
  options?: Option[] // 复选框选项数据
  disabled?: boolean // 是否禁用
  vertical?: boolean // 是否垂直排列
  value?: (string | number)[] // (v-model) 当前选中的值，配合 options 使用
  gap?: number | number[] // 多个复选框之间的间距；垂直排列时为垂直间距，单位 px；数组间距用于水平排列折行时：[水平间距, 垂直间距]
  width?: string | number // 复选区域最大宽度，超出后折行，单位 px
  height?: string | number // 复选区域最大高度，超出后滚动，单位 px
  indeterminate?: boolean // 全选时的样式控制
  checked?: boolean // (v-model) 当前是否选中
}

const props = withDefaults(defineProps<Props>(), {
  options: () => [],
  disabled: false,
  vertical: false,
  value: () => [],
  gap: 8,
  width: 'auto',
  height: 'auto',
  indeterminate: false,
  checked: false
});
const checkboxChecked = ref<boolean>();
const optionsCheckedValue = ref<any[]>([]);
const emits = defineEmits(['update:value', 'update:checked', 'change']);
const optionsAmount = computed(() => {
  // 选项总数
  return props.options.length;
});
const maxWidth = computed(() => {
  // 复选区域最大宽度
  if (typeof props.width === 'number') {
    return `${props.width}px`;
  } else {
    return props.width;
  }
});
const maxHeight = computed(() => {
  // 复选区域最大高度
  if (typeof props.height === 'number') {
    return `${props.height}px`;
  } else {
    return props.height;
  }
});
const gapValue = computed(() => {
  if (!props.vertical && Array.isArray(props.gap)) {
    return `${props.gap[1]}px ${props.gap[0]}px`;
  }
  return `${props.gap}px`;
});
watchEffect(() => {
  checkboxChecked.value = props.checked;
});
watchEffect(() => {
  optionsCheckedValue.value = props.value;
});

function checkDisabled(disabled: boolean | undefined) {
  if (disabled === undefined) {
    return props.disabled;
  } else {
    return disabled;
  }
}

function onClick(value: string | number) {
  if (optionsCheckedValue.value.includes(value)) {
    // 已选中
    const newVal = optionsCheckedValue.value.filter((target) => target !== value);
    optionsCheckedValue.value = newVal;
    emits('update:value', newVal);
    emits('change', newVal);
  } else {
    // 未选中
    const newVal = [...optionsCheckedValue.value, value];
    optionsCheckedValue.value = newVal;
    emits('update:value', newVal);
    emits('change', newVal);
  }
}

function onChecked() {
  checkboxChecked.value = !checkboxChecked.value;
  emits('update:checked', checkboxChecked.value);
  emits('change', checkboxChecked.value);
}
</script>
<template>
  <div
      class="m-checkbox"
      :class="{ 'checkbox-vertical': vertical }"
      :style="`--checkbox-gap: ${gapValue}; --checkbox-max-width: ${maxWidth}; --checkbox-max-height: ${maxHeight};`"
  >
    <template v-if="optionsAmount">
      <div class="m-checkbox-wrap" v-for="(option, index) in options" :key="index">
        <div
            class="m-checkbox-box"
            :class="{ 'checkbox-disabled': checkDisabled(option.disabled) }"
            @click="checkDisabled(option.disabled) ? () => false : onClick(option.value)"
        >
          <span class="checkbox-box" :class="{ 'checkbox-checked': optionsCheckedValue.includes(option.value) }"></span>
          <span class="checkbox-label">
            <slot :label="option.label">{{ option.label }}</slot>
          </span>
        </div>
      </div>
    </template>
    <div v-else class="m-checkbox-wrap">
      <div
          class="m-checkbox-box"
          :class="{ 'checkbox-disabled': disabled }"
          @click="disabled ? () => false : onChecked()"
      >
        <span
            class="checkbox-box"
            :class="{
            'checkbox-checked': checkboxChecked && !indeterminate,
            'checkbox-indeterminate': indeterminate
          }"
        ></span>
        <span class="checkbox-label">
          <slot></slot>
        </span>
      </div>
    </div>
  </div>
</template>
<style lang="less" scoped>
.m-checkbox {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--checkbox-gap);
  color: rgba(0, 0, 0, 0.88);
  font-size: 14px;
  line-height: 1;
  max-width: var(--checkbox-max-width);
  max-height: var(--checkbox-max-height);
  overflow: auto;

  .m-checkbox-wrap {
    .m-checkbox-box {
      display: inline-flex;
      align-items: flex-start;
      cursor: pointer;

      &:not(.checkbox-disabled):hover {
        .checkbox-box {
          border-color: @themeColor;
        }
      }

      .checkbox-box {
        /*
          如果所有项目的flex-shrink属性都为1，当空间不足时，都将等比例缩小
          如果一个项目的flex-shrink属性为0，其他项目都为1，则空间不足时，前者不缩小。
        */
        flex-shrink: 0; // 默认 1.即空间不足时，项目将缩小
        position: relative;
        top: 3px;
        width: 16px;
        height: 16px;
        background: transparent;
        border: 1px solid #d9d9d9;
        border-radius: 4px;
        transition: all 0.3s;

        &::after {
          box-sizing: border-box;
          position: absolute;
          top: 50%;
          left: 21.5%;
          display: table;
          width: 5.7142857142857135px;
          height: 9.142857142857142px;
          border: 2px solid #fff;
          border-top: 0;
          border-left: 0;
          transform: rotate(45deg) scale(0) translate(-50%, -50%);
          opacity: 0;
          content: '';
          transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6),
          opacity 0.1s;
        }
      }

      .checkbox-checked {
        background-color: @themeColor;
        border-color: @themeColor;

        &::after {
          opacity: 1;
          transform: rotate(45deg) scale(1) translate(-50%, -50%);
          transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s;
        }
      }

      .checkbox-indeterminate {
        &::after {
          top: 50%;
          left: 50%;
          width: 8px;
          height: 8px;
          background-color: @themeColor;
          border: 0;
          transform: translate(-50%, -50%) scale(1);
          opacity: 1;
        }
      }

      .checkbox-label {
        word-break: break-all;
        padding: 0 8px;
        line-height: 1.5714285714285714;
      }
    }

    .checkbox-disabled {
      color: rgba(0, 0, 0, 0.25);
      cursor: not-allowed;

      .checkbox-box {
        border-color: #d9d9d9;
        background-color: rgba(0, 0, 0, 0.04);

        &::after {
          border-color: rgba(0, 0, 0, 0.25);
          animation-name: none;
        }
      }
    }
  }
}

.checkbox-vertical {
  flex-direction: column;
  flex-wrap: nowrap;
}
</style>
