<template>
  <div class="ecp-pro-descriptions">
    <div v-if="showHeader" class="ecp-pro-descriptions__header">
      <div class="ecp-pro-descriptions__title-wrap">
        <span v-if="effectiveProps.title" class="ecp-pro-descriptions__title">{{ effectiveProps.title }}</span>
        <el-tooltip v-if="effectiveProps.helpMessage" placement="top" effect="dark">
          <template slot="content">
            <span v-if="Array.isArray(effectiveProps.helpMessage)">
              <div v-for="(msg, index) in effectiveProps.helpMessage" :key="index">{{ msg }}</div>
            </span>
            <span v-else>{{ effectiveProps.helpMessage }}</span>
          </template>
          <i class="el-icon-question ecp-pro-descriptions__help" />
        </el-tooltip>
        <el-tooltip v-if="effectiveProps.collapseOptions?.helpMessage" placement="top" effect="dark">
          <template slot="content">
            <span v-if="Array.isArray(effectiveProps.collapseOptions.helpMessage)">
              <div v-for="(msg, index) in effectiveProps.collapseOptions.helpMessage" :key="`collapse-${index}`">{{ msg }}</div>
            </span>
            <span v-else>{{ effectiveProps.collapseOptions.helpMessage }}</span>
          </template>
          <i class="el-icon-info ecp-pro-descriptions__help" />
        </el-tooltip>
      </div>
      <el-button
        v-if="showCollapseButton"
        type="text"
        class="ecp-pro-descriptions__toggle"
        @click="expanded = !expanded"
      >
        {{ expanded ? collapseButtonText.collapse : collapseButtonText.expand }}
        <i class="el-icon-arrow-down" :class="expanded ? 'is-expanded' : ''" />
      </el-button>
    </div>

    <div
      class="ecp-pro-descriptions__body"
      :class="[
        `is-${effectiveProps.size}`,
        { 'is-bordered': effectiveProps.bordered, 'is-collapsed': showCollapseButton && !expanded },
      ]"
      :style="bodyStyle"
      v-bind="$attrs"
    >
      <template v-for="(row, rowIndex) in renderedRows">
        <template v-for="item in row.items">
          <div
            :key="`${rowIndex}-${item.dataIndex || item.field}`"
            class="ecp-pro-descriptions__item"
            :style="getItemStyle(item)"
          >
            <div class="ecp-pro-descriptions__label" :style="getLabelStyle(item)">
              {{ item.label }}
            </div>
            <div class="ecp-pro-descriptions__content" :style="getContentStyle(item)">
              <slot
                v-if="$scopedSlots[item.slot || item.dataIndex]"
                :name="item.slot || item.dataIndex"
                :value="getItemValue(item)"
                :record="effectiveData"
                :schema="item"
              />
              <DescriptionValueRenderer v-else :schema="item" :value="getItemValue(item)" :record="effectiveData" :empty-text="effectiveProps.emptyText || '-'" />
            </div>
          </div>
        </template>
      </template>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, defineComponent, h, onMounted, onUnmounted, ref, watch } from 'vue'
import { useComponentSetting } from '../useComponentSetting'
import { normalizeTooltipConfig } from '../utils/tooltip'
import type {
  DescriptionActionType,
  DescriptionColumn,
  DescriptionProps,
  DescriptionSchema,
} from '../types'

interface NormalizedRow {
  items: Array<DescriptionSchema & { _span: number }>
}

const props = withDefaults(
  defineProps<{
    title?: string
    helpMessage?: string | string[]
    size?: 'medium' | 'small'
    bordered?: boolean
    column?: DescriptionColumn
    schema?: DescriptionSchema[]
    data?: Record<string, unknown>
    emptyText?: string
    useCollapse?: boolean
    collapseOptions?: {
      canExpand?: boolean
      defaultExpand?: boolean
      expandButtonText?: string
      collapseButtonText?: string
      helpMessage?: string | string[]
      visibleRows?: number
    }
  }>(),
  {
    size: 'medium',
    bordered: true,
    column: () => ({ xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 }),
    schema: () => [],
    data: () => ({}),
    emptyText: '-',
    useCollapse: false,
    collapseOptions: () => ({ canExpand: false, defaultExpand: true, expandButtonText: '展开', collapseButtonText: '收起', visibleRows: 1 }),
  }
)

const emit = defineEmits<{
  (e: 'register', action: DescriptionActionType): void
}>()

const innerProps = ref<Partial<DescriptionProps>>({})
const innerData = ref<Record<string, unknown>>({})
const innerSchema = ref<DescriptionSchema[]>([])
const windowWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1920)
const expanded = ref(true)

const DescriptionValueRenderer = defineComponent({
  name: 'EcpDescriptionValueRenderer',
  props: {
    schema: { type: Object, required: true },
    value: { required: false },
    record: { type: Object, required: true },
    emptyText: { type: String, default: '-' },
  },
  setup(rendererProps) {
    return () => {
      const schema = rendererProps.schema as DescriptionSchema
      const record = rendererProps.record as Record<string, unknown>
      const value = rendererProps.value

      const renderTextNode = (text: string) => {
        const tooltipProps = normalizeTooltip(schema, text === rendererProps.emptyText ? value : text, record)
        const contentNode = h('span', text)
        if (!tooltipProps) return contentNode
        return h('el-tooltip', { props: tooltipProps }, [contentNode])
      }

      if (schema.render) {
        const rendered = schema.render(value, record)
        if (rendered == null || rendered === '') return renderTextNode(rendererProps.emptyText)
        if (typeof rendered === 'string' || typeof rendered === 'number') return renderTextNode(String(rendered))
        return rendered as never
      }

      if (Array.isArray(value)) {
        return renderTextNode(value.length ? value.join(', ') : rendererProps.emptyText)
      }

      if (value === null || value === undefined || value === '') {
        return renderTextNode(rendererProps.emptyText)
      }

      if (typeof value === 'object') {
        return renderTextNode(JSON.stringify(value))
      }

      return renderTextNode(String(value))
    }
  },
})

const normalizeTooltip = (schema: DescriptionSchema, value: unknown, record: Record<string, unknown>) => {
  const tooltip = schema.tooltip
  if (!tooltip) return null

  const resolved = typeof tooltip === 'function'
    ? tooltip({ value, record, schema })
    : tooltip
  return normalizeTooltipConfig(resolved, value)
}

const { mergeSettings } = useComponentSetting()
const effectiveProps = computed(() => mergeSettings<DescriptionProps>('ProDescriptions', { ...props, ...innerProps.value }))

const breakpoints = { xxl: 1920, xl: 1200, lg: 992, md: 768, sm: 576 }

const resolveColumn = (column: DescriptionColumn | undefined, width: number) => {
  if (typeof column === 'number') return Math.max(1, column)
  const value = column ?? {}
  if (width >= breakpoints.xxl) return value.xxl ?? value.xl ?? value.lg ?? value.md ?? value.sm ?? value.xs ?? 3
  if (width >= breakpoints.xl) return value.xl ?? value.lg ?? value.md ?? value.sm ?? value.xs ?? 3
  if (width >= breakpoints.lg) return value.lg ?? value.md ?? value.sm ?? value.xs ?? 3
  if (width >= breakpoints.md) return value.md ?? value.sm ?? value.xs ?? 3
  if (width >= breakpoints.sm) return value.sm ?? value.xs ?? 2
  return value.xs ?? 1
}

const currentColumn = computed(() => resolveColumn(effectiveProps.value.column, windowWidth.value))
const effectiveData = computed(() => effectiveProps.value.data ?? innerData.value ?? {})

const visibleSchema = computed(() => {
  return (innerSchema.value.length ? innerSchema.value : effectiveProps.value.schema ?? []).filter((item) => {
    const key = item.dataIndex || item.field
    if (!key) return false
    if (typeof item.show === 'function') {
      return item.show(effectiveData.value)
    }
    return item.show !== false
  }).map((item) => ({
    ...item,
    dataIndex: item.dataIndex || item.field || '',
  }))
})

const normalizedRows = computed<NormalizedRow[]>(() => {
  const rows: NormalizedRow[] = []
  let currentRow: NormalizedRow = { items: [] }
  let used = 0
  const totalColumn = currentColumn.value
  visibleSchema.value.forEach((item, index) => {
    const remainingItems = visibleSchema.value.length - index
    const requested = Math.max(1, Math.min(item.span ?? 1, totalColumn))
    const span = remainingItems === 1 ? totalColumn - used || totalColumn : requested
    if (used + span > totalColumn) {
      rows.push(currentRow)
      currentRow = { items: [] }
      used = 0
    }
    const normalizedSpan = remainingItems === 1 && used < totalColumn ? Math.max(1, totalColumn - used) : span
    currentRow.items.push({ ...item, _span: normalizedSpan })
    used += normalizedSpan
    if (used >= totalColumn) {
      rows.push(currentRow)
      currentRow = { items: [] }
      used = 0
    }
  })
  if (currentRow.items.length > 0) rows.push(currentRow)
  return rows
})

const renderedRows = computed(() => {
  if (!showCollapseButton.value || expanded.value) return normalizedRows.value
  const visibleRows = Math.max(1, effectiveProps.value.collapseOptions?.visibleRows ?? 1)
  return normalizedRows.value.slice(0, visibleRows)
})

const showCollapseButton = computed(() => {
  const visibleRows = Math.max(1, effectiveProps.value.collapseOptions?.visibleRows ?? 1)
  return !!effectiveProps.value.useCollapse && !!effectiveProps.value.collapseOptions?.canExpand && normalizedRows.value.length > visibleRows
})
const showHeader = computed(() => !!effectiveProps.value.title || !!effectiveProps.value.helpMessage || showCollapseButton.value)
const collapseButtonText = computed(() => ({
  expand: effectiveProps.value.collapseOptions?.expandButtonText ?? '展开',
  collapse: effectiveProps.value.collapseOptions?.collapseButtonText ?? '收起',
}))

const bodyStyle = computed(() => ({
  gridTemplateColumns: `repeat(${currentColumn.value}, minmax(0, 1fr))`,
}))

const getItemStyle = (item: DescriptionSchema & { _span: number }) => ({
  gridColumn: `span ${item._span}`,
})

const getLabelStyle = (item: DescriptionSchema) => ({
  minWidth: item.labelMinWidth ? `${item.labelMinWidth}px` : undefined,
  ...(item.labelStyle ?? {}),
})

const getContentStyle = (item: DescriptionSchema) => ({
  minWidth: item.contentMinWidth ? `${item.contentMinWidth}px` : undefined,
  ...(item.contentStyle ?? {}),
})

const getItemValue = (item: DescriptionSchema) => effectiveData.value[item.dataIndex]

const syncSchema = () => {
  innerSchema.value = [...(effectiveProps.value.schema ?? [])]
}

const syncData = () => {
  innerData.value = { ...(effectiveProps.value.data ?? {}) }
}

const setProps = async (descriptionProps: Partial<DescriptionProps>) => {
  innerProps.value = { ...innerProps.value, ...descriptionProps }
  if (descriptionProps.schema) innerSchema.value = [...descriptionProps.schema]
  if (descriptionProps.data) innerData.value = { ...descriptionProps.data }
}

const setData = async (data: Record<string, unknown>) => {
  innerData.value = { ...innerData.value, ...data }
  innerProps.value = { ...innerProps.value, data: innerData.value }
}

const getData = () => ({ ...(effectiveProps.value.data ?? innerData.value ?? {}) })

const descriptionAction: DescriptionActionType = {
  setProps,
  setData,
  getData,
}

defineExpose(descriptionAction)

const updateWindowWidth = () => {
  if (typeof window !== 'undefined') windowWidth.value = window.innerWidth
}

onMounted(() => {
  syncSchema()
  syncData()
  expanded.value = effectiveProps.value.collapseOptions?.defaultExpand !== false
  emit('register', descriptionAction)
  if (typeof window !== 'undefined') {
    window.addEventListener('resize', updateWindowWidth)
  }
})

onUnmounted(() => {
  if (typeof window !== 'undefined') {
    window.removeEventListener('resize', updateWindowWidth)
  }
})

watch(() => effectiveProps.value.schema, syncSchema, { deep: true })
watch(() => effectiveProps.value.data, syncData, { deep: true })
watch(() => effectiveProps.value.collapseOptions?.defaultExpand, (value) => {
  if (value !== undefined) expanded.value = value
})
</script>

<style>
.ecp-pro-descriptions {
  width: 100%;
  box-sizing: border-box;
}

.ecp-pro-descriptions__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
  gap: 12px;
}

.ecp-pro-descriptions__title-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
}

.ecp-pro-descriptions__title {
  font-size: 16px;
  font-weight: 600;
  color: #303133;
}

.ecp-pro-descriptions__help,
.ecp-pro-descriptions__toggle {
  color: #909399;
}

.ecp-pro-descriptions__toggle .el-icon-arrow-down {
  margin-left: 4px;
  transition: transform 0.2s ease;
}

.ecp-pro-descriptions__toggle .el-icon-arrow-down.is-expanded {
  transform: rotate(180deg);
}

.ecp-pro-descriptions__body {
  display: grid;
  border-top: 1px solid #ebeef5;
  border-left: 1px solid #ebeef5;
  overflow: hidden;
}

.ecp-pro-descriptions__body.is-collapsed {
  overflow: hidden;
}

.ecp-pro-descriptions__body:not(.is-bordered) {
  border-top: 0;
  border-left: 0;
  gap: 12px 16px;
}

.ecp-pro-descriptions__item {
  display: flex;
  min-width: 0;
  border-right: 1px solid #ebeef5;
  border-bottom: 1px solid #ebeef5;
}

.ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__item {
  border-right: 0;
  border-bottom: 0;
}

.ecp-pro-descriptions__label,
.ecp-pro-descriptions__content {
  min-width: 0;
  box-sizing: border-box;
  word-break: break-word;
}

.ecp-pro-descriptions__label {
  flex: 0 0 120px;
  padding: 12px 16px;
  color: #606266;
  background: #fafafa;
}

.ecp-pro-descriptions__content {
  flex: 1;
  padding: 12px 16px;
  color: #303133;
  background: #fff;
}

.ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__label {
  flex-basis: auto;
  padding: 0;
  margin-right: 8px;
  background: transparent;
  font-weight: 500;
}

.ecp-pro-descriptions__body:not(.is-bordered) .ecp-pro-descriptions__content {
  padding: 0;
  background: transparent;
}

.ecp-pro-descriptions__body.is-small .ecp-pro-descriptions__label,
.ecp-pro-descriptions__body.is-small .ecp-pro-descriptions__content {
  padding-top: 8px;
  padding-bottom: 8px;
  font-size: 13px;
}

@media (max-width: 767px) {
  .ecp-pro-descriptions__item {
    flex-direction: column;
  }

  .ecp-pro-descriptions__label {
    flex-basis: auto;
  }
}
</style>
