<!-- TableCom.vue -->
<script lang="ts" setup generic="T extends Record<string, any>">
import { PlayIcon } from "@heroicons/vue/24/solid";

import { ChevronUpDownIcon } from "@/assets/Icons";
import { Skeleton } from "@/components/ui/skeleton";
import {
  Table,
  TableBody,
  TableCell,
  TableEmpty,
  TableFooter,
  TableHead,
  TableHeader,
  TableRow
} from "@/components/ui/table";
import { cn } from "@/lib/utils";
import { useSortableTable } from "@/utils";

import NewTablePagination from "./NewTablePagination.vue";
import type { TableColumn, TableProps } from "./type";

const props = defineProps<TableProps<T>>();

const emits = defineEmits<{
  "sort:change": [{ sortColumn: string; sortDirection: "asc" | "desc" | "" }];
  "page:change": [page: number];
  "row-expand:change": [{ row: T; index: number; expanded: boolean }];
}>();

const isMobile = useMediaQuery("(max-width: 640px)");
const $slots = useSlots();
const tableEl = useTemplateRef<HTMLDivElement>("tableEl");
const { x, arrivedState, measure } = useScroll(tableEl);
const { width } = useElementSize(tableEl);

// 显示的数据，避免加载时表格闪烁
const displayData = ref<T[]>([]);

// 监听数据加载完成，更新显示数据
watch(
  [() => props.data, () => props.load],
  ([newData, loading], [_, oldLoading]) => {
    // 如果不启用数据缓冲，直接更新显示数据
    if (!props.buffer) {
      displayData.value = newData;
    }
    // 只有当加载状态从 true 变为 false 时才更新显示数据
    if (oldLoading === true && loading === false) {
      displayData.value = newData;
    }
    // 首次加载完成
    if (oldLoading === undefined && loading === false && newData.length > 0) {
      displayData.value = newData;
    }
  }
);

const defaultSortColumn = computed(() => props.defaultSortColumn || "");
const defaultSortDirection = computed(() => props.defaultSortDirection || "");
const columnsConfig = computed(() => props.columns || []);

// 表格排序
const { sortColumn, sortDirection, handleSort, sortedData } = useSortableTable(
  displayData,
  defaultSortColumn,
  defaultSortDirection,
  columnsConfig
);

// 监听排序变化，通知父组件
watchEffect(() => {
  emits("sort:change", {
    sortColumn: sortColumn.value,
    sortDirection: sortDirection.value
  });
});

// 最终数据
const finalData = computed(() => {
  if (!props.pinnedCondition) return sortedData.value;

  const pinned: T[] = [];
  const unpinned: T[] = [];

  sortedData.value.forEach((row) => {
    if (props.pinnedCondition!(row as T)) {
      pinned.push(row as T);
    } else {
      unpinned.push(row as T);
    }
  });

  return [...pinned, ...unpinned];
});

// 获取索引值
const getIndexValue = (column: TableColumn, rowIndex: number) => {
  if (typeof column.index === "function") return column.index(rowIndex);
  return typeof column.index === "number" ? column.index + rowIndex : rowIndex;
};

// 获取单元格内容
const getCellValue = (row: any, column: TableColumn, rowIndex: number) => {
  // 如果是索引列
  if (column.type === "index") return getIndexValue(column, rowIndex + 1);

  // 优先使用 prop
  if (column.prop && typeof column.prop === "string") return row[column.prop];

  // 其次使用 slot 对应的字段
  if (column.slot && typeof column.slot === "string") return row[column.slot];

  return undefined;
};

// 获取列的字段名
const getColumnKey = (column: TableColumn, index?: number) =>
  column.key ||
  (column.prop as string) ||
  column.slot ||
  column.label ||
  (index ?? "");

// 处理表头点击排序
const onHeaderClick = (column: TableColumn) => {
  if (!column.sortable) return;

  const columnKey = getColumnKey(column);
  if (!columnKey) return;

  handleSort({
    key: String(columnKey),
    sortable: column.sortable,
    name: column.label || ""
  });
};

/** 过滤隐藏列 */
const visibleColumns = computed(() =>
  (props.columns || []).filter((column) => !column.hidden)
);

/** 获取列的样式 */
const getColumnStyle = (column: TableColumn) => {
  const style: Record<string, string> = {};

  if (column.width) {
    style.width =
      typeof column.width === "number" ? `${column.width}px` : column.width;
    style.maxWidth =
      typeof column.width === "number" ? `${column.width}px` : column.width;
  }

  if (column.minWidth) {
    style.minWidth =
      typeof column.minWidth === "number"
        ? `${column.minWidth}px`
        : column.minWidth;
  }

  return style;
};

const reMeasure = () => setTimeout(() => measure(), 0);
// 尺寸变化重新计算滚动状态
useResizeObserver(tableEl, () => reMeasure());

// 显示滚动阴影
const showScrollShadow = (direction: "left" | "right") => {
  if (props.hiddenShadow) return false;
  const canScroll = !(arrivedState.left && arrivedState.right);
  return canScroll && !arrivedState[direction] && !props.load;
};

// ---- 行折叠相关 ----
/** 存储每行的展开状态，key 为行索引或行 id */
const expandedRows = ref<Set<string | number>>(new Set());

/** 获取行的唯一标识 */
const getRowKey = (row: T, index: number): string | number => row.id ?? index;

/** 查是否有 row-append-{slot} 插槽 */
const hasSlot = computed(() =>
  visibleColumns.value.some(
    (col) => col.slot && $slots[`row-append-${col.slot}`]
  )
);

/** 判断表格是否存在任何可展开的行 */
const hasAnyRowAppend = computed(() => {
  if (!hasSlot.value) return false;
  // 再检查是否有任意一行满足 rowAppendVisible 条件
  return finalData.value.some((row, index) => {
    if (props.rowAppendVisible) {
      return props.rowAppendVisible(row as T, index);
    }
    return true;
  });
});

/** 判断某行是否有 row-append 内容 */
const hasRowAppend = (row: T, index: number): boolean => {
  if (!hasSlot.value) return false;

  // 再检查 rowAppendVisible 条件
  if (props.rowAppendVisible) {
    return props.rowAppendVisible(row as T, index);
  }
  return true;
};

/** 判断某行是否展开 */
const isRowExpanded = (row: T, index: number): boolean => {
  const key = getRowKey(row, index);
  return expandedRows.value.has(key);
};

/** 切换行的展开状态 */
const toggleRowExpand = (row: T, index: number) => {
  if (!props.rowAppendExpandable) return;
  if (!hasRowAppend(row, index)) return;

  const key = getRowKey(row, index);
  const newExpanded = !expandedRows.value.has(key);

  if (newExpanded) {
    expandedRows.value.add(key);
  } else {
    expandedRows.value.delete(key);
  }

  emits("row-expand:change", { row, index, expanded: newExpanded });
};

/** 初始化展开状态 */
const initExpandedRows = () => {
  expandedRows.value.clear();

  // 如果默认展开，则把所有有 row-append 的行加入展开集合
  if (props.rowAppendDefaultExpanded !== false) {
    finalData.value.forEach((row, index: number) => {
      if (hasRowAppend(row as T, index)) {
        expandedRows.value.add(getRowKey(row as T, index));
      }
    });
  }
};

// 监听数据变化，重新初始化展开状态
watch(
  () => finalData.value,
  () => {
    initExpandedRows();
  },
  { immediate: true }
);

// ---- 行折叠相关 ----

// 初始化显示数据
onMounted(() => {
  if (!props.load && props.data.length > 0) {
    displayData.value = props.data;
  }
});
</script>

<template>
  <div
    class="relative [&>*]:scrollbar-hide"
    :class="[
      cn(
        'before:absolute before:top-0 before:bottom-0 before:left-0 before:w-2',
        'before:bg-linear-to-r before:from-border before:to-transparent',
        'before:pointer-events-none before:z-10 before:transition-opacity',
        'before:shadow-[inset_5px_0_4px_-3px_rgba(37,99,235,0.05)]',

        'after:absolute after:top-0 after:bottom-0 after:right-0 after:w-2',
        'after:bg-linear-to-l after:from-border after:to-transparent',
        'after:pointer-events-none after:z-10 after:transition-opacity',
        'after:shadow-[inset_-5px_0_4px_-3px_rgba(37,99,235,0.05)]',
        {
          'before:opacity-100': showScrollShadow('left'),
          'before:opacity-0': !showScrollShadow('left'),
          'after:opacity-100': showScrollShadow('right'),
          'after:opacity-0': !showScrollShadow('right'),
          'before:bottom-13 after:bottom-13':
            props.pagination?.currentPage && props.pagination?.pageSize
        }
      )
    ]"
  >
    <Table
      ref="tableEl"
      :class="[
        cn(
          'relative bg-card text-xs 2xl:text-sm rounded-2xl overflow-hidden',
          props.class
        )
      ]"
    >
      <TableHeader class="bg-muted select-none">
        <TableRow class="text-foreground font-medium">
          <TableHead
            v-for="(column, index) in visibleColumns"
            :key="getColumnKey(column, index)"
            :style="getColumnStyle(column)"
            :class="{
              'cursor-pointer': column.sortable,
              'pl-8': hasAnyRowAppend && index === 0
            }"
            @click="onHeaderClick(column)"
          >
            <div
              :class="
                cn(
                  'flex items-center text-nowrap',
                  column.headerAlign || props.headerAlign
                    ? `justify-${column.headerAlign || props.headerAlign}`
                    : '',
                  props.headerClassName,
                  column.headerClassName
                )
              "
            >
              <slot
                v-if="column.slot"
                :name="`header-${column.slot}`"
                :label="column.label"
                :column="column"
                :is-sorted="sortColumn === getColumnKey(column)"
              >
                <slot
                  name="header"
                  :label="column.label"
                  :column="column"
                  :is-sorted="sortColumn === getColumnKey(column)"
                >
                  {{ column.label }}
                </slot>
              </slot>
              <template v-else>
                <slot
                  name="header"
                  :label="column.label"
                  :column="column"
                  :is-sorted="sortColumn === getColumnKey(column)"
                >
                  {{ column.label }}
                </slot>
              </template>
              <!-- 排序指示器 icon -->
              <span v-if="column.sortable" class="flex items-center ml-2">
                <ChevronUpDownIcon
                  class="h-6! w-6! [&>.svg-top]:text-muted-foreground/40 [&>.svg-bottom]:text-muted-foreground/40"
                  :class="{
                    '[&>.svg-top]:text-primary! [&>.svg-bottom]:text-muted-foreground/40!':
                      sortColumn === getColumnKey(column) &&
                      sortDirection === 'asc',
                    '[&>.svg-bottom]:text-primary! [&>.svg-top]:text-muted-foreground/40!':
                      sortColumn === getColumnKey(column) &&
                      sortDirection === 'desc'
                  }"
                />
              </span>
            </div>
          </TableHead>
        </TableRow>
      </TableHeader>

      <TableBody class="[&_tr:last-child]:border-0! text-muted-foreground">
        <template
          v-for="(row, rowIndex) in finalData"
          :key="row.id || rowIndex"
        >
          <TableRow
            class="relative group border-border hover:bg-muted/50 text-inherit"
            :class="{
              'cursor-pointer':
                rowAppendExpandable && hasRowAppend(row as T, rowIndex)
            }"
            @click="toggleRowExpand(row as T, rowIndex)"
          >
            <TableCell
              v-for="(column, colIndex) in visibleColumns"
              :key="getColumnKey(column, colIndex)"
              :style="getColumnStyle(column)"
              :class="
                cn(
                  column.cellAlign || props.cellAlign
                    ? `text-${column.cellAlign || props.cellAlign}`
                    : '',
                  props.cellClassName,
                  column.cellClassName,
                  colIndex === 0 &&
                    rowAppendExpandable &&
                    hasAnyRowAppend &&
                    !hasRowAppend(row as T, rowIndex)
                    ? 'pl-8'
                    : ''
                )
              "
            >
              <div
                class="contents"
                :class="[
                  colIndex === 0 &&
                  rowAppendExpandable &&
                  hasRowAppend(row as T, rowIndex)
                    ? 'flex! h-full items-center gap-1.5'
                    : ''
                ]"
              >
                <!-- 展开/收起指示器 -->
                <span
                  v-if="
                    colIndex === 0 &&
                    rowAppendExpandable &&
                    hasRowAppend(row as T, rowIndex)
                  "
                  class="flex items-center justify-center transition-transform duration-250"
                  :class="{ 'rotate-90': isRowExpanded(row as T, rowIndex) }"
                >
                  <PlayIcon class="size-2.5! text-muted-foreground" />
                </span>

                <slot
                  :name="column.slot"
                  :row="row"
                  :column="column"
                  :index="rowIndex"
                  :value="getCellValue(row, column, rowIndex)"
                  :is-sorted="sortColumn === getColumnKey(column)"
                >
                  {{
                    column.formatter
                      ? (column.formatter(
                          getCellValue(row, column, rowIndex),
                          row,
                          rowIndex
                        ) ?? "-")
                      : (getCellValue(row, column, rowIndex) ?? "-")
                  }}
                </slot>
              </div>
            </TableCell>
          </TableRow>

          <!-- 按列分发的行追加内容 -->
          <TableRow
            v-if="
              visibleColumns.some(
                (col) => col.slot && $slots[`row-append-${col.slot}`]
              ) &&
              (rowAppendVisible?.(row as T, rowIndex) ?? true)
            "
            class="border-border overflow-hidden bg-muted! hover:bg-muted/70! transition-colors duration-200"
            :class="{
              'visible [&>td>div]:max-h-0 [&>td>div]:opacity-0 border-transparent ':
                !isRowExpanded(row as T, rowIndex)
            }"
          >
            <TableCell
              v-for="(column, colIndex) in visibleColumns"
              :key="'append-' + getColumnKey(column, colIndex)"
              :style="getColumnStyle(column)"
              :class="
                cn(
                  'py-0 transition-all duration-300',
                  column.cellAlign || props.cellAlign
                    ? `text-${column.cellAlign || props.cellAlign}`
                    : '',
                  props.cellClassName,
                  column.cellClassName
                )
              "
            >
              <div
                class="transition-all duration-300 overflow-hidden"
                :class="{
                  'max-h-96 opacity-100': isRowExpanded(row as T, rowIndex),
                  'max-h-0 opacity-0': !isRowExpanded(row as T, rowIndex)
                }"
              >
                <slot
                  v-if="column.slot"
                  :name="`row-append-${column.slot}`"
                  :row="row"
                  :index="rowIndex"
                  :column="column"
                  :expanded="isRowExpanded(row as T, rowIndex)"
                />
              </div>
            </TableCell>
          </TableRow>
        </template>

        <!-- 空状态 -->
        <TableEmpty
          v-if="finalData.length === 0 && !props.load"
          :colspan="visibleColumns?.length"
          class="relative"
        >
          <div
            class="max-sm:absolute max-sm:flex items-center justify-center"
            :style="{
              left: `${x}px`,
              width: isMobile ? `${width}px` : 'auto'
            }"
          >
            <slot name="empty" :colspan="visibleColumns?.length">
              <span class="text-gray-400">暂无数据</span>
            </slot>
          </div>
        </TableEmpty>

        <!-- 加载骨架 -->
        <template v-if="props.load && displayData.length === 0">
          <TableRow v-for="i in 5" :key="i">
            <TableCell v-for="j in visibleColumns?.length" :key="j" class="p-2">
              <Skeleton class="h-10 w-full" />
            </TableCell>
          </TableRow>
        </template>
      </TableBody>

      <!-- 加载状态 -->
      <template v-if="props.load && displayData.length !== 0">
        <div class="absolute inset-0 bg-background/40 z-10" />
      </template>

      <TableFooter :colspan="visibleColumns?.length">
        <slot
          name="footer"
          :data="finalData"
          :columns="visibleColumns"
          :total="finalData.length"
        />
      </TableFooter>
    </Table>

    <!-- 分页器 只有当 pagination 配置了 currentPage 和 pageSize 时才显示 -->
    <div
      v-if="props.pagination?.currentPage && props.pagination?.pageSize"
      class="w-full mt-4 h-9"
      :colspan="visibleColumns?.length"
    >
      <NewTablePagination
        :total="props.pagination.total"
        :page-size="props.pagination.pageSize"
        :current-page="props.pagination.currentPage"
        :load="props.load"
        :align="props.pagination.align"
        class="h-full"
        @update:page="(page: number) => emits('page:change', page)"
      />
    </div>
  </div>
</template>
