<template>
  <div v-loading="loading" class="dg-table">
    <DgSearch
      v-bind="innerSearchProps"
      ref="dgTableSearch"
      class="dg-table__search"
      :form="toolbarData"
      :options="search.queries"
      @search="handleSearch"
    />
    <div v-if="search.actions.length || $slots.action" class="dg-table__actions">
      <el-button
        v-for="action in search.actions"
        v-bind="action.buttonProps"
        :key="action.label"
        v-monitor="action.monitor"
        @click="handleActionClick(action)"
        >{{ action.label }}</el-button
      >

      <slot name="action" />
    </div>

    <el-table
      v-bind="table.props"
      ref="DgTable"
      class="dg-table__table"
      :data="innerData"
      @selection-change="handleSelectionChange"
      @row-click="handleRowClick"
      @row-dblclick="handleRowDblclick"
      @cell-click="handleCellClick"
      v-on="$listeners"
    >
      <template v-for="column in table.columns">
        <el-table-column v-if="column.actions" :key="'action_' + column.prop" v-bind="column">
          <TableRowActions slot-scope="scope" v-bind="{ ...scope, ...rowActionProps }" :actions="column.actions" />
        </el-table-column>
        <el-table-column v-else-if="column.slot" v-bind="omit(column, 'slot')" :key="'slot_' + column.prop">
          <span slot-scope="scope">
            <slot :name="column.slot" v-bind="scope"></slot>
          </span>
        </el-table-column>
        <el-table-column
          v-else-if="column.renderCell"
          v-bind="omit(column, 'renderCell')"
          :key="'renderCell_' + column.prop"
        >
          <cell slot-scope="scope" :scope="scope" :render="column.renderCell"></cell>
        </el-table-column>
        <el-table-column
          v-else-if="column.render"
          v-bind="omit(column, ['render', 'renderPara'])"
          :key="'render_' + column.prop"
        >
          <!-- eslint-disable-next-line vue/no-v-html -->
          <span slot-scope="scope" v-html="column.render(scope.row, column.renderPara, scope)"></span>
        </el-table-column>
        <el-table-column v-else v-bind="column" :key="'default_' + column.prop" />
      </template>
    </el-table>

    <div class="dg-table__footer">
      <el-pagination
        v-if="pagination"
        class="dg-table__pagination"
        v-bind="tablePagination"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      ></el-pagination>

      <div v-if="search.bottomActions.length" class="dg-table__actions-bottom">
        <el-button
          v-for="action in search.bottomActions"
          v-bind="action.buttonProps"
          :key="action.label"
          v-monitor="action.monitor"
          :disabled="!selection.length"
          @click="handleActionClick(action)"
          >{{ action.label }}</el-button
        >
      </div>
    </div>
  </div>
</template>

<script lang="jsx">
import { EditForm } from '../../dg-edit-form'
import { deprecatedWarn, omitFormNill } from '../../utils'
import { BUILT_IN_ACTIONS, createEditorProps, formatTableItems, formatToolbarItems } from './bridge'
import TableRowActions from './row-action.vue'

const OLD_PROPS = {
  toolbarItems: Array,
  tableItems: Array,
  getDataFn: Function,
  formItems: Array,
  rules: Object,
  checkboxtion: Boolean,
  tableData: Array,
  labelPosition: String,
}

export default {
  name: 'DgTable',
  components: {
    TableRowActions,
    Cell: {
      props: { render: Function, scope: Object },
      render() {
        return this.render(this.scope.row, this.scope)
      },
    },
  },
  provide() {
    return {
      tableRoot: this,
    }
  },
  inheritAttrs: false,
  props: {
    ...OLD_PROPS,
    apiCode: { type: String, default: '' },
    queryTerms: { type: [String, Object], default: '' },
    isInitData: { type: Boolean, default: true },
    title: { type: String, default: '' },
    queries: { type: Array, default: () => [] },
    actions: { type: Array, default: () => [] },
    bottomActions: { type: Array, default: () => [] },
    columns: { type: Array, default: () => [] },
    props: { type: Object, default: () => ({}) },
    form: { type: Object, default: () => ({ items: [] }) },
    data: { type: Array, default: () => [] },
    searchProps: { type: Object, default: () => ({}) },
    rowActionProps: { type: Object, default: () => ({}) },
    pagination: { type: [Boolean, Object], default: () => ({}) },
    selectable: { type: Boolean, default: false },
    searchButtonProps: { type: Object, default: () => ({ visible: true }) },
    showLoading: { type: Boolean, default: false },
    defaultSearchData: { type: Object, default: () => ({}) },
    requestData: { type: Object, default: () => ({}) },
  },
  data() {
    return {
      loading: false,
      innerData: [],
      rawData: null,
      selection: [],
      autoFormatColumns: [],
      toolbarData: { ...this.defaultSearchData },
    }
  },
  computed: {
    innerSearchProps() {
      const { label, buttonProps } = this.search.searchButtonProps || {}

      return {
        searchText: label,
        ...this.searchProps,
        searchButtonProps: {
          visible: true,
          loading: this.loading,
          ...buttonProps,
          ...this.searchButtonProps,
        },
      }
    },
    search() {
      const {
        queries = this.queries,
        actions = this.actions,
        searchButtonProps,
        bottomActions = this.bottomActions,
      } = formatToolbarItems(this.toolbarItems)

      this.enhanceActions(actions)
      this.polyfillHandleChange(this.toolbarItems)

      return {
        queries,
        actions,
        bottomActions,
        searchButtonProps,
      }
    },
    table() {
      const columns = formatTableItems(this.tableItems) || this.columns
      this.enhanceColumn(columns)

      if (
        (this.selectable || this.checkboxtion || !!this.search.bottomActions.length) &&
        !columns.some(({ type }) => type === 'selection')
      ) {
        columns.unshift({ type: 'selection', width: 48, align: 'center' })
      }

      return { columns, props: { ...this.$attrs, height: '100%', ...this.props } }
    },
    tableAutoFormatMap() {
      return this.autoFormatColumns.reduce((map, query) => {
        const key = query.filter(Boolean).join('_')
        const value = this.$store?.state?.dimData?.[key] || []
        map[key] = Object.fromEntries(value.map(({ id, name }) => [id, name]))
        return map
      }, {})
    },
    editForm() {
      if (!this.formItems) return this.form
      deprecatedWarn('formItems/rules', 'form')
      return { items: this.formItems.map(createEditorProps), rules: this.rules }
    },
    tablePagination() {
      if (!this.pagination) return { currentPage: 1, pageSize: 15 }

      const pagination = Object.assign(
        {
          background: false,
          layout: 'total, prev, pager, next',
          pageSizes: [5, 15, 50, 100, 200],
          currentPage: 1,
          pageSize: 15,
          total: 0,
        },
        typeof this.pagination === 'object' ? this.pagination : {}
      )

      return pagination
    },
    omitToolbarData() {
      return Object.entries(this.toolbarData)
        .filter(([k, v]) => !k.startsWith('name_') && (v || v === 0))
        .reduce((m, [k, v]) => {
          m[k] = v
          return m
        }, {})
    },
  },
  watch: {
    data: {
      immediate: true,
      handler(val) {
        if (this.getDataFn || this.apiCode) return
        this.innerData = val
        this.tablePagination.total = val.length
      },
    },
    tableData: {
      immediate: true,
      handler(val) {
        if (this.getDataFn || this.apiCode) return
        this.innerData = val || []
        this.tablePagination.total = (val || []).length
      },
    },
  },
  mounted() {
    this.watchRefetchTableData()
  },
  methods: {
    handleSearch(form) {
      this.tablePagination.currentPage = 1
      this.getTableData()
      this.$emit('search', form)
    },

    async handleActionClick(action) {
      const { handler, onBefore, onAfter } = action
      if (typeof onBefore === 'function') {
        await onBefore(action, this.rawData, this.toolbarData)
      }

      this.$emit('action-click', action)

      if (typeof handler === 'function') {
        await handler(action, this.rawData, this.omitToolbarData)
      } else if (action.builtIn) {
        await this.handleBuiltInActionClick(action)
      }

      if (typeof onAfter === 'function') {
        onAfter(action, this.rawData, this.toolbarData)
      }
    },
    handleBuiltInActionClick(action) {
      switch (action.builtIn) {
        case 'down': {
          const form = this.$refs.dgTableSearch.form
          const { fileName } = action
          let downParams = new URLSearchParams(omitFormNill(form)).toString()
          downParams = `&` + downParams
          let name = fileName || ''

          if (typeof fileName === 'function') {
            name = fileName(action, this.rawData, form)
          }

          const url = this.$api.downUrl(this.apiCode, name || '', downParams)
          console.log(`通用下载链接 >>> ${url}`)
          window.open(url)
          break
        }
        case 'add':
        case 'insert':
          this.showEditForm('insert')
          break

        case 'detail':
          this.showEditForm('readonly')
          break

        case 'del':
          this.deleteRows(this.selection)
          break

        default:
          console.error(
            `<dg-table>: builtin action [${action.builtIn}] does not provide handler. Please set the \`action.handler\``
          )
      }
    },
    handleSizeChange(pageSize) {
      this.tablePagination.pageSize = pageSize
      this.tablePagination.currentPage = 1
      this.getTableData()
      this.$emit('size-change', ...arguments)
    },
    handleCurrentChange(currentPage) {
      this.tablePagination.currentPage = currentPage
      this.getTableData()
      this.$emit('current-change', ...arguments)
    },
    handleSelectionChange(selection) {
      this.selection = selection
      this.$emit('selectionChange', selection)
    },

    showEditForm(type, form = {}) {
      // if (!this.apiCode) {
      //   console.error('[dg-table] 未配置 apiCode 不支持内置表单编辑')
      //   return
      // }

      if (!(this.editForm.items && this.editForm.items.length)) {
        console.error('[dg-table] 内置表单编辑，请传入 prop [form] 或 [formItems] ')
        return
      }

      const onSuccess = (data, type, formInstance) => {
        this.editForm.onSuccess && this.editForm.onSuccess(data, type, formInstance)
        this.getTableData()
      }

      EditForm({
        form: JSON.parse(JSON.stringify(form)),
        apiCode: this.apiCode,
        type,
        labelPosition: this.labelPosition,
        ...this.editForm,
        onSuccess,
      })
    },
    async deleteRows(rows) {
      if (!this.apiCode) {
        console.error('[dg-table] 未配置 apiCode 不支持内置表单删除')
        return
      }

      try {
        await this.$confirm('此操作将永久删除当前选中记录, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'error',
        })
      } catch (error) {
        this.$message.info('已取消删除')
        return
      }

      if (this.showLoading) {
        this.loading = true
      }
      const res = await this.$api.DgJsonStore({
        apiCode: this.apiCode,
        params: {
          command: 'delete',
          records: JSON.stringify(rows),
        },
      })
      if (res.success) {
        this.$message.success('操作数据成功')
        this.getTableData()
      } else {
        this.$message.error('操作数据失败')
        this.loading = false
      }
    },

    watchRefetchTableData() {
      this.$watch(
        () => [this.apiCode, this.queryTerms, this.getDataFn],
        function () {
          this.tablePagination.currentPage = 1
          this.getTableData()
        },
        { immediate: this.isInitData }
      )
    },
    setTableData(response) {
      const { count, root } = response
      this.tablePagination.total = count
      this.innerData = root
      this.rawData = response
      this.$emit('update:tableData', root)
    },
    async getTableData() {
      try {
        const { currentPage, pageSize } = this.tablePagination
        if (this.showLoading) {
          this.loading = true
        }
        if (this.getDataFn) {
          const response = await this.getDataFn(this.omitToolbarData, pageSize, currentPage)
          this.setTableData(response)
        } else if (this.apiCode) {
          let queryTermsObj = {}
          if (this.queryTerms) {
            queryTermsObj =
              typeof this.queryTerms === 'string'
                ? this.queryTerms.split('&').reduce((obj, item) => {
                    if (item) {
                      const [key, value] = item.split('=')
                      if (key) {
                        obj[key] = value
                      }
                    }
                    return obj
                  }, {})
                : this.queryTerms
          }

          const response = await this.$api.DgJsonStore({
            // apiCode: `${this.apiCode}&${this.queryTerms}`.replace(/&&/g, '&').replace(/&$/, ''),
            apiCode: this.apiCode,
            params: {
              command: 'init',
              ...this.omitToolbarData,
              limit: pageSize,
              start: (currentPage - 1) * pageSize,
              ...queryTermsObj,
            },
            ...this.requestData,
          })
          this.setTableData(response)
        }
      } catch (e) {
        //
      } finally {
        this.loading = false
      }
    },

    omit(obj, keys = []) {
      const res = { ...obj }
      ;[].concat(keys).forEach((key) => {
        delete res[key]
      })
      return res
    },
    enhanceColumn(columns) {
      const autoFormatColumns = []
      columns.forEach((column) => {
        if (!column.renderHeader && column.explain) {
          column.renderHeader = () => {
            return (
              <span>
                {column.label}
                <dg-tooltip {...column.explain}>
                  <i class="el-icon-question el-icon--right" />
                </dg-tooltip>
              </span>
            )
          }
        }

        if (column.dimCode || column.apiCode) {
          const { dimCode, apiCode, filterVal, queryTerms } = column
          const key = [dimCode, apiCode, filterVal, queryTerms].filter(Boolean).join('_')
          this.$store?.dispatch('GetDimdataByCode', { dimCode, apiCode, filterVal, queryTerms })
          autoFormatColumns.push([dimCode, apiCode, filterVal, queryTerms])
          column.formatter = (r, c, value) => {
            const values = value ? value.split(',').filter(Boolean) : []
            return values.map((v) => (this.tableAutoFormatMap[key] || {})[v]).join(',') || value
          }
        }
      })

      this.autoFormatColumns = autoFormatColumns

      return columns
    },
    enhanceActions(actions) {
      actions.forEach((action) => {
        if (action.builtIn) {
          const { label, ...props } = BUILT_IN_ACTIONS[action.builtIn]
          action.buttonProps = { ...props, ...action.buttonProps }
          action.label = action.label || label
        }
      })
    },
    polyfillHandleChange(queries) {
      ;(this.lastWatch || []).forEach((stop) => stop())
      if (queries && queries.length) {
        this.lastWatch = queries.reduce((watches, item) => {
          const { value, handleChange } = item
          if (handleChange) {
            watches.push(
              this.$watch(`toolbarData.${value}`, () => {
                handleChange(null, item, this.toolbarData, this.toolbarData[value])
              })
            )
          }

          return watches
        }, [])
      }
    },

    /**
     * @deprecated
     */
    getData() {
      this.getTableData()
    },
    handleRowClick(row, column, event) {
      this.$emit('rowClick', row, column, event)
    },
    handleRowDblclick(row, column, event) {
      this.$emit('rowDblclick', row, column, event)
    },
    handleCellClick(row, column, cell, event) {
      this.$emit('cellClick', row, column, cell, event)
    },
  },
}
</script>
