<script lang="ts" setup generic="T">
import type { TableColumnCtx } from 'element-plus'
import { computed, useAttrs, inject, ref } from 'vue'
import { Filter } from '@element-plus/icons-vue'
import { SortOrder, TableContextKey } from './token'

interface Props extends /* @vue-ignore */ Partial<Omit<TableColumnCtx<T>, 'prop'>> {
  prop?: (keyof T & string) | (string & {})
  zmSortable?: boolean
  zmFilterable?: boolean
  filterModelValue?: any
  realValue?: (value: any) => any
}

const emits = defineEmits(['update:filterModelValue'])
const props = defineProps<Props>()
const attrs = useAttrs()

const tableContext = inject(TableContextKey, {
  data: ref([]),
  sortingFields: ref([]),
  onQuery: () => {},
  onSort: () => {},
  loading: ref(false)
})

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

const bindProps = computed(() => {
  const resolvedAlign =
    props.zmSortable || props.zmFilterable
      ? 'left'
      : attrs.align || props.align || defaultOptions.value.align

  return {
    ...defaultOptions.value,
    ...attrs,
    ...props,
    prop: props.prop,
    align: resolvedAlign,
    className: (props.className ?? '') + ' ' + props.prop
  }
})

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

const currentSortField = computed(() => {
  if (!props.prop) return undefined
  return tableContext.sortingFields.value.find((f: any) => f.field === props.prop)
})

const isSortActive = computed(() => !!currentSortField.value)

const currentOrder = computed<SortOrder | undefined>(() => currentSortField.value?.order)

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

  let nextOrder: SortOrder | null = 'asc'

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

  tableContext.onSort(props.prop, nextOrder)
}

const handleSearchClick = () => {
  if (tableContext.onQuery && props.prop) {
    tableContext.onQuery({
      prop: props.prop,
      value: props.filterModelValue
    })
  }
}

const getTextWidth = (text: string, fontSize = 14) => {
  const span = document.createElement('span')
  span.style.visibility = 'hidden'
  span.style.position = 'absolute'
  span.style.whiteSpace = 'nowrap'
  span.style.fontSize = `${fontSize}px`
  span.style.fontFamily = 'PingFang SC'
  span.innerText = text

  document.body.appendChild(span)
  const width = span.offsetWidth
  document.body.removeChild(span)

  return width
}

const calculativeWidth = () => {
  const values = tableContext.data.value
    .map((item) => props.realValue?.(item[props.prop]) || item[props.prop])
    .filter(Boolean)

  let labelWidth = getTextWidth(bindProps.value.label || '') + 38

  if (props.zmFilterable || props.zmSortable) {
    labelWidth += 8
  }

  if (props.zmFilterable) labelWidth += 22
  if (props.zmSortable) labelWidth += 22

  const maxWidth = Math.min(
    Math.max(...values.map((value) => getTextWidth(value) + 38), labelWidth),
    360
  )

  defaultOptions.value.minWidth = maxWidth
}

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

<template>
  <el-table-column ref="columnRef" v-bind="bindProps">
    <template v-for="(_, name) in $slots" :key="name" #[name]="slotData">
      <slot v-if="name !== 'header'" :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="bindProps.zmSortable || bindProps.zmFilterable" class="action-area">
            <el-tooltip
              v-if="bindProps.zmSortable"
              :content="
                currentOrder === 'asc'
                  ? '点击降序'
                  : currentOrder === 'desc'
                    ? '取消排序'
                    : '点击升序'
              "
              placement="top"
              :show-after="500"
            >
              <div
                class="icon-btn"
                :class="{ 'is-active': isSortActive }"
                @click.stop="handleSortClick"
              >
                <i
                  class="zm-sort-icon"
                  :class="{
                    'is-desc': currentOrder === 'desc'
                  }"
                ></i>
              </div>
            </el-tooltip>

            <el-popover
              v-if="bindProps.zmFilterable"
              placement="top-end"
              :popper-options="{ modifiers: [{ name: 'offset', options: { offset: [16, 18] } }] }"
              trigger="click"
              :width="260"
              :show-arrow="false"
            >
              <template #reference>
                <div
                  class="icon-btn"
                  :class="{ 'is-active': bindProps.filterModelValue }"
                  @click.stop
                >
                  <el-icon :size="16"><Filter /></el-icon>
                </div>
              </template>

              <slot name="filter" v-bind="scope">
                <div class="flex gap-2 p-1">
                  <el-input
                    :model-value="bindProps.filterModelValue"
                    @input="(val) => emits('update:filterModelValue', val)"
                    placeholder="输入关键词"
                    size="small"
                    clearable
                    @keydown.enter="handleSearchClick"
                  />
                  <el-button type="primary" size="small" @click="handleSearchClick">
                    搜索
                  </el-button>
                </div>
              </slot>
            </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%;
  user-select: none;
}

.action-area {
  display: flex;
  height: 100%;
  margin-left: 8px;
  align-items: center;
  gap: 4px;
}

.icon-btn {
  display: flex;
  width: 20px;
  height: 20px;
  color: var(--el-text-color-secondary);
  cursor: pointer;
  border-radius: 4px;
  transition: background-color 0.2s;
  align-items: center;
  justify-content: center;

  &:hover {
    color: var(--el-color-primary);
    background-color: var(--el-fill-color-darker);
  }

  &.is-active {
    color: var(--el-color-primary);
  }
}

.zm-sort-icon {
  position: relative;
  display: flex;
  width: 12px;
  height: 12px;
  align-items: center;
  justify-content: center;

  &::before,
  &::after {
    position: absolute;
    width: 8px;
    height: 2px;
    background-color: currentcolor;
    border-radius: 2px;
    content: '';
    transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  }

  &::before {
    transform: translateX(-2.2px) rotate(45deg);
  }

  &::after {
    transform: translateX(2.2px) rotate(-45deg);
  }

  &.is-desc {
    &::before {
      transform: translateX(-2.2px) rotate(-45deg);
    }

    &::after {
      transform: translateX(2.2px) rotate(45deg);
    }
  }
}
</style>
