<script lang="ts" setup generic="T">
import { type TableColumnCtx } from 'element-plus'
import { computed, inject, nextTick, ref, useAttrs, useSlots, watch } from 'vue'
import { TableContextKey } from './token'
import ZmTableColumnSettingTree from './ZmTableColumnSettingTree.vue'
import type { ColumnAlign, ColumnSettingItem, SortChangePayload, SortOrder } from './token'
import type { DefaultRow } from 'element-plus/es/components/table/src/table/defaults'

interface Props
  extends /* @vue-ignore */ Partial<
    Omit<TableColumnCtx<T extends DefaultRow ? T : DefaultRow>, 'prop'>
  > {
  prop?: (keyof T & string) | (string & {})
  action?: boolean
  hideInColumnSettings?: boolean
  isParent?: boolean
  zmSortable?: boolean
  zmFilterable?: boolean
  sortOrder?: SortOrder | null
  defaultSortOrder?: SortOrder | null
  zmSortMethod?: (prop: string, order: SortOrder | null) => void
  filterActive?: boolean
  filterModelValue?: any
  realValue?: (...args: any[]) => any
  coverFormatter?: boolean
}

const emits = defineEmits<{
  'update:filterModelValue': [value: any]
  'update:sortOrder': [order: SortOrder | null]
  'sort-change': [payload: SortChangePayload]
  'filter-click': [payload: { prop?: string }]
  'filter-visible-change': [visible: boolean]
}>()

const props = defineProps<Props>()
const attrs = useAttrs()
const slots = useSlots()

const tableContext = inject(TableContextKey, {
  data: ref([]),
  loading: ref(false),
  columnAlign: ref<ColumnAlign>('center'),
  columnMaxWidth: ref(360),
  columnSettings: ref([]),
  updateColumnVisible: () => {},
  updateColumnFixed: () => {},
  updateColumnOrder: () => {},
  resetColumnSettings: () => {}
})

const innerSortOrder = ref<SortOrder | null>(props.defaultSortOrder ?? null)
const filterVisible = ref(false)
const settingVisible = ref(false)
const hasHeaderAction = computed(() => props.action || props.zmSortable || props.zmFilterable)
const forwardedSlots = computed(() => {
  const { header: _header, ...restSlots } = slots
  return restSlots
})

const defaultOptions = ref<Partial<Props>>({
  align: 'center',
  resizable: true
})

const bindProps = computed(() => {
  const {
    action,
    hideInColumnSettings,
    zmSortable,
    zmFilterable,
    sortOrder,
    defaultSortOrder,
    zmSortMethod,
    filterActive,
    filterModelValue,
    realValue,
    coverFormatter,
    isParent,
    ...columnProps
  } = props
  const columnAlign = props.align || (attrs.align as ColumnAlign | undefined)
  const resolvedAlign = columnAlign || tableContext.columnAlign.value || defaultOptions.value.align

  return {
    ...defaultOptions.value,
    ...attrs,
    ...columnProps,
    prop: props.prop,
    align: resolvedAlign,
    className: [props.className, props.prop].filter(Boolean).join(' '),
    formatter: coverFormatter ? realValue : props.formatter
  }
})

const alignMap: Record<string, string> = {
  center: 'justify-center',
  left: 'justify-between',
  right: 'justify-end'
}
const headerFlexClass = computed(() => {
  if (hasHeaderAction.value) return 'justify-between'
  return alignMap[String(bindProps.value.align)] || 'justify-center'
})

const isSortControlled = computed(() => props.sortOrder !== undefined)
const currentOrder = computed<SortOrder | null>(() => {
  return isSortControlled.value ? (props.sortOrder ?? null) : innerSortOrder.value
})
const isSortActive = computed(() => currentOrder.value !== null)

const hasFilterValue = (value: any) => {
  if (Array.isArray(value)) return value.length > 0
  if (typeof value === 'string') return value.length > 0
  return value !== undefined && value !== null && value !== ''
}
const isFilterActive = computed(() => props.filterActive ?? hasFilterValue(props.filterModelValue))

const columnSettingsModel = computed<ColumnSettingItem[]>({
  get: () => tableContext.columnSettings.value,
  set: (items) => {
    tableContext.updateColumnOrder(items.map((item) => item.key))
  }
})

const handleSortClick = () => {
  if (!props.prop) return

  let nextOrder: SortOrder | null = 'asc'

  if (currentOrder.value === 'asc') {
    nextOrder = 'desc'
  } else if (currentOrder.value === 'desc') {
    nextOrder = null
  }

  if (!isSortControlled.value) {
    innerSortOrder.value = nextOrder
  }

  emits('update:sortOrder', nextOrder)
  emits('sort-change', { prop: props.prop, order: nextOrder })
  props.zmSortMethod?.(props.prop, nextOrder)
}

const updateFilterModelValue = (value: any) => {
  emits('update:filterModelValue', value)
}

const closeFilterPopover = () => {
  filterVisible.value = false
}

const setFilterVisible = (visible: boolean) => {
  filterVisible.value = visible
}

const getFilterSlotProps = (scope: any) => ({
  ...scope,
  prop: props.prop,
  filterModelValue: props.filterModelValue,
  close: closeFilterPopover,
  setVisible: setFilterVisible,
  updateFilterModelValue
})

const handleFilterReferenceClick = () => {
  emits('filter-click', { prop: props.prop })
}

watch(
  () => props.defaultSortOrder,
  (order) => {
    if (!isSortControlled.value) {
      innerSortOrder.value = order ?? null
    }
  }
)

watch(filterVisible, (visible) => {
  emits('filter-visible-change', visible)
})

const getTableComputedStyle = () => {
  const tableElement = document.querySelector('.zm-table') as HTMLElement | null
  return getComputedStyle(tableElement ?? document.documentElement)
}

const getTableStyleVariable = (name: string, fallback: string) => {
  const style = getTableComputedStyle()
  return style.getPropertyValue(name).trim() || fallback
}

const getTextWidth = (text: string) => {
  const tableStyle = getTableComputedStyle()
  const span = document.createElement('span')
  span.style.visibility = 'hidden'
  span.style.position = 'absolute'
  span.style.whiteSpace = 'nowrap'
  span.style.fontSize = getTableStyleVariable(
    '--zm-table-column-width-measure-font-size',
    getTableStyleVariable('--zm-table-font-size', tableStyle.fontSize || '12px')
  )
  span.style.fontFamily = getTableStyleVariable(
    '--zm-table-column-width-measure-font-family',
    tableStyle.fontFamily || 'Noto Sans SC'
  )
  span.innerText = text
  document.body.appendChild(span)
  const width = span.offsetWidth
  document.body.removeChild(span)
  return width
}

const calculativeWidth = () => {
  if (!props.prop) return
  const values = tableContext.data.value
    .map((item) => props.realValue?.(item) ?? item[props.prop as keyof typeof item])
    .filter(hasFilterValue)
  let labelWidth = getTextWidth(bindProps.value.label || '') + 32
  if (hasHeaderAction.value) labelWidth += 8
  if (props.zmFilterable) labelWidth += 22
  if (props.zmSortable) labelWidth += 22
  const maxWidth = Math.min(
    Math.max(...values.map((value) => getTextWidth(String(value)) + 38), labelWidth),
    tableContext.columnMaxWidth.value
  )
  defaultOptions.value.minWidth = maxWidth
}

watch(
  [() => tableContext.loading.value, () => tableContext.columnMaxWidth.value],
  () => {
    nextTick(() => {
      calculativeWidth()
    })
  },
  { immediate: true }
)
</script>

<template>
  <el-table-column ref="columnRef" v-bind="bindProps">
    <template v-for="(_, name) in forwardedSlots" :key="name" #[name]="slotData">
      <slot :name="name" v-bind="slotData || {}"></slot>
    </template>
    <template #header="scope">
      <slot name="header" v-bind="scope">
        <div class="header-wrapper" :class="headerFlexClass">
          <span class="truncate" :title="scope.column.label">{{ scope.column.label }}</span>
          <div v-if="hasHeaderAction" class="action-area">
            <el-tooltip
              v-if="props.zmSortable"
              :content="
                currentOrder === 'asc'
                  ? '点击降序'
                  : currentOrder === 'desc'
                    ? '取消排序'
                    : '点击升序'
              "
              placement="top"
              :show-after="500"
            >
              <button
                type="button"
                class="icon-btn"
                :class="{ 'is-active': isSortActive }"
                @click.stop="handleSortClick"
              >
                <div v-if="currentOrder === 'asc'" class="sort-icon i-lucide:arrow-up-narrow-wide">
                </div>
                <div
                  v-else-if="currentOrder === 'desc'"
                  class="sort-icon i-lucide:arrow-down-wide-narrow"
                >
                </div>
                <div v-else class="sort-icon i-lucide:arrow-up-down"></div>
              </button>
            </el-tooltip>

            <el-popover
              v-if="props.zmFilterable"
              v-model:visible="filterVisible"
              placement="top"
              :popper-options="{ modifiers: [{ name: 'offset', options: { offset: [16, 18] } }] }"
              trigger="click"
              :width="260"
              :show-arrow="false"
            >
              <template #reference>
                <button
                  type="button"
                  class="icon-btn"
                  :class="{ 'is-active': isFilterActive }"
                  @click.stop="handleFilterReferenceClick"
                >
                  <div class="filter-icon i-lucide:list-filter"></div>
                </button>
              </template>
              <slot name="filter" v-bind="getFilterSlotProps(scope)"></slot>
            </el-popover>

            <el-popover
              v-if="props.action"
              v-model:visible="settingVisible"
              placement="bottom-end"
              trigger="click"
              :width="360"
              :show-arrow="false"
              popper-class="zm-table-column-setting-popper"
            >
              <template #reference>
                <button type="button" class="icon-btn" title="列设置" @click.stop>
                  <div class="setting-icon i-lucide:settings"></div>
                </button>
              </template>

              <div class="column-setting-panel">
                <ZmTableColumnSettingTree v-model="columnSettingsModel" />

                <div class="column-setting-footer">
                  <el-button
                    link
                    type="primary"
                    size="small"
                    @click="tableContext.resetColumnSettings"
                  >
                    重置
                  </el-button>
                </div>
              </div>
            </el-popover>
          </div>
        </div>
      </slot>
    </template>
  </el-table-column>
</template>

<style scoped lang="scss">
// 表头整体容器：左侧是标题，右侧是按钮区。
.header-wrapper {
  display: flex;
  align-items: center;
  width: 100%;
  height: 100%;
  min-width: 0;
  gap: 6px;
  font-size: var(--zm-table-header-font-size, var(--zm-table-font-size, 12px));
  font-weight: var(--zm-table-header-font-weight, 600);
  line-height: var(--zm-table-header-line-height, 16px);
  color: var(--zm-table-header-text-color, #6b7f99);
  user-select: none;

  .truncate {
    min-width: 0;
  }
}

// 表头右侧按钮区。
.action-area {
  display: flex;
  flex: 0 0 auto;
  height: 100%;
  margin-left: 4px;
  align-items: center;
  gap: 3px;
}

// 表头小图标按钮的通用样式。
.icon-btn {
  display: flex;
  width: var(--zm-table-header-icon-btn-size, 18px);
  height: var(--zm-table-header-icon-btn-size, 18px);
  padding: 0;
  color: var(--zm-table-header-icon-btn-color, #8aa0b8);
  cursor: pointer;
  background: transparent;
  border: 0;
  border-radius: var(--zm-table-header-icon-btn-radius, 4px);
  transition:
    color 0.16s ease,
    background-color 0.16s ease;
  align-items: center;
  justify-content: center;

  &:hover {
    color: var(--zm-table-header-icon-btn-hover-color, var(--el-color-primary));
    background-color: var(--zm-table-header-icon-btn-hover-bg, var(--el-color-primary-light-9));
  }

  &.is-active {
    color: var(--zm-table-header-icon-btn-active-color, var(--el-color-primary));
    background-color: var(--zm-table-header-icon-btn-active-bg, var(--el-color-primary-light-9));
  }
}

// 排序、筛选、设置这几个图标统一大小。
.sort-icon,
.filter-icon,
.setting-icon {
  width: var(--zm-table-header-icon-size, 16px);
  height: var(--zm-table-header-icon-size, 16px);
}

// 列设置面板容器。
.column-setting-panel {
  min-width: 0;
}

// 面板底部区域，放重置按钮。
.column-setting-footer {
  display: flex;
  justify-content: flex-end;
  padding-top: 8px;
  margin-top: 8px;
  border-top: 1px solid var(--zm-table-column-setting-border-color, var(--el-border-color-lighter));
}

:global(.zm-table-column-setting-popper) {
  --zm-table-column-setting-font-size: var(--zm-table-font-size, 12px);
  --zm-table-column-setting-text-color: var(--el-text-color-regular);
  --zm-table-column-setting-border-color: var(--el-border-color);
  --zm-table-column-setting-hover-bg: var(--el-fill-color-lighter);
  --zm-table-column-setting-radius: 6px;
  --zm-table-column-setting-gap: 4px;
  --zm-table-column-setting-max-height: 360px;
  --zm-table-column-setting-row-height: 32px;
  --zm-table-column-setting-child-row-height: 30px;
  --zm-table-column-setting-item-font-weight: 400;
  --zm-table-column-setting-group-font-weight: 600;
  --zm-table-column-setting-icon-btn-size: 22px;
  --zm-table-column-setting-icon-size: 15px;
  --zm-table-column-setting-icon-color: #8aa0b8;
  --zm-table-column-setting-icon-active-color: var(--el-color-primary);
  --zm-table-column-setting-icon-active-bg: var(--el-color-primary-light-9);
  --zm-table-column-setting-ghost-bg: var(--el-color-primary-light-9);
  --zm-table-column-setting-ghost-opacity: 0.55;
}
</style>
