<script lang="ts" setup>
import {
  computed,
  defineComponent,
  nextTick,
  onBeforeUnmount,
  onMounted,
  onUpdated,
  ref,
} from 'vue';

import { Pagination, Table } from 'antdv-next';

defineOptions({ name: 'AdminConfigTable' });

const props = withDefaults(
  defineProps<{
    autoHeight?: boolean;
    columns: Array<Record<string, any>>;
    current: number;
    dataSource: Array<Record<string, any>>;
    hasMore?: boolean;
    infinite?: boolean;
    loading?: boolean;
    loadMoreText?: string;
    pageSize: number;
    plain?: boolean;
    rowKey?: string;
    rowSelection?: Record<string, any>;
    scrollX?: number | string;
    showPager?: boolean;
    showSizeChanger?: boolean;
    summaryText?: string;
    total: number;
  }>(),
  {
    autoHeight: false,
    hasMore: false,
    infinite: false,
    loadMoreText: '下滑加载下一页',
    loading: false,
    plain: false,
    rowKey: 'id',
    scrollX: 1000,
    showPager: true,
    showSizeChanger: true,
  },
);

const emit = defineEmits<{
  loadMore: [];
  pageChange: [page: number, pageSize: number];
}>();

const RenderNode = defineComponent({
  name: 'AdminConfigTableRenderNode',
  props: {
    node: {
      default: null,
      required: false,
    },
  },
  setup(props) {
    return () => props.node as any;
  },
});

const tableAreaRef = ref<HTMLDivElement | null>(null);
const pagerRef = ref<HTMLDivElement | null>(null);
const summaryRef = ref<HTMLDivElement | null>(null);
const tableScrollY = ref(320);
let tableResizeObserver: null | ResizeObserver = null;
let tableBodyElement: HTMLElement | null = null;
let loadMoreLocked = false;

const tableScroll = computed(() => {
  const scroll: Record<string, any> = { x: props.scrollX };
  if (props.autoHeight) {
    scroll.y = tableScrollY.value;
  }
  return scroll;
});

const infiniteStatusText = computed(() => {
  if (!props.infinite) {
    return '';
  }
  if (props.loading && props.dataSource.length > 0) {
    return '加载中...';
  }
  if (props.hasMore) {
    return props.loadMoreText;
  }
  return props.dataSource.length > 0 ? '已加载全部' : '';
});

function stringifyCellValue(value: any) {
  if (value === undefined || value === null || value === '') {
    return '-';
  }
  return String(value);
}

function getColumnValue(record: Record<string, any>, column: Record<string, any>) {
  const dataIndex = column?.dataIndex;
  if (Array.isArray(dataIndex)) {
    let value = record;
    for (const key of dataIndex) {
      value = value?.[String(key)];
    }
    return value;
  }
  if (dataIndex === undefined || dataIndex === null) {
    return undefined;
  }
  return record?.[String(dataIndex)];
}

function isVNodeLike(value: any) {
  return Boolean(value && typeof value === 'object' && value.__v_isVNode);
}

function updateTableScrollY() {
  if (!props.autoHeight) {
    return;
  }
  const area = tableAreaRef.value;
  if (!area) {
    return;
  }
  const headerHeight =
    area.querySelector('.ant-table-thead')?.getBoundingClientRect().height || 48;
  const scrollbarReserve = 8;
  tableScrollY.value = Math.max(
    160,
    Math.floor(area.clientHeight - headerHeight - scrollbarReserve),
  );
}

function emitLoadMore() {
  if (loadMoreLocked) {
    return;
  }
  loadMoreLocked = true;
  emit('loadMore');
  window.setTimeout(() => {
    loadMoreLocked = false;
  }, 300);
}

function handleTableBodyScroll(event: Event) {
  if (!props.infinite || props.loading || !props.hasMore) {
    return;
  }
  const target = event.currentTarget as HTMLElement;
  const distanceToBottom =
    target.scrollHeight - target.scrollTop - target.clientHeight;
  if (distanceToBottom <= 80) {
    emitLoadMore();
  }
}

function bindTableBodyScroll() {
  const nextBody = props.infinite
    ? (tableAreaRef.value?.querySelector('.ant-table-body') as HTMLElement | null)
    : null;
  if (nextBody === tableBodyElement) {
    return;
  }
  tableBodyElement?.removeEventListener('scroll', handleTableBodyScroll);
  tableBodyElement = nextBody;
  tableBodyElement?.addEventListener('scroll', handleTableBodyScroll);
}

async function refreshTableScroll() {
  await nextTick();
  updateTableScrollY();
  bindTableBodyScroll();
}

onMounted(async () => {
  await refreshTableScroll();
  if (!props.autoHeight || typeof ResizeObserver === 'undefined') {
    return;
  }
  tableResizeObserver = new ResizeObserver(() => {
    void refreshTableScroll();
  });
  if (tableAreaRef.value) {
    tableResizeObserver.observe(tableAreaRef.value);
  }
  if (pagerRef.value) {
    tableResizeObserver.observe(pagerRef.value);
  }
  if (summaryRef.value) {
    tableResizeObserver.observe(summaryRef.value);
  }
  window.addEventListener('resize', updateTableScrollY);
});

onUpdated(() => {
  void refreshTableScroll();
});

onBeforeUnmount(() => {
  tableBodyElement?.removeEventListener('scroll', handleTableBodyScroll);
  tableResizeObserver?.disconnect();
  window.removeEventListener('resize', updateTableScrollY);
});
</script>

<template>
  <div
    class="admin-config-table-card"
    :class="{ 'admin-config-table-card--plain': plain }"
  >
    <div v-if="summaryText" ref="summaryRef" class="admin-config-table-summary">
      {{ summaryText }}
    </div>

    <div ref="tableAreaRef" class="admin-config-table-area">
      <Table
        class="admin-config-table"
        :columns="columns"
        :data-source="dataSource"
        :loading="loading"
        :pagination="false"
        :row-key="rowKey"
        :row-selection="rowSelection"
        :scroll="tableScroll"
      >
        <template #headerCell="slotProps">
          <RenderNode
            v-if="isVNodeLike(slotProps.column?.title)"
            :node="slotProps.column.title"
          />
          <slot v-else name="headerCell" v-bind="slotProps">
            {{ slotProps.column.title }}
          </slot>
        </template>
        <template #bodyCell="slotProps">
          <RenderNode v-if="isVNodeLike(slotProps.text)" :node="slotProps.text" />
          <slot v-else name="bodyCell" v-bind="slotProps">
            {{ stringifyCellValue(getColumnValue(slotProps.record, slotProps.column)) }}
          </slot>
        </template>
      </Table>
    </div>

    <div v-if="infiniteStatusText" class="admin-config-table-load-status">
      {{ infiniteStatusText }}
    </div>

    <div v-if="showPager" ref="pagerRef" class="admin-config-table-pager">
      <Pagination
        :current="current"
        :page-size="pageSize"
        :total="total"
        :show-size-changer="showSizeChanger"
        @change="(page: number, size: number) => emit('pageChange', page, size)"
        @show-size-change="(page: number, size: number) => emit('pageChange', page, size)"
      />
    </div>
  </div>
</template>

<style scoped>
.admin-config-table-card {
  background: #fff;
  border: 1px solid rgb(229 231 235);
  border-radius: 8px;
  box-shadow: none;
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

.admin-config-table-card--plain {
  border: 0;
  border-radius: 0;
}

.admin-config-table-area {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

.admin-config-table-summary {
  border-bottom: 1px solid rgb(237 240 244);
  color: rgb(71 84 103);
  flex: 0 0 auto;
  font-size: 12px;
  line-height: 18px;
  padding: 8px 12px;
}

.admin-config-table {
  height: 100%;
  width: 100%;
}

.admin-config-table :deep(.ant-table) {
  color: rgb(52 64 84);
  font-size: var(--density-table-body-font-size);
}

.admin-config-table :deep(.ant-spin-nested-loading),
.admin-config-table :deep(.ant-spin-container) {
  height: 100%;
  min-height: 0;
}

.admin-config-table :deep(.ant-spin-container) {
  display: flex;
  flex-direction: column;
}

.admin-config-table :deep(.ant-table) {
  flex: 1 1 auto;
  min-height: 0;
}

.admin-config-table :deep(.ant-table-container) {
  min-height: 0;
}

.admin-config-table :deep(.ant-table-thead > tr > th) {
  background: #fafafa;
  border-bottom-color: rgb(237 240 244);
  color: rgb(71 84 103);
  font-size: var(--density-table-header-font-size);
  font-weight: 500;
  height: var(--density-table-header-height);
  line-height: 1.35;
  padding: 8px var(--density-table-cell-padding-x);
}

.admin-config-table :deep(.ant-table-thead > tr > th::before) {
  background-color: rgb(237 240 244) !important;
}

.admin-config-table :deep(.ant-table-tbody > tr > td) {
  border-bottom-color: rgb(237 240 244);
  height: var(--density-table-row-height);
  line-height: 1.35;
  padding: 3px var(--density-table-cell-padding-x);
}

.admin-config-table :deep(.ant-table-body) {
  overflow-y: auto !important;
}

.admin-config-table :deep(.switch-column) {
  padding-left: var(--density-table-fixed-cell-padding-x) !important;
  padding-right: var(--density-table-fixed-cell-padding-x) !important;
}

.admin-config-table :deep(.action-column) {
  padding-left: var(--density-table-fixed-cell-padding-x) !important;
  padding-right: var(--density-table-fixed-cell-padding-x) !important;
}

.admin-config-table :deep(.ant-table-tbody > tr:hover > td) {
  background: rgb(250 252 255);
}

.admin-config-table :deep(.ant-table-cell-fix-left),
.admin-config-table :deep(.ant-table-cell-fix-right) {
  background: #fff;
}

.admin-config-table :deep(.ant-table-tbody > tr:hover > .ant-table-cell-fix-left),
.admin-config-table :deep(.ant-table-tbody > tr:hover > .ant-table-cell-fix-right) {
  background: rgb(250 252 255);
}

.admin-config-table-load-status {
  border-top: 1px solid rgb(237 240 244);
  color: rgb(100 116 139);
  flex: 0 0 auto;
  font-size: 12px;
  line-height: 18px;
  padding: 8px 12px;
  text-align: center;
}

.admin-config-table-pager {
  border-top: 1px solid rgb(237 240 244);
  display: flex;
  flex: 0 0 auto;
  justify-content: flex-end;
  padding: 10px 12px;
}
</style>
