// 从 'ant-design-vue/es/_util/vue-types' 模块中导入 PropTypes，它通常用于定义 Vue 组件的属性（props）类型验证相关功能，类似于 React 中的 PropTypes，可帮助确保传入组件的属性符合预期的类型要求。
import PropTypes from 'ant-design-vue/es/_util/vue-types';
// 从当前目录下的 'TagSelectOption.jsx' 文件中导入 Option 组件（具体该组件的功能要看其内部定义，但大概率是与 TagSelect 组件相关的一个选项组件，可能用于展示单个可选择的标签选项等）。
import Option from './TagSelectOption.jsx';
// 从 '../../components/_util/util' 模块中导入 filterEmpty 函数，从函数名推测它可能用于过滤空值或者无效的元素，也许是对传入的插槽内容等数据进行预处理，去除空元素的操作。
import { filterEmpty } from '../../components/_util/util';

export default {
  // 将导入的 Option 组件注册到当前组件中，使得在该组件的模板或其他地方可以直接使用 <Option> 标签来调用这个组件，具体用法和功能取决于 Option 组件本身的定义。
  Option,
  name: 'TagSelect',
  // 定义组件的 v-model 绑定相关配置，指定了绑定的属性名为 'checked'，触发的事件名为 'change'。这意味着在使用该组件时，如果要进行双向数据绑定（类似 Vue 中常见的 v-model 用法），外部数据的变化会通过 'change' 事件传递进来更新内部的 'checked' 属性，同时内部 'checked' 属性的变化也会通过触发 'change' 事件通知外部进行相应更新。
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    // 定义 prefixCls 属性，类型为字符串，用于设置组件的类名前缀，默认值是 'ant-pro-tag-select'，通过这个前缀可以方便地对组件添加自定义样式，外部传入不同的前缀可以改变样式应用的类名基础，便于样式定制和主题切换等操作。
    prefixCls: {
      type: String,
      default: 'ant-pro-tag-select'
    },
    // 定义 defaultValue 属性，类型为通过 PropTypes.array 定义的数组类型（借助导入的 PropTypes 进行类型验证），用于设置组件的默认值（通常是初始状态下选中项等相关数据），默认值为 null，表示如果外部没有传入该属性值，初始状态下对应的值为 null，后续可能根据业务逻辑进行相应处理（比如使用空数组等其他默认情况）。
    defaultValue: {
      type: PropTypes.array,
      default: null
    },
    // 定义 value 属性，同样类型为通过 PropTypes.array 定义的数组类型，用于接收外部传入的当前值（比如选中项的数组等情况），默认值为 null，与 defaultValue 类似，但它更侧重于外部动态传入的值来控制组件的当前状态，在双向数据绑定等场景中会经常用到。
    value: {
      type: PropTypes.array,
      default: null
    },
    // 定义 expandable 属性，类型为布尔值，用于控制组件是否可展开（具体展开的功能和表现形式要看组件内部实现，可能是展开更多选项、显示隐藏部分内容等相关操作），默认值为 false，表示默认情况下组件不可展开。
    expandable: {
      type: Boolean,
      default: false
    },
    // 定义 hideCheckAll 属性，类型为布尔值，用于控制是否隐藏全选按钮（从后续代码中可以看到与全选相关的逻辑，这里通过该属性来决定是否展示全选的操作入口），默认值为 false，即默认不隐藏全选按钮。
    hideCheckAll: {
      type: Boolean,
      default: false
    }
  },
  data () {
    return {
      // 定义 expand 属性，用于记录组件当前是否处于展开状态，初始值为 false，表示默认是未展开的，后续可以通过相关逻辑（比如点击展开按钮等操作）来改变这个值。
      expand: false,
      // 定义 localCheckAll 属性，用于记录当前是否全选了所有选项，初始值为 false，表示默认没有全选，会根据选项的选中情况动态更新这个值（通过后续的相关方法来改变）。
      localCheckAll: false,
      // 调用 getItemsKey 方法，传入过滤空值后的默认插槽内容（this.$slots.default 通过 filterEmpty 函数处理后），获取并存储每个选项对应的键值对象，用于记录每个选项的选中状态等相关信息，具体结构和用法要看 getItemsKey 方法的实现细节。
      items: this.getItemsKey(filterEmpty(this.$slots.default)),
      // 根据组件传入的 value 属性或者 defaultValue 属性（如果 value 不存在则使用 defaultValue）来初始化 val 属性，如果两者都为 null，则初始化为空数组，这个 val 属性可能用于存储组件当前的实际值（比如选中项等相关数据），后续操作会基于这个值进行处理和更新。
      val: this.value || this.defaultValue || []
    }
  },
  methods: {
    /**
     * 当选项的选中状态发生改变时调用的方法，接收一个包含选中信息的对象（checked）参数，用于更新组件内部的选中状态相关数据以及全选状态的判断。
     * @param checked 包含选中信息的对象，可能包含类似 value（选项的值）、checked（是否选中的布尔值）等属性，具体结构取决于调用时传入的参数情况以及相关组件的约定。
     */
    onChange (checked) {
      // 通过 Object.keys 获取 items 对象（存储选项键值及选中状态的对象）的所有键（即每个选项对应的键），然后过滤出与传入的 checked.value 相等的键，得到对应的选项键数组（这里的逻辑是找到与当前改变选中状态的选项对应的键）。
      const key = Object.keys(this.items).filter(key => key === checked.value);
      // 根据找到的选项键，更新 items 对象中对应选项的选中状态为传入的 checked.checked 值（即更新该选项在组件内部记录的选中状态）。
      this.items[key] = checked.checked;
      // 通过 Object.values 获取 items 对象中所有值（即所有选项的选中状态值），然后查找最后一个值为 false 的索引位置（也就是查找是否还有未选中的选项），如果返回 -1，表示所有选项都被选中了，没有值为 false 的情况；否则表示还有未选中的选项。
      const bool = Object.values(this.items).lastIndexOf(false);
      if (bool === -1) {
        // 如果所有选项都被选中，将 localCheckAll 属性设置为 true，表示当前处于全选状态。
        this.localCheckAll = true;
      } else {
        // 如果还有未选中的选项，将 localCheckAll 属性设置为 false，表示当前不是全选状态。
        this.localCheckAll = false;
      }
    },
    /**
     * 当点击全选按钮时调用的方法，接收一个包含全选按钮选中状态信息的对象（checked）参数，用于更新所有选项的选中状态以及组件内部的全选状态记录。
     * @param checked 包含全选按钮选中状态的对象，通常包含 checked 属性（布尔值，表示全选按钮是否被选中），用于统一设置所有选项的选中状态。
     */
    onCheckAll (checked) {
      // 遍历 items 对象的所有键（即每个选项对应的键），将每个选项的选中状态都设置为传入的 checked.checked 值，实现全选或者取消全选所有选项的操作。
      Object.keys(this.items).forEach(v => {
        this.items[v] = checked.checked;
      });
      // 将组件内部记录全选状态的 localCheckAll 属性设置为传入的 checked.checked 值，保持全选状态的记录与实际选项的选中情况一致。
      this.localCheckAll = checked.checked;
    },
    /**
     * 用于获取每个选项对应的键值对象，根据传入的选项数组（items）创建一个新的对象，对象的键为每个选项的特定值（通常是用于唯一标识选项的值，比如选项的 ID 等），值初始化为 false（表示默认未选中状态），方便后续根据选项的键来记录和查询其选中状态等信息。
     * @param items 选项数组，数组元素可能是包含选项相关信息的虚拟 DOM 节点或者对象（具体取决于组件插槽传入内容的结构和格式）。
     * @returns {Object} 返回一个对象，对象的键为选项的特定值，值为布尔类型的选中状态（初始化为 false）。
     */
    getItemsKey (items) {
      const totalItem = {};
      items.forEach(item => {
        // 从每个选项对象（这里假设 item 是包含选项相关属性的对象，比如从插槽传入的组件选项配置对象）中获取用于唯一标识选项的值（通过 item.componentOptions.propsData.value 获取，具体的属性名 'value' 可能是组件约定的用于标识选项的关键属性），并以此值作为键，在 totalItem 对象中创建对应项，初始值设置为 false，表示默认未选中该选项。
        totalItem[item.componentOptions.propsData && item.componentOptions.propsData.value] = false;
      });
      return totalItem;
    },
    // CheckAll Button
    /**
     * 用于渲染全选按钮的方法，根据 hideCheckAll 属性判断是否需要隐藏全选按钮，如果不隐藏（hideCheckAll 为 false），则返回一个 <Option> 组件（之前导入的组件）作为全选按钮，设置其 key 属性为 'total'，选中状态为 localCheckAll 属性的值（即当前组件内部记录的全选状态），并绑定 onChange 事件为 onCheckAll 方法，按钮显示文本为 'All'；如果需要隐藏（hideCheckAll 为 true），则返回 null，表示不渲染全选按钮。
     */
    renderCheckAll () {
      return!this.hideCheckAll && (<Option key={'total'} checked={this.localCheckAll} onChange={this.onCheckAll}>All</Option>) || null;
    },
    // expandable
    /**
     * 用于处理组件可展开相关逻辑的方法，目前函数体为空，可能后续需要在这里添加代码来实现组件展开时的具体行为，比如展开显示更多选项、展开后加载更多数据等相关操作，具体功能取决于组件的设计需求。
     */
    renderExpandable () {

    },
    // render option
    /**
     * 用于渲染选项的方法，接收选项数组（items）参数，主要功能是为每个选项添加 change 事件监听器，当选项的选中状态改变时，先触发组件内部的 onChange 方法更新内部状态，再通过 $emit 触发 'change' 事件将选中状态变化通知给外部组件（实现双向数据绑定或者让外部组件能响应选项变化等功能），最后返回处理后的选项数组（包含添加了事件监听器的虚拟 DOM 节点等），用于在组件模板中进行渲染展示。
     * @param items 选项数组，同前面提到的选项相关数组，包含要渲染的各个选项的信息。
     */
    renderTags (items) {
      const listeners = {
        change: (checked) => {
          this.onChange(checked);
          this.$emit('change', checked);
        }
      };

      return items.map(vnode => {
        const options = vnode.componentOptions;
        options.listeners = listeners;
        return vnode;
      });
    }
  },
  render () {
    // 从组件实例的 $props 对象中解构出 prefixCls 属性，获取组件当前的类名前缀，用于后续构建类名相关的对象来应用样式。
    const { $props: { prefixCls } } = this;
    const classString = {
      // 创建一个对象 classString，以 `${prefixCls}` 作为键，值为 true，用于在模板中通过动态绑定类名（比如使用 :class 指令）来添加组件的基础类名，方便样式应用（后续可以根据这个基础类名添加更多修饰类名等进行样式定制）。
      [`${prefixCls}`]: true
    };
    const tagItems = filterEmpty(this.$slots.default);
    return (
      <div class={classString}>
        {this.renderCheckAll()}
        {this.renderTags(tagItems)}
      </div>
    );
  }
}
