// 从 'ant-design-vue/es/table/Table' 模块中导入名为 'T' 的对象（推测这里的 'T' 很可能是 Ant Design Vue 框架中 Table 组件相关的定义或者配置对象，后续代码中会基于它进行一些扩展和功能整合）
import T from 'ant-design-vue/es/table/Table';
// 从 'lodash.get' 模块中导入名为 'get' 的函数（lodash.get 常用于从嵌套的对象结构中安全地获取指定路径的值，在代码中可能用于获取表格行数据中特定字段的值等操作）
import get from 'lodash.get';
export default {
  data() {
    return {
      // 定义一个数组 needTotalList，用于存储需要进行总计统计的列相关信息（可能后续会根据表格数据来计算每列的合计值等，具体要看相关方法的使用）
      needTotalList: [],

      // 定义一个数组 selectedRows，用于存储当前选中的表格行数据（当表格支持行选择功能时会用到）
      selectedRows: [],
      // 定义一个数组 selectedRowKeys，用于存储当前选中的表格行对应的唯一标识（通常是每行数据的一个特定字段作为键值，方便进行行的选中状态管理等操作）
      selectedRowKeys: [],

      // 定义一个布尔值 localLoading，用于标记表格数据是否正在加载中，初始化为 false，表示初始状态下没有正在加载数据
      localLoading: false,
      // 定义一个数组 localDataSource，用于存储表格实际展示的数据，初始为空数组，后续会通过加载数据的方法来填充真实数据
      localDataSource: [],
      // 定义一个对象 localPagination，通过复制 this.pagination（可能是从父组件传入的分页相关配置对象）来初始化，用于管理表格的分页相关信息（如当前页码、每页显示数量等），在组件内部进行分页相关操作时会基于这个对象进行修改和维护
      localPagination: Object.assign({}, this.pagination)
    }
  },
  props: Object.assign({}, T.props, {
    // 定义 rowKey 属性，类型可以是字符串或者函数，用于指定表格每行数据的唯一标识字段或生成唯一标识的函数，默认值为 'key'，意味着如果外部没有传入该属性指定的标识方式，默认以每行数据中的 'key' 字段作为唯一标识
    rowKey: {
      type: [String, Function],
      default: 'key'
    },
    // 定义 data 属性，类型为函数，且是必需属性（required: true），这个函数用于获取表格的数据，外部组件需要传入一个符合要求的函数，该函数接收一些参数（比如分页、筛选、排序等条件参数）并返回相应的数据（通常是包含数据数组以及分页相关信息等的对象）
    data: {
      type: Function,
      required: true
    },
    // 定义 pageNum 属性，类型为数字，用于指定表格初始的页码，默认值为 1，表示默认显示第一页数据
    pageNum: {
      type: Number,
      default: 1
    },
    // 定义 pageSize 属性，类型为数字，用于指定每页显示的数据条数，默认值为 10，即每页默认展示 10 条数据
    pageSize: {
      type: Number,
      default: 10
    },
    // 定义 showSizeChanger 属性，类型为布尔值，用于控制是否显示每页显示数量的切换组件（例如可以切换每页显示 10 条、20 条等不同数量），默认值为 true，表示默认显示该切换组件
    showSizeChanger: {
      type: Boolean,
      default: true
    },
    // 定义 size 属性，类型为字符串，可能用于控制表格的尺寸大小等样式相关属性，默认值为 'default'，具体的尺寸样式对应关系要看 Ant Design Vue 框架中 Table 组件的实现
    size: {
      type: String,
      default: 'default'
    },
    /**
     * 定义 alert 属性，类型可以是对象或者布尔值，默认值为 null。当为对象时，可能包含一些与表格数据提示相关的配置，比如是否显示提示信息、清除提示相关的回调函数等（从注释中的示例结构来看）；当为布尔值时，可能用于简单地控制提示功能的开启或关闭
     */
    alert: {
      type: [Object, Boolean],
      default: null
    },
    // 定义 rowSelection 属性，类型为对象，默认值为 null，用于配置表格行的选择功能相关设置，比如可以设置是否支持多选、单选等选择模式以及相关的回调函数等（具体取决于 Ant Design Vue 框架的 Table 组件支持的配置项）
    rowSelection: {
      type: Object,
      default: null
    },
    /** @Deprecated 表示该属性已被弃用，建议后续不再使用此属性，这里的 showAlertInfo 用于控制是否显示某种提示信息（具体要看之前的业务逻辑，但既然被标记弃用，应该有替代的实现方式或者不再需要此功能了） */
    showAlertInfo: {
      type: Boolean,
      default: false
    },
    // 定义 showPagination 属性，类型可以是字符串或者布尔值，用于控制是否显示分页组件以及分页相关的显示策略，默认值为 'auto'，可能意味着会根据数据情况自动判断是否显示分页（比如数据条数是否超过每页显示数量等情况来决定）
    showPagination: {
      type: String | Boolean,
      default: 'auto'
    },
    /**
     * 定义 pageURI 属性，类型为布尔值，用于控制是否启用基于页面 URI（统一资源标识符，比如 URL 中的路径部分）的分页模式，默认值为 false，开启后可能会根据页面 URL 中的参数来确定当前页码等分页信息，例如不同的页码对应不同的 URL 路径，像 /users/1、/users/2 等这种形式来表示不同页码的数据访问
     */
    pageURI: {
      type: Boolean,
      default: false
    }
  }),
  watch: {
    // 监听 localPagination 对象的 current 属性变化（通常 current 表示当前页码），当页码发生变化时，如果 pageURI 为 true（即启用了基于页面 URI 的分页模式），则通过 $router.push 方法来更新页面路由，将当前页码信息添加到路由参数中，实现页面 URL 与分页页码的同步更新
    'localPagination.current'(val) {
      this.pageURI && this.$router.push({
        ...this.$route,
        name: this.$route.name,
        params: Object.assign({}, this.$route.params, {
          pageNo: val
        })
      })
    },
    // 监听 pageNum 属性变化，当 pageNum 改变时，将 localPagination 对象中的 current 属性更新为新的 pageNum 值，保持组件内部的分页相关数据的一致性
    pageNum(val) {
      Object.assign(this.localPagination, {
        current: val
      })
    },
    // 监听 pageSize 属性变化，当 pageSize 改变时，相应地更新 localPagination 对象中的 pageSize 属性值，以适配每页显示数量的改变
    pageSize(val) {
      Object.assign(this.localPagination, {
        pageSize: val
      })
    },
    // 监听 showSizeChanger 属性变化，根据其值来更新 localPagination 对象中的 showSizeChanger 属性，用于控制每页显示数量切换组件的显示隐藏状态
    showSizeChanger(val) {
      Object.assign(this.localPagination, {
        showSizeChanger: val
      })
    }
  },
  created() {
    // 从当前页面路由的参数中获取 pageNo 参数值（前提是基于页面 URI 的分页模式启用且路由参数中有该值），并将其转换为整数（如果存在的话），否则使用传入的 pageNum 属性值作为初始页码
    const {pageNo} = this.$route.params;
    const localPageNum = this.pageURI && (pageNo && parseInt(pageNo)) || this.pageNum;
    // 根据 showPagination 属性的值判断是否需要初始化 localPagination 对象，如果 showPagination 的值在 ['auto', true] 数组中（表示需要显示分页或者自动根据情况判断显示分页），则将 localPagination 对象更新为包含当前页码、每页显示数量以及每页显示数量切换组件显示状态等相关信息的对象
    this.localPagination = ['auto', true].includes(this.showPagination) && Object.assign({}, this.localPagination, {
      current: localPageNum,
      pageSize: this.pageSize,
      showSizeChanger: this.showSizeChanger
    });
    // 调用 initTotalList 方法，传入表格的列信息（this.columns，可能从父组件传入或者在组件内部其他地方定义），初始化需要进行总计统计的列信息列表，并将结果赋值给 needTotalList 属性
    this.needTotalList = this.initTotalList(this.columns);
    // 调用 loadData 方法开始加载表格数据，触发数据获取的流程
    this.loadData();
  },
  methods: {
    /**
     * 表格重新加载方法
     * 如果参数为 true，则强制刷新到第一页，重新获取数据进行展示。
     * @param Boolean bool 一个布尔类型的参数，默认值为 false，用于控制是否强制刷新到第一页。
     */
    refresh(bool = false) {
      // 如果传入的 bool 参数为 true，就重置 localPagination 对象中的当前页码（current）为 1，同时保持每页显示数量（pageSize）为组件当前设定的值（this.pageSize）。
      // 这里使用 Object.assign 进行对象属性的合并赋值，确保只更新需要改变的属性，而不影响 localPagination 中的其他属性。
      bool && (this.localPagination = Object.assign({}, {
        current: 1,
        pageSize: this.pageSize
      }));
      // 调用 loadData 方法，触发数据加载流程，根据更新后的分页设置（如果有强制刷新到第一页的操作）获取最新的数据并更新表格展示内容。
      this.loadData();
    },
    /**
     * 加载数据方法
     * 此方法用于根据传入的分页、过滤、排序等条件参数，向数据源请求数据，并处理返回的数据，更新组件内部相关的数据状态（如分页信息、表格数据源等）。
     * @param {Object} pagination 分页选项器，包含分页相关的参数，比如当前页码、每页显示数量等信息，由表格组件的分页交互操作产生。
     * @param {Object} filters 过滤条件，包含用户设置的筛选条件信息，用于从数据源筛选出符合条件的数据。
     * @param {Object} sorter 排序条件，包含用户对表格列进行排序操作产生的相关条件，比如排序字段、排序顺序等信息。
     */
    loadData(pagination, filters, sorter) {
      // 将 localLoading 属性设置为 true，表示数据开始加载，通常可以在页面上通过显示加载动画等方式提示用户正在获取数据。
      this.localLoading = true;
      // 构建一个 parameter 对象，用于整合传递给获取数据的函数（this.data）的参数。
      // 从 pagination 参数（如果存在）、组件内部的 localPagination 对象或者默认的 pageNum 和 pageSize 属性中获取当前页码和每页显示数量相关信息。
      // 同时，根据 sorter 参数（如果存在排序相关信息）获取排序字段和排序顺序信息，以及合并 filters 参数中的过滤条件信息。
      const parameter = Object.assign({
          pageNo: (pagination && pagination.current) ||
            this.localPagination.current || this.pageNum,
          pageSize: (pagination && pagination.pageSize) ||
            this.localPagination.pageSize || this.pageSize
        },
        (sorter && sorter.field && {
          sortField: sorter.field
        }) || {},
        (sorter && sorter.order && {
          sortOrder: sorter.order
        }) || {}, {
          ...filters
        }
      );
      // 调用 this.data 函数（外部传入的用于获取表格数据的函数），传入构建好的 parameter 参数，获取数据结果。返回的结果通常是一个包含数据数组以及分页相关信息（如总记录数、当前页码等）的对象（具体结构取决于数据接口的返回约定）。
      const result = this.data(parameter);
      // 以下代码用于处理异步数据获取的情况（当返回结果是一个 Promise 对象时，说明是异步获取数据，比如通过网络请求获取后端接口返回的数据），需要对返回的 Promise 进行 then 操作来处理成功获取数据后的逻辑。
      // eslint-disable-next-line：这行注释是用于告诉 ESLint 代码检查工具忽略下一行代码可能存在的相关规则检查（可能是因为下一行代码的写法不符合某些 ESLint 默认规则，但此处是有意为之）。
      if ((typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function') {
        result.then(r => {
          // 根据从接口返回的数据（r 对象）更新 localPagination 对象中的相关分页信息，包括当前页码、总记录数、每页显示数量切换组件的显示状态以及每页显示数量等，保持组件内部的分页相关数据与接口返回的数据一致。
          this.localPagination = Object.assign({}, this.localPagination, {
            current: r.pageNo, // 返回结果中的当前分页数
            total: r.totalCount, // 返回结果中的总记录数
            showSizeChanger: this.showSizeChanger,
            pageSize: (pagination && pagination.pageSize) ||
              this.localPagination.pageSize
          });
          // 如果返回的数据数组长度为 0（表示没有数据了，可能是因为删除了所有数据等情况）且当前页码大于 1，则自动将当前页码减 1，并再次调用 loadData 方法重新加载数据，避免出现空页面的情况，确保页面始终有数据展示（如果有上一页数据的话）。
          if (r.data.length === 0 && this.localPagination.current > 1) {
            this.localPagination.current--;
            this.loadData();
            return;
          }

          // 这里用于判断接口是否有返回 r.totalCount（总记录数）或 this.showPagination = false 的情况。
          // 当满足这些情况时，表示数据不满足分页大小（比如总记录数小于每页显示数量或者明确不需要分页显示），则将 localPagination 对象的 hideOnSinglePage 属性设置为 true，关闭 table 分页功能（可能是隐藏分页组件等相关操作，具体要看组件中对该属性的使用方式）。
          (!this.showPagination || !r.totalCount && this.showPagination === 'auto') && (this.localPagination.hideOnSinglePage = true);
          // 将返回结果中的数据数组赋值给 localDataSource 属性，用于更新表格实际展示的数据，使得表格展示最新获取到的数据内容。
          this.localDataSource = r.data; // 返回结果中的数组数据
          // 将 localLoading 属性设置为 false，表示数据加载完成，通常可以相应地隐藏加载提示（比如停止显示加载动画），告知用户数据已加载完毕。
          this.localLoading = false;
        })
      }
    },
    /**
     * 初始化用于总计统计的列信息列表方法
     * 遍历传入的表格列信息（columns），找出需要进行总计统计（通过列对象中的 needTotal 属性判断）的列，为这些列创建新的对象，添加一个初始值为 0 的 total 属性，用于后续统计每列数据的总和，最后返回包含这些需要统计列信息的数组。
     * @param columns 表格的列信息数组，每个元素是一个列对象，包含列的相关配置和属性。
     * @returns {Array} 返回一个数组，数组元素为需要进行总计统计的列对象，每个对象包含原列的信息以及新增的 total 属性（初始值为 0）。
     */
    initTotalList(columns) {
      const totalList = [];
      // 先判断 columns 是否存在且是一个数组类型，然后遍历 columns 数组中的每个列对象。
      columns && columns instanceof Array && columns.forEach(column => {
        // 如果列对象中有 needTotal 属性为 true，表示该列需要进行总计统计，就将该列信息复制一份，并添加一个 total 属性初始化为 0，放入 totalList 数组中。
        if (column.needTotal) {
          totalList.push({
            ...column,
            total: 0
          });
        }
      });
      return totalList;
    },
    /**
     * 用于更新已选中的列表数据 total 统计方法
     * 根据传入的已选中的行键值数组（selectedRowKeys）和选中的行数据数组（selectedRows），更新组件内部记录选中信息的属性（selectedRows 和 selectedRowKeys），然后遍历需要总计统计的列信息列表（needTotalList），计算每列在选中行中的数据总和，更新每列对应的 total 属性值。
     * @param selectedRowKeys 已选中的表格行对应的唯一标识（键值）数组，通常是每行数据的一个特定字段作为键值，方便进行行的选中状态管理等操作。
     * @param selectedRows 已选中的表格行数据数组，包含每行的详细数据内容。
     */
    updateSelect(selectedRowKeys, selectedRows) {
      // 更新组件内部记录选中行数据的属性 selectedRows，将传入的 selectedRows 参数赋值给它，保持数据的一致性。
      this.selectedRows = selectedRows;
      // 同样，更新组件内部记录选中行键值的属性 selectedRowKeys，将传入的 selectedRowKeys 参数赋值给它，用于后续操作能准确获取选中行的相关信息。
      this.selectedRowKeys = selectedRowKeys;
      const list = this.needTotalList;
      // 遍历 needTotalList 数组（之前初始化的需要总计统计的列信息列表），对于每个需要总计统计的列，执行以下操作来更新 total 属性值。
      this.needTotalList = list.map(item => {
        return {
          ...item,
          // 使用 reduce 方法遍历选中的行数据数组（selectedRows），从每行数据中获取对应列的数据（通过 lodash.get 函数安全获取，防止数据结构异常等情况）并累加起来，计算每列在选中行中的数据总和，作为该列的 total 值。
          // 如果获取到的值无法转换为数字（通过 parseInt 转换失败，返回 NaN），则将 total 值设置为 0，确保数据的合理性。
          total: selectedRows.reduce((sum, val) => {
            const total = sum + parseInt(get(val, item.dataIndex));
            return isNaN(total) ? 0 : total;
          }, 0)
        };
      });
    },
    /**
     * 清空 table 已选中项方法
     * 如果组件启用了行选择功能（通过判断 rowSelection 属性是否存在），则调用 rowSelection 的 onChange 方法传入空数组，同时调用 updateSelect 方法传入空数组来清空选中的行相关数据以及总计统计信息，实现清空选中项的功能。
     */
    clearSelected() {
      if (this.rowSelection) {
        // 调用 rowSelection 的 onChange 方法，传入空的选中行键值数组和空的选中行数据数组，通知相关的逻辑（可能是外部监听行选择变化的代码）选中项已被清空，触发相应的处理逻辑（比如更新界面上显示的选中数量等）。
        this.rowSelection.onChange([], []);
        // 调用 updateSelect 方法，传入空数组，清空组件内部记录的选中行相关数据以及总计统计信息，使组件状态回到未选中任何行的初始状态。
        this.updateSelect([], []);
      }
    },
    /**
     * 处理交给 table 使用者去处理 clear 事件时，内部选中统计同时调用的方法
     * 根据是否存在选中项以及传入的回调函数（callback），决定是否返回一个带有“清空”按钮的虚拟 DOM 元素（在 Vue 中使用类似 JSX 的语法），点击该按钮会先执行传入的 callback 函数，然后调用 clearSelected 方法清空选中项相关数据。
     * @param callback 一个回调函数，通常由 table 使用者传入，用于在点击“清空”按钮时执行额外的自定义逻辑（比如可能涉及到外部状态的更新、数据的保存等操作）。
     * @returns {*} 如果没有选中项（selectedRowKeys 长度小于等于 0），则返回 null，表示不渲染“清空”按钮；否则返回包含“清空”按钮及相应点击事件处理逻辑的虚拟 DOM 元素。
     */
    renderClear(callback) {
      if (this.selectedRowKeys.length <= 0) return null;
      return (
        <a style="margin-left: 24px" onClick={() => {
          // 点击“清空”按钮时，先执行传入的 callback 函数，让 table 使用者有机会处理自己的业务逻辑（比如保存当前选中状态等操作）。
          callback();
          // 然后调用 clearSelected 方法，清空组件内部选中项相关的数据以及统计信息，更新组件状态。
          this.clearSelected();
        }}>清空</a>
      );
    },
    /**
     * 绘制包含表格相关统计信息、清空按钮等内容的 alert 组件方法
     * 此方法用于构建一个带有统计信息和操作按钮的提示组件（a-alert），展示已选择的行数、各统计列的总计信息以及“清空”按钮（根据相关条件判断是否显示），方便用户直观了解表格的选中状态和相关统计数据，并提供操作入口来清空选中项。
     */
    renderAlert() {
      // 绘制统计列数据
      // 遍历 needTotalList 数组（包含需要进行总计统计的列信息），为每个列创建一个包含列标题和对应总计数据的 <span> 元素，用于展示每列的总计信息，最后返回一个包含这些 <span> 元素的数组（在 Vue 中使用类似 JSX 的语法构建虚拟 DOM 元素）。
      const needTotalItems = this.needTotalList.map((item) => {
        return (<span style="margin-right: 12px">
                {item.title}总计 <a
          style="font-weight: 600">{!item.customRender ? item.total : item.customRender(item.total)}</a>
            </span>)
      });

      // 绘制 清空 按钮
      // 根据 alert 属性的不同情况来决定是否显示以及如何显示“清空”按钮。
      // 如果 alert.clear 是一个布尔值且为 true，表示需要显示“清空”按钮，调用 renderClear 方法传入 clearSelected 函数（用于清空选中项）来获取对应的虚拟 DOM 元素（即带有点击事件处理逻辑的“清空”按钮）。
      // 如果 alert 属性不为 null 且 alert.clear 是一个函数，表示用户自定义了清除逻辑，同样调用 renderClear 方法传入自定义的清除函数来获取“清空”按钮对应的虚拟 DOM 元素。
      // 如果不满足以上条件，则将 clearItem 设置为 null，表示不显示“清空”按钮。
      const clearItem = (typeof this.alert.clear === 'boolean' && this.alert.clear) ? (
        this.renderClear(this.clearSelected)
      ) : (this.alert !== null && typeof this.alert.clear === 'function') ? (
        this.renderClear(this.alert.clear)
      ) : null;

      // 绘制 alert 组件
      // 使用 a-alert 组件构建一个提示框，设置显示图标（showIcon={true}），并添加底部外边距（style="margin-bottom: 16px"），在提示框的 message 插槽中放入已选择的行数信息、统计列的总计信息以及“清空”按钮（如果存在）的虚拟 DOM 元素，最后返回构建好的 a-alert 组件对应的虚拟 DOM 元素，用于在页面上展示表格相关的统计提示信息和操作按钮。
      return (
        <a-alert showIcon={true} style="margin-bottom: 16px">
          <template slot="message">
            <span style="margin-right: 12px">已选择: <a style="font-weight: 600">{this.selectedRows.length}</a></span>
            {needTotalItems}
            {clearItem}
          </template>
        </a-alert>
      );
    }
  },

  render() {
    // 创建一个空对象来存储将要传递给表格的属性
    const props = {}
    // 获取当前组件data中的所有键名
    const localKeys = Object.keys(this.$data)
    // 判断是否显示alert，条件包括alert对象存在且show属性为真，同时rowSelection的selectedRowKeys已定义，或者无条件显示alert
    const showAlert = (typeof this.alert === 'object' && this.alert !== null && this.alert.show) && typeof this.rowSelection.selectedRowKeys !== 'undefined' || this.alert

    // 遍历T.props中的每个键
    Object.keys(T.props).forEach(k => {
      // 根据T.props中的键名生成对应的本地键名（首字母大写）
      const localKey = `local${k.substring(0, 1).toUpperCase()}${k.substring(1)}`
      // 如果本地数据中存在这个键名，则将其值赋给props对象中的对应键
      if (localKeys.includes(localKey)) {
        props[k] = this[localKey]
        return props[k]
      }
      // 如果当前键是'rowSelection'
      if (k === 'rowSelection') {
        // 如果需要显示alert且rowSelection已定义
        if (showAlert && this.rowSelection) {
          // 重新绑定rowSelection事件，包括selectedRows, selectedRowKeys和onChange事件处理函数
          props[k] = {
            selectedRows: this.selectedRows,
            selectedRowKeys: this.selectedRowKeys,
            onChange: (selectedRowKeys, selectedRows) => {
              // 更新组件内部状态
              this.updateSelect(selectedRowKeys, selectedRows)
              // 如果原rowSelection中有onChange方法，则调用它
              typeof this[k].onChange !== 'undefined' && this[k].onChange(selectedRowKeys, selectedRows)
            }
          }
          return props[k]
        } else if (!this.rowSelection) {
          // 如果没有定义rowSelection，则将其置为null
          props[k] = null
          return props[k]
        }
      }
      // 如果当前组件中有这个属性，则将其添加到props对象中
      this[k] && (props[k] = this[k])
      return props[k]
    })

    // 使用JSX语法渲染<a-table>组件，并传入props和scopedSlots
    const table = (
      <a-table {...{props, scopedSlots: {...this.$scopedSlots}}} onChange={this.loadData}>
        { // 遍历组件的插槽，并渲染它们
          Object.keys(this.$slots).map(name => (<template slot={name}>{this.$slots[name]}</template>))
        }
      </a-table>
    )

    // 返回最终的渲染结果，包括可能显示的alert和表格
    return (
      <div class="table-wrapper">
        {showAlert ? this.renderAlert() : null} // 如果需要显示alert，则调用renderAlert方法渲染alert
        {table} // 渲染表格
      </div>
    )
  }
}
