<template>
  <div class="ecp-pro-table-form" v-bind="$attrs">
    <el-form
      ref="formRef"
      :model="formModelRef"
      :rules="mergedRules"
      :label-width="labelWidth"
      :size="formSize"
      :label-position="labelPosition"
      :disabled="disabled"
      v-bind="formProps"
      class="ecp-pro-table-form__form"
    >
      <el-table
        ref="tableRef"
        :data="tableRows"
        :border="bordered"
        :stripe="stripe"
        :size="size"
        :max-height="maxHeight"
        :height="height"
        :row-key="rowKeyFn"
        :row-class-name="rowClassName"
        :default-expand-all="defaultExpandAll"
        :span-method="spanMethodAdapter"
        header-cell-class-name="ecp-pro-table-form__header-cell"
        v-bind="tableProps"
        class="ecp-pro-table-form__table"
        @row-click="handleTableRowClick"
        @row-dblclick="handleTableRowDblclick"
        @sort-change="handleTableSortChange"
        @expand-change="handleTableExpandChange"
      >
        <template v-for="col in flatColumns">
          <!-- 分组列：有 children -->
          <el-table-column
            v-if="col.children && col.children.length > 0"
            :key="'col-' + col._key"
            :label="col.title"
            :min-width="col.minWidth || col.width || 120"
            :width="col.width"
            :align="col.align || 'center'"
            :header-align="col.headerAlign"
            :fixed="col.fixed"
            :cell-style="col.cellStyle"
            :header-cell-style="col.headerCellStyle"
            :cell-class-name="col.cellClassName"
            :header-cell-class-name="col.headerCellClassName"
            :sortable="col.sortable"
            :resizable="col.resizable !== false"
          >
            <template #header>
              <slot :name="`header-${col.key}`" :column="col">
                <span class="ecp-pro-table-form__th-text">
                  <span v-if="col.required" class="ecp-pro-table-form__req">*</span>{{ col.title }}
                </span>
              </slot>
            </template>
            <template v-for="child in col.children">
              <el-table-column
                v-if="!child.hideInTable"
                :key="'col-' + col.key + '-' + child.key"
                :label="child.title"
                :min-width="child.minWidth || child.width || 120"
                :width="child.width"
                :align="child.align || 'center'"
                :header-align="child.headerAlign"
                :fixed="child.fixed"
                :cell-style="child.cellStyle"
                :header-cell-style="child.headerCellStyle"
                :cell-class-name="child.cellClassName"
                :header-cell-class-name="child.headerCellClassName"
                :sortable="child.sortable"
                :resizable="child.resizable !== false"
              >
                <template #header>
                  <slot :name="`header-${child.key}`" :column="child">
                    <span class="ecp-pro-table-form__th-text">
                      <span v-if="child.required" class="ecp-pro-table-form__req">*</span>{{ child.title }}
                    </span>
                  </slot>
                </template>
                <template #default="slotProps">
                  <!-- 渲染函数 -->
                  <template v-if="child.render">
                    <el-form-item
                      :prop="getCellProp(slotProps.row, col, child.key)"
                      :rules="mergedRules[getCellProp(slotProps.row, col, child.key)]"
                      class="ecp-pro-table-form__cell-item"
                    >
                      <template v-if="getRender(child, slotProps.row, col, child.key).isText">
                        <span>{{ getRender(child, slotProps.row, col, child.key).value }}</span>
                      </template>
                      <template v-else>
                        <RenderCell :render-fn="child.render" :render-params="{ row: slotProps.row, col, childKey: child.key }" />
                      </template>
                    </el-form-item>
                  </template>
                  <!-- 插槽列 -->
                  <template v-else-if="child.component === 'slot' && child.slotName">
                    <el-form-item
                      :prop="getCellProp(slotProps.row, col, child.key)"
                      :rules="mergedRules[getCellProp(slotProps.row, col, child.key)]"
                      class="ecp-pro-table-form__cell-item"
                    >
                      <slot
                        :name="`cell-${child.slotName}`"
                        :column="child"
                        :row="slotProps.row"
                        :index="slotProps.$index"
                        :value="getCellValue(slotProps.row, col, child.key)"
                        :update-value="slotUpdateHandler(slotProps, col, child.key)"
                      />
                    </el-form-item>
                  </template>
                  <!-- 内置组件 -->
                  <el-form-item
                    v-else
                    :prop="getCellProp(slotProps.row, col, child.key)"
                    :rules="mergedRules[getCellProp(slotProps.row, col, child.key)]"
                    class="ecp-pro-table-form__cell-item"
                  >
                    <CellEditor
                      :col="child"
                      :value="getCellValue(slotProps.row, col, child.key)"
                      :row="slotProps.row"
                      :index="slotProps.$index"
                      :size="size"
                      :placeholder="col.placeholder || metricPlaceholder"
                      :formActionType="action"
                      @update="setCellValue(slotProps.row, col, child.key, $event)"
                    />
                  </el-form-item>
                </template>
              </el-table-column>
            </template>
          </el-table-column>

          <!-- 叶子列：无 children -->
          <el-table-column
            v-else-if="!col.hideInTable"
            :key="'leaf-' + col._key"
            :label="col.title"
            :min-width="col.minWidth || col.width || 120"
            :width="col.width"
            :align="col.align || 'center'"
            :header-align="col.headerAlign"
            :fixed="col.fixed"
            :cell-style="col.cellStyle"
            :header-cell-style="col.headerCellStyle"
            :cell-class-name="col.cellClassName"
            :header-cell-class-name="col.headerCellClassName"
            :sortable="col.sortable"
            :resizable="col.resizable !== false"
          >
            <template #header>
              <slot :name="`header-${col.key}`" :column="col">
                <span class="ecp-pro-table-form__th-text">
                  <span v-if="col.required" class="ecp-pro-table-form__req">*</span>{{ col.title }}
                </span>
              </slot>
            </template>
            <template #default="slotProps">
              <!-- 渲染函数 -->
              <template v-if="col.render">
                <el-form-item
                  :prop="getCellProp(slotProps.row, col)"
                  :rules="mergedRules[getCellProp(slotProps.row, col)]"
                  class="ecp-pro-table-form__cell-item"
                >
                  <template v-if="getRender(col, slotProps.row, col).isText">
                    <span>{{ getRender(col, slotProps.row, col).value }}</span>
                  </template>
                  <template v-else>
                    <RenderCell :render-fn="col.render" :render-params="{ row: slotProps.row, col }" />
                  </template>
                </el-form-item>
              </template>
              <!-- 插槽列 -->
              <template v-else-if="col.component === 'slot' && col.slotName">
                <el-form-item
                  :prop="getCellProp(slotProps.row, col)"
                  :rules="mergedRules[getCellProp(slotProps.row, col)]"
                  class="ecp-pro-table-form__cell-item"
                >
                  <slot
                    :name="`cell-${col.slotName}`"
                    :column="col"
                    :row="slotProps.row"
                    :index="slotProps.$index"
                    :value="getCellValue(slotProps.row, col)"
                    :update-value="slotUpdateHandler(slotProps, col)"
                  />
                </el-form-item>
              </template>
              <!-- 内置组件 -->
              <el-form-item
                v-else
                :prop="getCellProp(slotProps.row, col)"
                :rules="mergedRules[getCellProp(slotProps.row, col)]"
                class="ecp-pro-table-form__cell-item"
              >
                <CellEditor
                  :col="col"
                  :value="getCellValue(slotProps.row, col)"
                  :row="slotProps.row"
                  :index="slotProps.$index"
                  :size="size"
                  :placeholder="col.placeholder || metricPlaceholder"
                  :formActionType="action"
                  @update="setCellValue(slotProps.row, col, undefined, $event)"
                />
              </el-form-item>
            </template>
          </el-table-column>
        </template>

        <!-- 操作列 -->
        <slot name="action" :add-row="handleAddRow" :remove-row="handleRemoveRow" />
      </el-table>
    </el-form>
  </div>
</template>

<script lang="ts">
export default {
  name: 'ProTableForm',
  model: {
    prop: 'modelValue',
    event: 'update:modelValue',
  },
}
</script>

<script setup lang="ts">
import { computed, ref } from 'vue'
import { useProTableForm } from './useProTableForm'
import { defineComponent } from 'vue'
import type { VNode } from 'vue'
import type {
  ProTableFormColumn,
  ProTableFormColumnChild,
  ProTableFormColumnRender,
} from '../types'
import CellEditor from './CellEditor.vue'

/** 与 useProTableForm 内部 tableRows 元素类型对齐 */
type TableRow = { _index: number }

/** RenderCell：接收 render 函数，在 td 内渲染 VNode，完全脱离 el-table 插槽作用域 */
const RenderCell = defineComponent({
  name: 'RenderCell',
  props: {
    renderFn: { type: Function as unknown as () => ProTableFormColumnRender, required: true },
    renderParams: { type: Object as () => Record<string, unknown>, required: true },
  },
  setup(props) {
    return () => {
      const vnode = props.renderFn(props.renderParams as Parameters<ProTableFormColumnRender>[0])
      return Array.isArray(vnode) ? vnode : (vnode as VNode)
    }
  },
})

/** 单次执行 render，返回 { isText, value } */
function execRenderOnce(
  child: ProTableFormColumn | ProTableFormColumnChild,
  row: Record<string, unknown>,
  col: ProTableFormColumn,
  colKey?: string
): { isText: boolean; value: ReturnType<NonNullable<typeof child.render>> } {
  const value = colKey !== undefined
    ? getCellValue(row as TableRow, col, colKey)
    : getCellValue(row as TableRow, col)
  const result = child.render!({ row, value, column: child })
  const isText = isPrimitive(result)
  return { isText, value: result }
}

/** 行→列key→渲染结果的缓存，避免同一 render 在模板中被多次调用 */
const _renderCache = new WeakMap<object, Map<string, { isText: boolean; value: unknown }>>()

function getRender(
  child: ProTableFormColumn | ProTableFormColumnChild,
  row: Record<string, unknown>,
  col: ProTableFormColumn,
  colKey?: string
): { isText: boolean; value: unknown } {
  if (!_renderCache.has(row as object)) _renderCache.set(row as object, new Map())
  const cache = _renderCache.get(row as object)!
  const cacheKey = col.key + (colKey ? '.' + colKey : '')
  if (!cache.has(cacheKey)) cache.set(cacheKey, execRenderOnce(child, row, col, colKey))
  return cache.get(cacheKey)!
}

/** 是否为基础类型（string / number / boolean / null / undefined） */
function isPrimitive(val: unknown): val is string | number | boolean | null | undefined {
  return val === null || typeof val in { string: 1, number: 1, boolean: 1, bigint: 1, symbol: 1 }
}

const props = withDefaults(
  defineProps<{
    modelValue?: Record<string, unknown>[]
    columns: ProTableFormColumn[]
    metricPlaceholder?: string
    minRows?: number
    rules?: Record<string, unknown>
    labelWidth?: string
    bordered?: boolean
    spanMethod?: (params: {
      row: Record<string, unknown>
      column: { property: string; label: string }
      rowIndex: number
      columnIndex: number
    }) => [number, number] | { rowspan: number; colspan: number } | void
    // ─── el-form 配置 ────────────────────────────────────────────────
    formSize?: 'medium' | 'small' | 'large'
    labelPosition?: 'left' | 'right' | 'top'
    disabled?: boolean
    // ─── el-table 配置 ────────────────────────────────────────────────
    stripe?: boolean
    size?: 'medium' | 'small' | 'large'
    maxHeight?: number | string
    height?: number | string
    rowClassName?: string | ((params: { row: Record<string, unknown>; rowIndex: number }) => string)
    expandRowKeys?: (string | number)[]
    defaultExpandAll?: boolean
    onRowClick?: (row: Record<string, unknown>, event: Event) => void
    onRowDblclick?: (row: Record<string, unknown>, event: Event) => void
    onSortChange?: (sortInfo: { prop: string; order: string }) => void
    onExpandChange?: (row: Record<string, unknown>, expanded: boolean) => void
    tableProps?: Record<string, unknown>
    formProps?: Record<string, unknown>
  }>(),
  {
    modelValue: () => [],
    metricPlaceholder: '请输入',
    minRows: 0,
    labelWidth: '0',
    bordered: true,
    stripe: false,
    size: 'medium',
    defaultExpandAll: false,
  }
)

const emit = defineEmits<{
  (e: 'update:modelValue', v: Record<string, unknown>[]): void
  (e: 'add-row'): void
  (e: 'remove-row', index: number): void
  (e: 'register', action: Record<string, unknown>): void
}>()

const {
  formModelRef,
  currentModelValue,
  mergedRules,
  tableRows,
  rowKeyFn,
  spanMethodAdapter,
  formRef,
  slotUpdateHandler,
  getCellProp,
  getCellValue,
  setCellValue,
  handleAddRow,
  handleRemoveRow,
  getRow,
  setRow,
  validate,
  clearValidate,
} = useProTableForm({
  props,
  emit,
  emitAddRow: () => emit('add-row'),
  emitRemoveRow: (index) => emit('remove-row', index),
})

void formRef

/** 表格行点击 */
function handleTableRowClick(row: TableRow, _column: unknown, event: Event) {
  props.onRowClick?.(row as Record<string, unknown>, event)
}
/** 表格行双击 */
function handleTableRowDblclick(row: TableRow, _column: unknown, event: Event) {
  props.onRowDblclick?.(row as Record<string, unknown>, event)
}
/** 表格排序变化 */
function handleTableSortChange(sortInfo: { prop: string; order: string }) {
  props.onSortChange?.(sortInfo)
}
/** 表格展开变化 */
function handleTableExpandChange(row: TableRow, expanded: boolean) {
  props.onExpandChange?.(row as Record<string, unknown>, expanded)
}

/** 将 columns 扁平化，保留 _key 用于 template v-for key */
const flatColumns = computed<(ProTableFormColumn & { _key: string })[]>(() => {
  return props.columns.map((col, i) => ({
    ...col,
    _key: col.key || `__col-${i}`,
  }))
})

const tableRef = ref<{ clearSelection: () => void } | null>(null)

const action = {
  validate,
  clearValidate,
  addRow: handleAddRow,
  removeRow: handleRemoveRow,
  getRows: () => [...currentModelValue.value],
  getRowCount: () => currentModelValue.value.length,
  getTable: () => tableRef.value,
  getModelValue: () => [...currentModelValue.value],
  setModelValue: (val: Record<string, unknown>[]) => emit('update:modelValue', val),
  getFormRef: () => formRef.value,
  getRow,
  setRow,
}

defineExpose(action)
emit('register', action)
</script>

<style>
.ecp-pro-table-form__form :deep(.el-form-item) {
  margin-bottom: 0;
}
.ecp-pro-table-form__cell-item {
  width: 100%;
}
.ecp-pro-table-form__cell-item :deep(.el-form-item__content) {
  margin-left: 0 !important;
  line-height: normal;
}
/* .ecp-pro-table-form__cell-item :deep(.el-form-item__error) {
  display: none;
} */
.ecp-pro-table-form__req {
  color: #f56c6c;
  margin-right: 2px;
}
/* .ecp-pro-table-form__th-text {
  font-weight: 500;
  color: #606266;
} */
.ecp-pro-table-form__cell-item .el-form-item__error {
  position: relative;
}
</style>
