<!-- @Author: xiaodongyu -->
<!-- @date 2019-11-20 10:18:31 -->
<!-- @Last Modified by: xiaodongyu -->
<!-- @Last Modified time: 2023-03-03 13:27:41 -->

<template>
    <a-config-provider
        v-if="records"
        :render-empty="renderEmpty"
    >
        <div :class="['yqg-simple-table', renderEmpty ? 'custom-empty-table' : '']">
            <div class="yst-header yqg-clear-float">
                <a-input-search
                    v-if="enableClientSearch"
                    :placeholder="isSearchTableRendering ? $t('table.searchLoading') : $t('table.searchPlaceholder')"
                    class="search-input"
                    :disabled="isSearchTableRendering"
                    :loading="isSearchTableRendering"
                    @change="onSearch"
                />

                <slot name="yst-extra-header" />

                <a-button
                    v-if="$listeners['export-current-page']"
                    style="float: right;"
                    size="small"
                    @click="onExportCurrentPage"
                >
                    {{ $t('table.exportCurrentPage') }}
                </a-button>
            </div>
            <p
                v-if="options.enableClientSort && $listeners.change"
                class="client-sort-tip"
            >
                *{{ $t('table.clientSortTip') }}
            </p>
            <field-checkbox
                v-if="enableSelectColumn"
                v-model="checkedColumns"
                :enum-type="columnsMap"
                :def="{}"
                class="mb10"
                @change="onCheckedColumnsChange"
            />
            <a-table
                ref="mainTable"
                :data-source="renderRecords"
                v-bind="tableOptions"
                @change="onChange"
                @expand="onExpand"
                @expandedRowsChange="onExpandedRowsChange"
            />
            <yqg-infinite-pagination
                v-if="isPaginationInfiniteVisible"
                :pagination="pagination"
                :data-size="(records && records.length) || 0"
                @change="onChange"
            />
            <!-- 用来获取表格内真实数据的全数据表格 -->
            <a-table
                v-if="enableClientSearch && isSearchTableRendering"
                v-show="false"
                ref="innerTextTable"
                :data-source="records.slice(searchSliceStart, searchSliceEnd)"
                v-bind="tableOptions"
                :pagination="false"
            />
        </div>
    </a-config-provider>
</template>

<script type="text/babel">
    import {DEFAULT_PAGINATION, DEFAULT_TABLE_OPTIONS, ClientSorterMap} from '../constant/table';
    import collectSlots from '../mixin/collect-slots';
    import staticProps from '../mixin/static-props';
    import {setStorage, getStorage} from '../util/local-storage';
    import {evalProp, pickValue} from '../util/object';
    import {camelCaseToUnderscore} from '../util/tool';

    export default {
        name: 'YqgSimpleTable',

        mixins: [staticProps, collectSlots],

        inject: {
            timezoneKey: {default: ''},
            isValidDef: {default: () => x => x}
        },

        props: {
            ctx: {
                type: Object,
                default: () => null
            },
            options: {
                type: Object,
                default: () => ({})
            },
            records: {
                type: Array,
                default: null
            },
            pagination: {
                type: [Boolean, Object],
                default: () => ({})
            },
            simpleEmpty: {
                type: Boolean,
                default: false
            },
            defaultPagination: {
                type: Object,
                default: () => DEFAULT_PAGINATION
            },
            fontSize: {
                type: Number,
                default: 13
            },
            cellHorizontalPadding: {
                type: Number,
                default: 20
            },
            paginationInfinite: {
                type: Boolean,
                default: false
            },
            rowInnerTextDelay: {
                type: Number,
                default: 1e3
            },
            // 表内搜索全数据表格切片步长
            searchSliceStep: {
                type: Number,
                default: 10
            },
            // 额外自定义的表内搜索方法
            additionalSearchFilter: {
                type: Function,
                default: null
            }
        },

        data() {
            const {options: {colDefs = []}} = this;

            return {
                searchText: '',
                visibleMap: colDefs.filter(def => def.child).reduce((map, {field: key, child}) => {
                    map[key] = !!child.defaultVisible;

                    return map;
                }, {}),
                checkedColumns: [],
                rowInnerTextList: null,
                searchSliceStart: 0
            };
        },

        computed: {
            isPaginationInfiniteVisible() {
                const {paginationInfinite, pagination: {pageNo}, records} = this;

                if (!paginationInfinite) return false;

                return records?.length || (pageNo > 1);
            },

            columnsMap() {
                const {validDefColumns: cols} = this;

                return cols.reduce((acc, {field, label, labelParam = []}) => ({
                    ...acc,
                    [field]: this.$t(label, labelParam)
                }), {});
            },

            enableSelectColumn() {
                return this.options.enableSelectColumn;
            },

            enableClientSearch() {
                const {options: {search, simpleSearch}, records} = this;

                return (search || simpleSearch) && records?.length;
            },
            // 表内搜索表格是否正在渲染
            isSearchTableRendering() {
                const {records, searchSliceStart, options: {simpleSearch}} = this;

                return records.length && searchSliceStart < records.length && !simpleSearch;
            },
            // 表内搜索全数据切片尾标
            searchSliceEnd() {
                return this.searchSliceStart + this.searchSliceStep;
            },

            validDefColumns() {
                const {options: {colDefs}} = this;
                const isValidDef = this.$app?.isValidDef || this.isValidDef;
                const realColumns = this.getColDefs(colDefs);

                return realColumns.filter(def => def && isValidDef(def));
            },

            activeDefColumns() {
                const {validDefColumns, checkedColumns, enableSelectColumn} = this;
                if (!enableSelectColumn) return validDefColumns;

                return validDefColumns.filter(def => checkedColumns.includes(def.field));
            },

            tableOptions() {
                const {
                    options: {scroll = DEFAULT_TABLE_OPTIONS.scroll, ...rest},
                    records,
                    simpleEmpty,
                    defaultPagination,
                    activeDefColumns,
                    paginationInfinite,
                    $scopedSlots: {footer}
                } = this;
                const columns = activeDefColumns.map(this.getColumnOptions);

                let {pagination} = this;
                if (paginationInfinite) pagination = false;
                if (pagination) {
                    // 如果有传入pagination的话, 表示服务端分页, 否则表示客户端分页
                    // 客户端分页不传current, 让<pagiation />非受控
                    if (pagination.pageNo) pagination.current = pagination.pageNo;
                    pagination = {
                        ...defaultPagination,
                        showTotal: (total, range) => `${[
                            range.join('-'),
                            this.$t('pagination.line'),
                            ', ',
                            this.$t('pagination.total'),
                            total,
                            this.$t('pagination.line')
                        ].join('')}`,
                        ...pagination
                    };
                    /*
                    const {total = 0, pageSizeOptions} = pagination;
                    pagination = Math.max(total, 0 || (records && records.length)) > Math.min(...pageSizeOptions)
                        && pagination;
                    */
                }

                return {
                    ...DEFAULT_TABLE_OPTIONS,
                    showHeader: !simpleEmpty || !!(records && records.length),
                    columns,
                    pagination,
                    scroll,
                    ...rest,
                    ...(footer ? {footer} : {})
                };
            },

            renderRecords() {
                const {searchText, records, rowInnerTextList, additionalSearchFilter} = this;

                if (!searchText || !rowInnerTextList?.length) {
                    return this.records;
                }

                const lowerSearchText = searchText.toLowerCase();

                return records
                    .filter((record, index) => rowInnerTextList[index]?.some(
                        tdText => {
                            if (['', undefined, null].includes(tdText)) return false;

                            return additionalSearchFilter?.(`${tdText}`, searchText)
                                // 支持驼峰搜索
                                || `${tdText}`.includes(camelCaseToUnderscore(searchText))
                                // 支持大小写搜索
                                || `${tdText}`.toLowerCase().includes(lowerSearchText);
                        }
                    ));
            },

            renderEmpty() {
                const {simpleEmpty, $slots} = this;
                let emptyVNode;
                if (simpleEmpty) emptyVNode = this.$t('common.noData');
                if ($slots.renderEmpty) emptyVNode = $slots.renderEmpty;

                return emptyVNode && (() => <div className="custom-empty-view">{emptyVNode}</div>);
            }
        },

        watch: {
            records: {
                handler: 'getRowInnerTextList',
                immediate: true
            },
        },

        mounted() {
            const {validDefColumns, enableSelectColumn, options: {tableCacheKey}} = this;
            if (enableSelectColumn) {
                this.checkedColumns = validDefColumns.map(({field}) => field);
                // 走localstorage缓存选中的表头
                if (tableCacheKey) {
                    const selectColumnsStr = getStorage(tableCacheKey);
                    // 之前有缓存过
                    if (selectColumnsStr) {
                        const selectColumns = JSON.parse(selectColumnsStr);
                        this.checkedColumns = selectColumns;
                    }
                }
            }
        },

        beforeDestroy() {
            clearTimeout(this.rowInnerTextListTimeout);
        },

        methods: {
            getColDefs(colDefs) {
                const {ctx} = this;

                return colDefs.filter(def => !evalProp(def.colHide, {ctx: ctx ?? this.$parent})).flatMap(def => {
                    const {field: key, child, children} = def;
                    const tempChildren = children?.length ? this.getColDefs(children) : null;

                    if (child && child.colDefs && child.colDefs.length) {
                        if (this.visibleMap[key]) {
                            return [{...def, children: tempChildren}, ...child.colDefs];
                        }
                    }

                    return {...def, children: tempChildren};
                });
            },

            getColumnOptions(def) {
                const {
                    options: {enableClientSort, scroll = DEFAULT_TABLE_OPTIONS.scroll},
                    records,
                    $scopedSlots,
                    ctx
                } = this;
                // children：表头合并；  child：表头伸缩（折叠）列；
                const {field: key, label, labelParam = [], column = {}, child, children, rowSpan, colSpan} = def;
                const title = typeof label === 'string' ? this.$t(label, labelParam) : label;

                return {
                    key,
                    dataIndex: key,
                    children: children ? children.map(this.getColumnOptions) : null,
                    title: () => {
                        if ($scopedSlots.title) return $scopedSlots.title({title, def});
                        if (child) {
                            const visible = this.visibleMap[key];

                            return (
                                <div>
                                    {title}
                                    <a-icon
                                        type={!visible ? 'custom-plus' : 'custom-minus'}
                                        class="ml10 yqg-text-primary"
                                        vOn:click_stop={() => this.changeVisible({key, visible: !visible})}
                                    />
                                </div>
                            );
                        }

                        return title;
                    },
                    customRender: (text, record, index) => {
                        const props = {
                            def,
                            value: text,
                            record,
                            values: record,
                            index,
                            defaultText: '/',
                            ctx: ctx ?? this.$parent
                        };

                        let cell = $scopedSlots[key]?.(props);
                        if (!cell) {
                            const scopedSlots = this.genSlots({def, parentIndex: index});
                            const comp = def.staticComp || 'def-value';
                            cell = <comp {...{props: this.getCompProps(props), scopedSlots}} />;
                        }

                        if (rowSpan || colSpan) {
                            const attrs = {
                                rowSpan: evalProp(rowSpan, props),
                                colSpan: evalProp(colSpan, props)
                            };

                            return {children: cell, attrs};
                        }

                        return cell;
                    },
                    ...(scroll.y ? {width: this.getColumnWidth({def, column, records})} : {}),
                    ...column,
                    ...(enableClientSort || def.clientSort ? {
                        sorter: this.getClientSorter(def)
                    } : {})
                };
            },

            getClientSorter(def) {
                const {field, type, filter} = def;
                let {column: {sorter} = {}} = def;
                if (!sorter) {
                    sorter = 'string';
                    if (/^(number|date)$/.test(type) || /^(date(Time)?$|numberCommas)/.test(filter)) {
                        sorter = 'number';
                    }
                }

                return sorter.constructor === String ? ClientSorterMap[sorter](field) : sorter;
            },

            getColumnWidth({def, records}) {
                const {label, field, column, filter} = def;
                if (column?.width) return column.width;

                const {$t, fontSize, cellHorizontalPadding} = this;
                const maxLen = Math.max($t(label).length, ...records.map(record => {
                    let val = pickValue(record, field);
                    val = !val && val !== 0 ? '' : val;
                    if (filter) {
                        return `${this.$options.filters[filter](val)}`.length * 0.6;
                    }

                    return `${val}`.length;
                }));

                return maxLen * fontSize + cellHorizontalPadding;
            },

            onChange(pagination, filters, sorter) {
                const {pageNo, current, pageSize} = pagination;
                if (pageNo === current && pageSize === this.pagination.pageSize && this.options.enableClientSort) return;

                pagination.pageNo = current;
                this.$emit('change', pagination, filters, sorter);
            },

            onExpand(...args) {
                this.$emit('expand', ...args);
            },

            onExpandedRowsChange(...args) {
                this.$emit('expandedRowsChange', ...args);
            },

            onSearch({target: {value}}) {
                this.searchText = value && value.trim();

                this.$emit('search', this.searchText);
            },

            changeVisible({key, visible}) {
                this.visibleMap = {
                    ...this.visibleMap,
                    [key]: visible
                };
            },

            onExportCurrentPage() {
                const el = this.$refs.mainTable.$el;
                const header = [].map.call(el.querySelectorAll('th'), item => item.textContent);
                const data = [].map.call(el.querySelectorAll('.ant-table-body tbody tr'), row => {
                    const tdList = row.children;

                    return [].map.call(tdList, td => td.textContent);
                });
                this.$emit('export-current-page', {header, data});
            },

            getRowInnerTextList() {
                this.rowInnerTextList = null;
                this.searchSliceStart = 0;
                if (!this.enableClientSearch) return;

                const {options: {simpleSearch}} = this; //

                if (simpleSearch) {
                    this.rowInnerTextList = this.records.map(record => {
                        const innerList = [];

                        this.activeDefColumns.forEach(({field}) => {
                            const innerValue = pickValue(record, field);
                            if (!['', undefined, null].includes(innerValue)) {
                                innerList.push(`${innerValue}`);
                            }
                        });

                        return innerList;
                    });

                    return;
                }

                this.rowInnerTextListTimeout = setTimeout(() => {
                    const loop = () => {
                        if (this.isSearchTableRendering) {
                            setTimeout(() => {
                                const el = this.$refs.innerTextTable?.$el;
                                if (!el) return;

                                const {map} = [];
                                const temp = map.call(el.querySelectorAll('.ant-table-body tbody tr'), rowEl => {
                                    return map.call(rowEl.querySelectorAll('td'), tdEl => tdEl.textContent?.toLowerCase());
                                });

                                this.rowInnerTextList = this.rowInnerTextList?.concat(temp) || temp;
                                this.searchSliceStart += this.searchSliceStep;
                                loop();
                            });
                        }
                    };

                    loop();
                }, this.rowInnerTextDelay);
            },

            onCheckedColumnsChange(columns) {
                const {options: {tableCacheKey}} = this;
                if (tableCacheKey) setStorage(tableCacheKey, JSON.stringify(columns));
            }
        }
    };
</script>
<style lang="scss" rel="stylesheet/scss">
    .yqg-simple-table {
        margin-top: 20px;

        .yst-header {
            margin-bottom: 10px;
        }

        .search-input {
            width: 200px;
        }

        .client-sort-tip {
            margin-bottom: 10px;
            font-size: 12px;
            color: #999;
        }

        &.custom-empty-table {
            .ant-table-placeholder {
                padding: 0;
                text-align: left;
                background-color: transparent;
                border: none !important;
            }
        }

        .ant-table {
            &-thead,
            &-tbody {
                tr {
                    th,
                    td {
                        padding: 10px;
                        white-space: nowrap;
                        text-align: center;
                        color: rgba(0, 0, 0, 95%);

                        pre {
                            display: inline-block;
                            min-width: 100px;
                            max-width: 300px;
                            white-space: pre-wrap;
                            word-break: break-word;
                            overflow: unset;
                        }
                    }
                }
            }

            &-pagination {
                &.ant-pagination {
                    float: none;
                }
            }
        }

        &.yqg-common-table {
            .ant-table-empty {
                .ant-table-thead {
                    display: none;
                }

                .ant-table-placeholder {
                    border-top: 1px solid #e8e8e8;
                }
            }
        }
    }
</style>
