<script setup lang="ts">
import {
  computed,
  defineComponent,
  h,
  nextTick,
  onActivated,
  onBeforeUnmount,
  onDeactivated,
  onMounted,
  ref,
  shallowRef,
  watch
} from 'vue'
import type { PropType } from 'vue'
import { useI18n } from 'vue-i18n'
import { AgGridVue } from 'ag-grid-vue3'
import type { ColDef, GridApi, GridOptions, GridReadyEvent, ICellRendererParams } from 'ag-grid-community'
import type { FirstDataRenderedEvent } from 'ag-grid-community'
import { myTheme, gridOptions, agGridWrapClass, agGridStyle } from '@/lib/diyAgGridOptions'
import { withReadonlyAutoHeight } from '@/lib/agGridReadonlyAutoHeight'
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
import {roundTo, sumByNumber, sumNullableNumbers} from '@/lib/decimal'
import { parseNumberOrNull } from '@/lib/number'
import { formatThousands, formatThousandsFlexible } from '@/lib/numberFormat'
import {
  ensurePricingMethodDetailRowsForServices,
  getPricingMethodTotalsForService,
  getPricingMethodTotalsForServices,
  type PricingMethodTotals
} from '@/lib/pricingMethodTotals'
import { Pencil, Eraser, Trash2 } from 'lucide-vue-next'
import {
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogOverlay,
  AlertDialogPortal,
  AlertDialogRoot,
  AlertDialogTitle,
} from 'reka-ui'
import { Button } from '@/components/ui/button'
import { TooltipProvider } from '@/components/ui/tooltip'
import { getServiceDictEntries, getServiceDictItemById, getWorkListEntries, isIndustryEnabledByType, getIndustryTypeValue, wholeProcessTasks } from '@/sql'
import type { WorkType } from '@/sql'
import { useTabStore } from '@/pinia/tab'
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
import { useKvStore } from '@/pinia/kv'
import ServiceCheckboxSelector from '@/features/shared/components/ServiceCheckboxSelector.vue'
import { buildProjectScopedSessionKey } from '@/lib/pricingPersistControl'
import { useDataStore } from '@/pinia/zx'

interface ServiceItem {
  id: string
  code: string
  name: string
  type: ServiceMethodType
}

interface DetailRow {
  id: string
  code: string
  name: string
  process: number | null
  investScale: number | null
  landScale: number | null
  serviceFee: number | null
  hourly: number | null
  subtotal?: number | null
  finalFee?: number | null
  remark?: string
  actions?: unknown
}

interface ZxFwViewState {
  selectedIds?: string[]
  selectedCodes?: string[]
  detailRows: DetailRow[]
}

interface WorkContentRowState {
  id: string
  content: string
  type: WorkType
  dictOrder?: number
  serviceGroup?: string
  serviceid?: number | null
  remark: string
  checked: boolean
  custom: boolean
  path: string[]
}

interface XmBaseInfoState {
  projectIndustry?: string
}

interface ServiceMethodType {
  scale?: boolean | null
  onlyCostScale?: boolean | null
  amount?: boolean | null
  workDay?: boolean | null
}

const props = defineProps<{
  contractId: string
  contractName?: string
  projectInfoKey?: string
}>()
const tabStore = useTabStore()
const zxFwPricingStore = useZxFwPricingStore()
const kvStore = useKvStore()
const { t, locale } = useI18n()
const PROJECT_INFO_KEY = computed(() => props.projectInfoKey || 'xm-base-info-v1')
const PRICING_CLEAR_SKIP_PREFIX = 'pricing-clear-skip:'
const PRICING_FORCE_DEFAULT_PREFIX = 'pricing-force-default:'
const PRICING_CLEAR_SKIP_TTL_MS = 5000
const PRICING_TOTALS_OPTIONS = { excludeInvestmentCostAndAreaRows: true } as const
const projectIndustry = ref('')

type ServiceListItem = {
  code?: string
  ref?: string
  name: string
  defCoe: number | null
  isRoad?: boolean
  isRailway?: boolean
  isWaterway?: boolean
  scale?: boolean | null
  onlyCostScale?: boolean | null
  amount?: boolean | null
  workDay?: boolean | null
}

/** 仅保留明确的 boolean，其他值统一转 null。 */
const toNullableBoolean = (value: unknown): boolean | null =>
  typeof value === 'boolean' ? value : null

/** 解析方法开关，未配置时返回默认值。 */
const resolveMethodEnabled = (value: boolean | null | undefined, fallback = true) =>
  typeof value === 'boolean' ? value : fallback

const defaultServiceMethodType = {
  scale: true,
  onlyCostScale: false,
  amount: true,
  workDay: true
}

const serviceDict = computed<ServiceItem[]>(() => {
  const industry = projectIndustry.value
  if (!industry) return []
  const filteredEntries = getServiceDictEntries()
    .map(({ id, item }) => ({ id, item: item as ServiceListItem }))
    .filter(({ item }) => {
      const itemCode = item?.code || item?.ref
      return Boolean(itemCode && item?.name) && item.defCoe !== null && isIndustryEnabledByType(item, getIndustryTypeValue(industry))

    })
  return filteredEntries.map(({ id, item }) => ({
    id,
    code: item.code || item.ref || '',
    name: item.name,
    type: {
      scale: toNullableBoolean(item.scale),
      onlyCostScale: toNullableBoolean(item.onlyCostScale),
      amount: toNullableBoolean(item.amount),
      workDay: toNullableBoolean(item.workDay)
    }
  }))
})

const serviceById = computed(() => new Map(serviceDict.value.map(item => [item.id, item])))
const serviceIdByCode = computed(() => new Map(serviceDict.value.map(item => [item.code, item.id])))
const serviceIdSignature = computed(() => serviceDict.value.map(item => item.id).join('|'))
const fixedBudgetRow: Pick<DetailRow, 'id' | 'code' | 'name'> = { id: 'fixed-budget-c', code: '', name: t('htZxFw.subtotal') }

/** 判断是否固定汇总行（小计行）。 */
const isFixedRow = (row?: DetailRow | null) => row?.id === fixedBudgetRow.id

/**
 * 统一把 store/raw row 转成页面使用的 DetailRow 结构。
 * 被 detailRows 计算属性和 getCurrentContractState 复用，避免重复映射逻辑。
 */
const normalizeDetailRow = (row: Partial<DetailRow>): DetailRow => {
  const result = {
    id: String(row.id || ''),
    code: row.code || '',
    name: row.name || '',
    process: String(row.id || '') === fixedBudgetRow.id ? null : (Number(row.process) === 1 ? 1 : 0),
    investScale: typeof row.investScale === 'number' ? row.investScale : null,
    landScale: typeof row.landScale === 'number' ? row.landScale : null,
    serviceFee: typeof row.serviceFee === 'number' ? row.serviceFee : null,
    hourly: typeof row.hourly === 'number' ? row.hourly : null,
    subtotal: typeof row.subtotal === 'number' ? row.subtotal : null,
    finalFee: typeof row.finalFee === 'number' ? row.finalFee : null,
    remark: String(row.remark || ''),
    actions: row.actions
  }

  return result
}

const fallbackDetailRows = ref<DetailRow[]>([])
const selectedIds = computed<string[]>(() => zxFwPricingStore.contracts[props.contractId]?.selectedIds || [])
const detailRows = computed<DetailRow[]>(() => {
  /*const contract = zxFwPricingStore.contracts[props.contractId]

  const rawRows = contract?.detailRows || []
  // 创建一个空数组来存放处理后的数据
  const result = []

  // 遍历原始数据数组
  for (let i = 0; i < rawRows.length; i++) {
    const row = rawRows[i]
    // 处理每一行数据并添加到结果数组
    result.push(normalizeDetailRow(row))
  }
  return result*/
  return fallbackDetailRows.value;
})

/**
 * 获取当前合同状态的“可变副本”。
 * 所有写回前都先读这里，避免直接修改 store 引用对象。
 */
const getCurrentContractState = (): ZxFwViewState => {
  const current = zxFwPricingStore.getContractState(props.contractId)
  if (current) {
    return {
      selectedIds: Array.isArray(current.selectedIds) ? [...current.selectedIds] : [],
      selectedCodes: Array.isArray(current.selectedCodes) ? [...current.selectedCodes] : [],
      detailRows: (current.detailRows || []).map(row => normalizeDetailRow(row as Partial<DetailRow>))
    }
  }
  return {
    selectedIds: [],
    detailRows: []
  }
}

/** 统一写回当前合同状态到 store。 */
const setCurrentContractState = async (nextState: ZxFwViewState) => {
  await zxFwPricingStore.setContractState(props.contractId, nextState)
}

const gridApi = shallowRef<GridApi<DetailRow> | null>(null)
/**
 * 记录 grid api，供编辑后局部刷新固定行使用
 * @param event AG Grid 初始化完成事件
 */
const onGridReady = (event: GridReadyEvent<DetailRow>) => {
  gridApi.value = event.api
  scheduleAutoRowHeights()
}

let autoHeightSyncTimer: ReturnType<typeof setTimeout> | null = null
/**
 * 检查 AG Grid API 是否仍然有效（未被销毁）
 * @param api AG Grid API 实例
 * @returns API 是否可用
 */
const isGridApiAlive = (api: GridApi<DetailRow> | null | undefined): api is GridApi<DetailRow> =>
  Boolean(api && !api.isDestroyed?.())

/**
 * 同步 AG Grid 行高自适应
 * 等待下一个 tick 后触发 AG Grid 重新计算行高
 */
const syncAutoRowHeights = async () => {
  await nextTick()
  const api = gridApi.value
  if (!isGridApiAlive(api)) return
  api.onRowHeightChanged()
}

/**
 * 调度行高自适应同步（防抖 0ms）
 * 避免频繁触发导致性能问题
 */
const scheduleAutoRowHeights = () => {
  if (autoHeightSyncTimer) clearTimeout(autoHeightSyncTimer)
  autoHeightSyncTimer = setTimeout(() => {
    autoHeightSyncTimer = null
    if (!isGridApiAlive(gridApi.value)) return
    void syncAutoRowHeights()
  }, 0)
}

/**
 * AG Grid 首次数据渲染完成回调
 * 触发首次行高自适应同步
 * @param _event 首次数据渲染事件
 */
const onFirstDataRendered = (_event: FirstDataRenderedEvent<DetailRow>) => {
  scheduleAutoRowHeights()
}

const clearConfirmOpen = ref(false)
const pendingClearServiceId = ref<string | null>(null)
const deleteConfirmOpen = ref(false)
const pendingDeleteServiceId = ref<string | null>(null)

/**
 * 计算待清空服务的显示名称
 * 优先从当前明细行获取，其次从服务词典获取
 */
const pendingClearServiceName = computed(() => {
  if (!pendingClearServiceId.value) return ''
  const row = detailRows.value.find(item => item.id === pendingClearServiceId.value)
  if (row) return `${row.code}${row.name}`
  const dict = serviceById.value.get(pendingClearServiceId.value)
  if (dict) return `${dict.code}${dict.name}`
  return pendingClearServiceId.value
})

/**
 * 计算待删除服务的显示名称
 * 优先从当前明细行获取，其次从服务词典获取
 */
const pendingDeleteServiceName = computed(() => {
  if (!pendingDeleteServiceId.value) return ''
  const row = detailRows.value.find(item => item.id === pendingDeleteServiceId.value)
  if (row) return `${row.code}${row.name}`
  const dict = serviceById.value.get(pendingDeleteServiceId.value)
  if (dict) return `${dict.code}${dict.name}`
  return pendingDeleteServiceId.value
})

/** 清空确认框开关回调。 */
const handleClearConfirmOpenChange = (open: boolean) => {
  clearConfirmOpen.value = open
}

/** 删除确认框开关回调。 */
const handleDeleteConfirmOpenChange = (open: boolean) => {
  deleteConfirmOpen.value = open
}

/** 触发“恢复默认”二次确认。 */
const requestClearRow = (row: DetailRow) => {
  if (isFixedRow(row)) return
  pendingClearServiceId.value = row.id
  clearConfirmOpen.value = true
}

/** 执行“恢复默认”并关闭确认框。 */
const confirmClearRow = async () => {
  const id = pendingClearServiceId.value
  if (!id) return
  const row = detailRows.value.find(item => item.id === id)
  if (!row || isFixedRow(row)) {
    clearConfirmOpen.value = false
    pendingClearServiceId.value = null
    return
  }
  await clearRowValues(row)
  clearConfirmOpen.value = false
  pendingClearServiceId.value = null
}

/** 触发“删除服务”二次确认。 */
const requestDeleteRow = (row: DetailRow) => {
  if (isFixedRow(row)) return
  pendingDeleteServiceId.value = row.id
  deleteConfirmOpen.value = true
}

/** 执行删除并关闭确认框。 */
const confirmDeleteRow = async () => {
  const id = pendingDeleteServiceId.value
  if (!id) return
  const row = detailRows.value.find(item => item.id === id)
  if (!row) {
    deleteConfirmOpen.value = false
    pendingDeleteServiceId.value = null
    return
  }
  await removeRow(row)
  deleteConfirmOpen.value = false
  pendingDeleteServiceId.value = null
}


/** 表格数字输入解析器（保留 3 位小数）。 */
const numericParser = (newValue: any): number | null => {
  return parseNumberOrNull(newValue, { precision: 3 })
}

/**
 * 判断两个可空数值是否相等（考虑精度）
 * @param a 第一个数值
 * @param b 第二个数值
 * @param precision 小数精度（默认 3 位）
 * @returns 是否相等
 */
const isSameNullableNumber = (a: number | null | undefined, b: number | null | undefined, precision = 3) => {
  if (a == null && b == null) return true
  if (a == null || b == null) return false
  return roundTo(a, precision) === roundTo(b, precision)
}

/**
 * 读取服务词典中的计价方法开关（规模/工作量/工时），并应用默认值。
 * 该方法被 sanitizePricingTotalsByService 复用。
 */
const getServiceMethodTypeById = (serviceId: string) => {
  const type = serviceById.value.get(serviceId)?.type
  const scale = resolveMethodEnabled(type?.scale, defaultServiceMethodType.scale)
  const onlyCostScale = resolveMethodEnabled(type?.onlyCostScale, defaultServiceMethodType.onlyCostScale)
  const amount = resolveMethodEnabled(type?.amount, defaultServiceMethodType.amount)
  const workDay = resolveMethodEnabled(type?.workDay, defaultServiceMethodType.workDay)
  return { scale, onlyCostScale, amount, workDay }
}

/**
 * 按服务方法开关过滤计价合计。
 * 被 clearRowValues/fillPricingTotalsForServiceIds 等入口复用。
 */
const sanitizePricingTotalsByService = (serviceId: string, totals: PricingMethodTotals): PricingMethodTotals => {
  const methodType = getServiceMethodTypeById(serviceId)
  const isScaleEnabled = methodType.scale
  const isLandScaleEnabled = isScaleEnabled && !methodType.onlyCostScale
  return {
    investScale: isScaleEnabled ? totals.investScale : null,
    landScale: isLandScaleEnabled ? totals.landScale : null,
    workload: methodType.amount ? totals.workload : null,
    hourly: methodType.workDay ? totals.hourly : null
  }
}

/**
 * 对 4 个计价字段做开关过滤（例如 onlyCostScale 会禁用用地规模法）。
 * 主要在“选择服务、加载历史值”时复用。
 */
const sanitizePricingFieldsByService = (
  serviceId: string,
  values: Pick<DetailRow, 'investScale' | 'landScale' | 'workload' | 'hourly'>
) => {
  const sanitized = sanitizePricingTotalsByService(serviceId, values)
  return {
    investScale: sanitized.investScale,
    landScale: sanitized.landScale,
    workload: sanitized.workload,
    hourly: sanitized.hourly
  }
}

type PricingMethodField = 'investScale' | 'landScale' | 'serviceFee' | 'hourly'

/**
 * 计算某列在“非固定行”上的合计。
 * 被 getMethodTotal 和固定行汇总逻辑复用。
 */
const getMethodTotalFromRows = (
  rows: DetailRow[],
  field: PricingMethodField
) => sumNullableNumbers(
  rows
    .filter(row => !isFixedRow(row))
    .map(row => row[field])
)

/** 当前页面行数据中某计价列总计（仅非固定行）。 */
const getMethodTotal = (field: PricingMethodField) => {
  return getMethodTotalFromRows(detailRows.value, field)
}

/**
 * 生成 4 个计价法列的公共配置，减少重复定义。
 * 值来源统一：固定行取列合计，普通行取自身字段。
 */
const createMethodColumn = (
  headerName: string,
  field: PricingMethodField,
  minWidth: number
): ColDef<DetailRow> => ({
  headerName,
  field,
  minWidth,
  flex: 1.5,
  editable: false,
  cellClassRules: {
    'zxfw-number-cell': () => true,
    'ag-right-aligned-cell': () => true
  },
  valueGetter: params => {
    if (!params.data) return null
    // return getMethodTotal(field)
    // if (isFixedRow(params.data)) return getMethodTotal(field)
    const value = params.data[field]
    return value
  },
  valueParser: params => numericParser(params.newValue),
  valueFormatter: params => (params.value == null ? '' : formatThousandsFlexible(params.value, 3))
})

/**
 * 获取某服务对应四个计价页的存储键
 * @param serviceId 服务 ID
 * @returns 四个计价方法的存储键对象
 */
const getPricingPaneStorageKeys = (serviceId: string) =>
  zxFwPricingStore.getServicePricingStorageKeys(props.contractId, serviceId)

/**
 * 清空某服务在 4 个计价页的缓存与持久化数据。
 * 会写入短期 skip/force 标记，避免“刚删完又被旧数据回填”。
 */
const clearPricingPaneValues = async (serviceId: string) => {
  const keys = getPricingPaneStorageKeys(serviceId)
  const clearIssuedAt = Date.now()
  const skipUntil = clearIssuedAt + PRICING_CLEAR_SKIP_TTL_MS
  const skipToken = `${clearIssuedAt}:${skipUntil}`
  for (const key of keys) {
    sessionStorage.setItem(buildProjectScopedSessionKey(PRICING_CLEAR_SKIP_PREFIX, key), skipToken)
    sessionStorage.setItem(buildProjectScopedSessionKey(PRICING_FORCE_DEFAULT_PREFIX, key), String(skipUntil))
  }
  zxFwPricingStore.removeAllServicePricingMethodStates(props.contractId, serviceId)
  // Reset后会立刻有逻辑读取IndexedDB计算默认值，这里强制同步删除持久层，避免读到旧数据。
  await Promise.all(keys.map(key => kvStore.removeItem(key)))
}

/**
 * 恢复单行到默认计价结果（“恢复默认”按钮）。
 * 执行顺序：关闭编辑页 -> 清缓存/持久层 -> 重新生成默认明细 -> 回填合计。
 */
const clearRowValues = async (row: DetailRow) => {
  if (isFixedRow(row)) return

  // 若该服务编辑页已打开，先关闭，避免子页面卸载时把旧数据写回。
  tabStore.removeTab(`zxfw-edit-${props.contractId}-${row.id}`)
  await nextTick()
  await clearPricingPaneValues(row.id)
  await ensurePricingMethodDetailRowsForServices({
    contractId: props.contractId,
    serviceIds: [row.id],
    options: PRICING_TOTALS_OPTIONS
  })
  const totals = await getPricingMethodTotalsForService({
    contractId: props.contractId,
    serviceId: row.id,
    options: PRICING_TOTALS_OPTIONS
  })
  const sanitizedTotals = sanitizePricingTotalsByService(row.id, totals)
  const currentState = getCurrentContractState()
  const clearedRows = currentState.detailRows.map(item => {
    if (item.id !== row.id) return item
    const newSubtotal = sumNullableNumbers([sanitizedTotals.investScale, sanitizedTotals.landScale, sanitizedTotals.workload, sanitizedTotals.hourly])
    return {
      ...item,
      investScale: sanitizedTotals.investScale,
      landScale: sanitizedTotals.landScale,
      workload: sanitizedTotals.workload,
      hourly: sanitizedTotals.hourly,
      subtotal: newSubtotal != null ? roundTo(newSubtotal, 2) : null,
      finalFee: newSubtotal != null ? roundTo(newSubtotal, 2) : null
    }
  })
  await setCurrentContractState({
    ...currentState,
    detailRows: applyFixedRowTotals(clearedRows)
  })
}

/** 打开服务编辑子页。 */
const openEditTab = (row: DetailRow) => {
  const serviceType = serviceById.value.get(row.id)?.type
  tabStore.openTab({
    id: `zxfw-edit-${props.contractId}-${row.id}`,
    title: t('htZxFw.editTabTitle', { name: `${row.code}${row.name}` }),
    componentName: 'ZxFwView',
    props: {
      contractId: props.contractId,
      contractName: props.contractName || '',
      serviceId: row.id,
      fwName: row.code + row.name,
      type: serviceType ? { ...serviceType } : undefined,
      projectInfoKey: props.projectInfoKey
    }
  })
}

/** 删除单行服务（本质是更新 selectedIds）。 */
const removeRow = async (row: DetailRow) => {
  const nextIds = selectedIds.value.filter(id => id !== row.id.split('-').at(-1))
  await handleServiceSelectionChange(nextIds)
}

/**
 * AG Grid 操作列单元格渲染器
 * 渲染编辑、恢复默认、删除三个操作按钮
 */
const ActionCellRenderer = defineComponent({
  name: 'ActionCellRenderer',
  props: {
    params: {
      type: Object as PropType<ICellRendererParams<DetailRow>>,
      required: true
    }
  },
  setup(props) {
    return () => {
      if (props.params.node?.rowPinned === 'bottom') {
        return null
      }

      if (isFixedRow(props.params.data)) return null
      return h('div', { class: 'zxfw-action-wrap' }, [
        h('div', { class: 'zxfw-action-group' }, [
          h('button', { class: 'zxfw-action-btn', 'data-action': 'edit', type: 'button' }, [
            h(Pencil, { size: 13, 'aria-hidden': 'true' }),
            h('span', t('htZxFw.edit'))
          ]),
          /*h('button', { class: 'zxfw-action-btn', 'data-action': 'clear', type: 'button' }, [
            h(Eraser, { size: 13, 'aria-hidden': 'true' }),
            h('span', t('htZxFw.resetDefault'))
          ]),*/
          h('button', { class: 'zxfw-action-btn zxfw-action-btn--danger', 'data-action': 'delete', type: 'button' }, [
            h(Trash2, { size: 13, 'aria-hidden': 'true' }),
            h('span', t('htZxFw.delete'))
          ])
        ])
      ])
    }
  }
})

/** 定义每个服务的工作环节类型 */
const PROCESS_TYPE_CONFIG: Record<string, 'none' | 'radio' | 'review_only'> = {
  'D1': 'none',         // 全过程造价咨询 - 无选项
  'D2-1': 'none',       // 前期阶段造价咨询 - 无选项
  'D2-2': 'none',       // 实施阶段造价咨询 - 无选项
  'D3-1': 'radio',      // 投资估算 - 单选编制/审查
  'D3-2': 'radio',      // 设计概算 - 单选编制/审查
  'D3-3': 'radio',      // 工程预算 - 单选编制/审查
  'D3-4': 'radio',      // 招标工程量清单及清单预算 - 单选编制/审查
  'D3-6-1': 'radio',    // 工程结算 - 单选编制/审查
  'D3-7': 'radio',      // 竣工决算 - 单选编制/审查
  'D4-6': 'radio',      // 工程造价鉴定报告 - 单选编制/审查
  'D5-1': 'review_only' // 工程经济鉴定评估报告 - 默认审查，不可编辑
}

/**
 * AG Grid 工作环节单元格渲染器
 * 根据服务类型渲染不同的工作环节选项（无选项/单选/仅审查）
 */
const ProcessCellRenderer = defineComponent({
  name: 'ProcessCellRenderer',
  props: {
    params: {
      type: Object as PropType<ICellRendererParams<DetailRow>>,
      required: true
    }
  },
  setup(props) {
    return () => {
      const row = props.params.data
      if (!row || isFixedRow(row)) return null
      
      const processType = PROCESS_TYPE_CONFIG[row.code] || 'radio'
      const processValue = row.process === 1 ? 1 : 0
      const isEnglish = locale.value.startsWith('en')
      
      // 无选项，不可编辑
      if (processType === 'none') {
        return h('div', { class: 'zxfw-process-cell' }, [
          h('span', { class: 'text-gray-400 text-sm' }, '无选项，不可编辑')
        ])
      }
      
      // 默认为审查，不可编辑
      if (processType === 'review_only') {
        return h('div', { class: 'zxfw-process-cell' }, [
          h('span', { class: 'text-gray-400 text-sm' }, '默认为审查，不可编辑')
        ])
      }
      
      // 单选：编制/审查
      const onSelect = (event: Event, value: 0 | 1) => {
        event.stopPropagation()
        void props.params.context?.onSetProcess?.(row.id, value)
      }
      const radioName = `process-${row.id}`
      
      return h('div', { class: ['zxfw-process-cell', isEnglish ? 'zxfw-process-cell--en' : ''] }, [
        h('label', { class: ['zxfw-process-option', isEnglish ? 'zxfw-process-option--en' : ''] }, [
          h('input', {
            type: 'radio',
            name: radioName,
            checked: processValue === 0,
            class: 'cursor-pointer h-4 w-4',
            onClick: (event: Event) => event.stopPropagation(),
            onChange: (event: Event) => onSelect(event, 0)
          }),
          h('span', '编制')
        ]),
        h('label', { class: ['zxfw-process-option', isEnglish ? 'zxfw-process-option--en' : ''] }, [
          h('input', {
            type: 'radio',
            name: radioName,
            checked: processValue === 1,
            class: 'cursor-pointer h-4 w-4',
            onClick: (event: Event) => event.stopPropagation(),
            onChange: (event: Event) => onSelect(event, 1)
          }),
          h('span', '审查')
        ])
      ])
    }
  }
})

/**
 * AG Grid 名称列单元格渲染器
 * 包装服务名称，支持自动换行
 */
const NameCellRenderer = defineComponent({
  name: 'NameCellRenderer',
  props: {
    params: {
      type: Object as PropType<ICellRendererParams<DetailRow>>,
      required: true
    }
  },
  setup(props) {
    return () => {
      const row = props.params.data
      if (!row || isFixedRow(row)) return ''
      return h('div', { class: 'zxfw-name-wrap' }, String(props.params.value || row.name || ''))
    }
  }
})

const columnDefs: ColDef<DetailRow>[] = [
  {
    headerName: t('htZxFw.columns.code'),
    field: 'code',
    minWidth: 50,
    maxWidth: 100,
    valueGetter: params => {
      if (!params.data) return ''
      if (isFixedRow(params.data)) return t('htZxFw.subtotal')
      return params.data.code
    },
    // --- 新增配置：动态计算列跨度 ---
    colSpan: (params) => {
      // 如果是 pinned 行且是我们标记的汇总行
      if (params.node?.rowPinned === 'bottom') {
        // 跨越 3 列（编码 + 名称 + 工作环节）
        return 3;
      }
      // 普通行返回 1
      return 1;
    },

    // colSpan: params => (params.data && isFixedRow(params.data) ? 3 : 1)
  },
  {
    headerName: t('htZxFw.columns.name'),
    field: 'name',
    minWidth: 150,
    flex: 3,
    cellClass: 'zxfw-name-cell',
    wrapText: true,
    autoHeight: true,
    cellStyle: { 'line-height': 1.6 },
    cellRenderer: NameCellRenderer,
    valueGetter: params => {
      if (!params.data) return ''
      if (isFixedRow(params.data)) return ''
      return params.data.name
    }
  },
  {
    headerName: t('htZxFw.columns.process'),
    field: 'process',
    headerClass: 'ag-center-header zxfw-process-header',
    minWidth: locale.value.startsWith('en') ? 118 : 150,
    maxWidth: locale.value.startsWith('en') ? 136 : 200,
    flex: 1,
    editable: false,
    sortable: false,
    filter: false,
    cellStyle: {
      textAlign: 'center',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center'
    },
    valueGetter: params => {
      if (!params.data || isFixedRow(params.data)) return null
      return params.data.process === 1 ? 1 : 0
    },
    cellRenderer: ProcessCellRenderer
  },
  createMethodColumn(t('htZxFw.columns.investScale'), 'investScale', 100),
  // createMethodColumn(t('htZxFw.columns.landScale'), 'landScale', 100),
  createMethodColumn(t('htZxFw.columns.workload'), 'serviceFee', 90),
  createMethodColumn(t('htZxFw.columns.hourly'), 'hourly', 90),
  {
    headerName: t('htZxFw.columns.subtotal'),
    field: 'subtotal',
    flex: 2,
    minWidth: 100,
    editable: false,
    cellClassRules: {
      'zxfw-number-cell': () => true,
      'ag-right-aligned-cell': () => true
    },
    valueGetter: params => {
      if (!params.data) return null
      return params.data.subtotal
    },
    valueFormatter: params => (params.value == null ? '' : formatThousands(params.value, 2))
  },
  {
    headerName: t('htZxFw.columns.finalFee'),
    field: 'finalFee',
    headerTooltip: t('htZxFw.columns.finalFeeTooltip'),
    flex: 2,
    minWidth: 110,
    editable: (params) => {
      if (params.node?.rowPinned === 'bottom') {
        return false
      } else {
        return true
      }
    },
    cellClassRules: {
      'zxfw-number-cell': () => true,
      'ag-right-aligned-cell': () => true
    },
    valueGetter: params => {
      if (!params.data) return null
      return params.data.finalFee
    },
    // valueSetter: params => {
    //   const parsed = parseNumberOrNull(params.newValue, { precision: 2 })
    //   const val = parsed != null ? roundTo(parsed, 2) : null
    //   if (params.data.finalFee === val) return false
    //   params.data.finalFee = val
    //   return true
    // },
    valueParser: params => {
      const parsed = parseNumberOrNull(params.newValue, { precision: 2 })
      return parsed != null ? roundTo(parsed, 2) : null
    },
    valueFormatter: params => (params.value == null ? '' : formatThousands(params.value, 2))
  },
  {
    headerName: t('htZxFw.columns.remark'),
    field: 'remark',
    minWidth: 160,
    flex: 1.8,
    editable: params => !isFixedRow(params.data),
    cellEditor: 'agLargeTextCellEditor',
    cellEditorPopup: true,
    cellEditorParams: {
      maxLength: 500,
      rows: 8,
      cols: 48
    },
    wrapText: true,
    autoHeight: true,
    cellStyle: { whiteSpace: 'normal', lineHeight: '1.4' },
    valueGetter: params => {
      if (!params.data || isFixedRow(params.data)) return ''
      return String(params.data.remark || '')
    },
    valueFormatter: params => (params.value == null ? '' : String(params.value)),
    cellClass: params => (!isFixedRow(params.data) ? 'remark-wrap-cell' : ''),
    cellClassRules: {
      'editable-cell-empty': params => !isFixedRow(params.data) && (params.value == null || String(params.value).trim() === '')
    }
  },
  {
    headerName: t('htZxFw.columns.actions'),
    field: 'actions',
    minWidth: 250,
    flex: 1.8,
    maxWidth: 320,
    editable: false,
    sortable: false,
    filter: false,
    suppressMovable: true,
    cellRenderer: ActionCellRenderer/*,
    valueFormatter: (params) => {
      if (params.node?.rowPinned === 'bottom') {
        return ' '
      }
    }*/
  }
]
const gridColumnDefs = computed(() => withReadonlyAutoHeight(columnDefs))

const detailGridOptions: GridOptions<DetailRow> = {
  ...gridOptions,
  treeData: false,
  getDataPath: undefined,
  context: {
    onSetProcess: async (rowId: string, value: 0 | 1) => {
      const currentState = getCurrentContractState()
      let changed = false
      const nextRows = currentState.detailRows.map(row => {
        if (isFixedRow(row) || String(row.id) !== String(rowId)) return row
        const nextValue = value === 1 ? 1 : 0
        if ((row.process === 1 ? 1 : 0) === nextValue) return row
        changed = true
        return {
          ...row,
          process: nextValue
        }
      })
      if (!changed) return
      await setCurrentContractState({
        ...currentState,
        detailRows: nextRows
      })
    }
  },
  onCellClicked: async params => {
    if (params.colDef.field !== 'actions' || !params.data || isFixedRow(params.data)) return
    const target = params.event?.target as HTMLElement | null
    const btn = target?.closest('button[data-action]') as HTMLButtonElement | null
    const action = btn?.dataset.action
    if (action === 'clear') {
      requestClearRow(params.data)
      return
    }
    if (action === 'edit') {
      openEditTab(params.data)
      return
    }
    if (action === 'delete') {
      requestDeleteRow(params.data)
    }
  }
}

/**
 * 只重算固定行（小计行）汇总，不覆盖普通行 finalFee。
 * 主要用于用户手动编辑 finalFee 后的同步场景。
 */
const applyFixedRowSummary = (rows: DetailRow[]) => {
  const nextInvestScale = getMethodTotalFromRows(rows, 'investScale')
  const nextLandScale = getMethodTotalFromRows(rows, 'landScale')
  const nextWorkload = getMethodTotalFromRows(rows, 'workload')
  const nextHourly = getMethodTotalFromRows(rows, 'hourly')
  const totalFinalFee = sumNullableNumbers(rows.filter(r => !isFixedRow(r)).map(r => r.finalFee))
  return rows.map(row =>
    isFixedRow(row)
      ? {
        ...row,
        investScale: nextInvestScale,
        landScale: nextLandScale,
        workload: nextWorkload,
        hourly: nextHourly,
        subtotal: sumNullableNumbers([nextInvestScale, nextLandScale, nextWorkload, nextHourly]),
        finalFee: totalFinalFee != null ? roundTo(totalFinalFee, 2) : null
      }
      : row
  )
}

/**
 * 计价法金额发生变化时调用：
 * 1) 普通行 finalFee 强制同步 subtotal
 * 2) 固定行汇总（四列+确认金额）统一重算
 */
const applyFixedRowTotals = (rows: DetailRow[]) => {
  const syncedRows = rows.map(row => {
    if (isFixedRow(row)) return row
    const rowSubtotal = sumNullableNumbers([row.investScale, row.landScale, row.workload, row.hourly])
    return {
      ...row,
      finalFee: rowSubtotal != null ? roundTo(rowSubtotal, 2) : null
    }
  })
  return applyFixedRowSummary(syncedRows)
}

/** 获取当前已选服务 id（排除固定小计行）。 */
const getSelectedServiceIdsWithoutFixed = () =>
  detailRows.value
    .filter(row => !isFixedRow(row))
    .map(row => String(row.id))

/**
 * 为当前选中服务确保“计价明细行”存在（必要时创建默认明细）。
 * 初始化、勾选变化后都会调用。
 */
const ensurePricingDetailRowsForCurrentSelection = async () => {
  const serviceIds = getSelectedServiceIdsWithoutFixed()
  if (serviceIds.length === 0) return
  await ensurePricingMethodDetailRowsForServices({
    contractId: props.contractId,
    serviceIds,
    options: PRICING_TOTALS_OPTIONS
  })
}

/**
 * 刷新指定服务的 4 个计价法合计，并回写到 zxFw 明细。
 * 计价法变更场景统一走这里，最终会触发 applyFixedRowTotals。
 */
const fillPricingTotalsForServiceIds = async (serviceIds: string[]) => {
  
  const currentState = getCurrentContractState()
  const targetIds = Array.from(
    new Set(
      serviceIds.filter(id =>
        currentState.detailRows.some(row => !isFixedRow(row) && String(row.id) === String(id))
      )
    )
  )

  if (targetIds.length === 0) {
    await setCurrentContractState({
      ...currentState,
      detailRows: applyFixedRowSummary(currentState.detailRows)
    })
    return
  }

  await ensurePricingMethodDetailRowsForServices({
    contractId: props.contractId,
    serviceIds: targetIds,
    options: PRICING_TOTALS_OPTIONS
  })

  const totalsByServiceId = await getPricingMethodTotalsForServices({
    contractId: props.contractId,
    serviceIds: targetIds,
    options: PRICING_TOTALS_OPTIONS
  })

  const targetSet = new Set(targetIds.map(id => String(id)))
  const nextRows = currentState.detailRows.map(row => {
    if (isFixedRow(row) || !targetSet.has(String(row.id))) return row
    const totalsRaw = totalsByServiceId.get(String(row.id))
    const totals = totalsRaw ? sanitizePricingTotalsByService(String(row.id), totalsRaw) : null
    if (!totals) return row
    const newSubtotal = sumNullableNumbers([totals.investScale, totals.landScale, totals.workload, totals.hourly])
    const methodChanged = !(
      isSameNullableNumber(row.investScale, totals.investScale)
      && isSameNullableNumber(row.landScale, totals.landScale)
      && isSameNullableNumber(row.workload, totals.workload)
      && isSameNullableNumber(row.hourly, totals.hourly)
    )
    return {
      ...row,
      investScale: totals.investScale,
      landScale: totals.landScale,
      workload: totals.workload,
      hourly: totals.hourly,
      finalFee: methodChanged
        ? (newSubtotal != null ? roundTo(newSubtotal, 2) : null)
        : row.finalFee
    }
  })

  await setCurrentContractState({
    ...currentState,
    detailRows: applyFixedRowSummary(nextRows)
  })
}

/**
 * 应用服务勾选结果（新增/删除服务）。
 * 会保留已有行可复用信息，并确保固定行始终存在。
 */
const applySelection = async (codes: string[]) => {
  const currentState = getCurrentContractState()
  const prevSelectedSet = new Set(currentState.selectedIds || [])
  const uniqueIds = Array.from(new Set(codes)).filter(
    id => serviceById.value.has(id) && id !== fixedBudgetRow.id
  )
  const existingMap = new Map(currentState.detailRows.map(row => [row.id, row]))

  const baseRows: DetailRow[] = uniqueIds
    .map<DetailRow | null>(id => {
      
      const dictItem = serviceById.value.get(id)
      if (!dictItem) return null

      const old = existingMap.get(id)
      
      const nextValues = sanitizePricingFieldsByService(id, {
        investScale: old?.investScale ?? null,
        landScale: old?.landScale ?? null,
        workload: old?.workload ?? null,
        hourly: old?.hourly ?? null
      })
      return {
        id: old?.id || id,
        code: dictItem.code,
        name: dictItem.name,
        process: old?.process === 1 ? 1 : 0,
        investScale: nextValues.investScale,
        landScale: nextValues.landScale,
        workload: nextValues.workload,
        hourly: nextValues.hourly,
            subtotal: typeof old?.subtotal === 'number' ? old.subtotal : null,

        finalFee: typeof old?.finalFee === 'number' ? old.finalFee : null,
        remark: String(old?.remark || '')
      }
    })
    .filter((row): row is DetailRow => row !== null)

  const orderMap = new Map(serviceDict.value.map((item, index) => [item.id, index]))
  baseRows.sort((a, b) => (orderMap.get(a.id) || 0) - (orderMap.get(b.id) || 0))

  const fixedOld = existingMap.get(fixedBudgetRow.id)
  const fixedRow: DetailRow = {
    id: fixedOld?.id || fixedBudgetRow.id,
    code: fixedBudgetRow.code,
    name: fixedBudgetRow.name,
    process: null,
    investScale: typeof fixedOld?.investScale === 'number' ? fixedOld.investScale : null,
    landScale: typeof fixedOld?.landScale === 'number' ? fixedOld.landScale : null,
    workload: typeof fixedOld?.workload === 'number' ? fixedOld.workload : null,
    hourly: typeof fixedOld?.hourly === 'number' ? fixedOld.hourly : null,
    subtotal: typeof fixedOld?.subtotal === 'number' ? fixedOld.subtotal : null,
    finalFee: typeof fixedOld?.finalFee === 'number' ? fixedOld.finalFee : null,
    remark: '',
    actions: null
  }

  const removedIds = Array.from(prevSelectedSet).filter(id => !uniqueIds.includes(id))
  for (const id of removedIds) {
    tabStore.removeTab(`zxfw-edit-${props.contractId}-${id}`)
  }

  await setCurrentContractState({
    ...currentState,
    selectedIds: uniqueIds,
    detailRows: applyFixedRowSummary([...baseRows, fixedRow])
  })
}

/**
 * 根据选中的服务过滤并显示对应内容
 * @param allRows 所有数据行
 * @param selectedIds 选中的服务ID数组
 * @returns 过滤后的数据行
 */
const filterRowsBySelectedServices = (allRows: any[], selectedIds: string[]) => {
  if (!selectedIds.length || !allRows.length) return []
  
  // 创建选中服务ID的Set，提高查找效率
  const selectedIdSet = new Set(selectedIds)
  
  // 过滤数据：只保留被选中服务对应的行
  return allRows
    .filter(row => selectedIdSet.has(String(row.id)))
    .map(row => ({
      id: String('zx-' + row.id || ''),
      code: row.code || '',
      name: row.name || '',
      process: row.process !== undefined ? row.process : 0,
      investScale: typeof row.investScale === 'number' ? row.investScale : null,
      landScale: typeof row.landScale === 'number' ? row.landScale : null,
      serviceFee: typeof row.serviceFee === 'number' ? row.serviceFee : null,
      hourly: typeof row.hourly === 'number' ? row.hourly : null,
      subtotal: null,
      finalFee: null,
      remark: row.remark || '',
      actions: null
    }))
}

/**
 * 根据选中的服务生成默认数据行
 * @param selectedIds 选中的服务ID数组
 * @returns 默认数据行数组
 */
const buildDefaultDataRows = (selectedIds: string[]) => {
  const contractId = props.contractId;
  return selectedIds.map(id => {
    const service = serviceById.value.get(id)
    return {
      id: `${contractId}-zx-${id}`,
      code: service?.code || '',
      name: service?.name || '',
      process: 0,
      investScale: null,
      landScale: null,
      serviceFee: null,
      hourly: null,
      subtotal: null,
      finalFee: null,
      remark: '',
      actions: null
    }
  })
}

/**
 * 更新过滤后的列表显示
 * 从 IndexedDB 查询数据，然后根据选中的服务进行过滤
 */
const updateFilteredRows = async () => {
  try {
    // 获取当前选中的服务IDs
    const contractData = zxFwPricingStore.getContractState(props.contractId)
    const currentSelectedIds = contractData?.selectedIds || []
    
    if (!currentSelectedIds.length) {
      fallbackDetailRows.value = []
      return
    }
    
    // 查询 IndexedDB 中的数据
    const zxRows = await useDataStore().query([
      { field: 'type', value: `${props.contractId}-zxFw`, operator: 'eq' }
    ])
    const investmentRows = await useDataStore().query([
      { field: 'type', value: `${props.contractId}-investment`, operator: 'eq' }
    ])
    const hourlyRows = await useDataStore().query([
      { field: 'type', value: `${props.contractId}-hourly`, operator: 'eq' }
    ])
    const taskRows = await useDataStore().query([
      { field: 'type', value: `${props.contractId}-task`, operator: 'eq' }
    ])
    let filteredRows: any[]
    
    // 如果 IndexedDB 中没有数据，生成默认数据
    /*if (!allDataRows || allDataRows.length === 0) {
      console.log('⚠️ IndexedDB 中无数据，生成默认数据...')
      filteredRows = buildDefaultDataRows(currentSelectedIds)
      
      // 可选：将默认数据保存到 IndexedDB
      // await useDataStore().upsertBatch(filteredRows.map(row => ({ ...row, type: 'investment' })))
    } else {
      // 过滤并更新显示
      filteredRows = filterRowsBySelectedServices(allDataRows, currentSelectedIds)
    }*/
    filteredRows = buildDefaultDataRows(currentSelectedIds)
    for (let filteredRow of filteredRows) {
      const code = filteredRow.code
      const investmentMatch = investmentRows.find(row =>
          row.groupCode === code
      )

      // ✅ 查找 hourly 数据
      const hourlyMatch = hourlyRows.find(row =>
          row.expertCode === code
      )

      // ✅ 查找 task 数据
      const taskMatch = taskRows.find(row =>
          row.taskCode === code
      )
      const zxMatch = zxRows.find(row =>
          row.code === code
      )
      /*const detailRow = detailRows.value.find(row =>
          row.code === code
      )*/
      filteredRow.investScale = 0;
      filteredRow.finalFee = 0;
      filteredRow.hourly = 0;
      filteredRow.serviceFee = 0;
      if (investmentMatch != null) {
        filteredRow.investScale = investmentMatch.serviceFee
      }
      if (zxMatch != null) {
        filteredRow.finalFee = zxMatch.finalFee
        filteredRow.remark = zxMatch.remark
      }
      if (hourlyMatch != null) {
        filteredRow.hourly = hourlyMatch.feeSubtotal6
      }
      if (taskMatch != null) {
        filteredRow.serviceFee = taskMatch.serviceFee
      }
      filteredRow.subtotal = filteredRow.investScale + filteredRow.serviceFee + filteredRow.hourly
      if (zxMatch != null && (zxMatch.finalFee != null || zxMatch.finalFee != 0)) {
        filteredRow.finalFee = zxMatch.finalFee
      } else {
        filteredRow.finalFee = filteredRow.subtotal
      }
    }
    fallbackDetailRows.value = filteredRows

    console.log('✅ 列表已更新，显示', fallbackDetailRows.value.length, '条数据')
  } catch (error) {
    console.error('updateFilteredRows failed:', error)
    fallbackDetailRows.value = []
  }
}

/**
 * 服务勾选变化入口：先更新行，再刷新新增服务的计价汇总。
 */
const handleServiceSelectionChange = async (ids: string[]) => {
  
  const prevIds = [...selectedIds.value]
  await applySelection(ids)
  const nextSelectedIds = getCurrentContractState().selectedIds || []
  const nextSelectedSet = new Set(nextSelectedIds)
  const addedIds = nextSelectedIds.filter(id => !prevIds.includes(id) && nextSelectedSet.has(id))
  await ensureWorkContentStateForServices(addedIds)
  await fillPricingTotalsForServiceIds(addedIds)
  await ensurePricingDetailRowsForCurrentSelection()
  
  // 服务选择变化后，更新过滤后的列表
  await updateFilteredRows()
}

// 方案1：使用 watch
const zxRows = ref<any[]>([])
const investmentRows = ref<any[]>([])
const hourlyRows = ref<any[]>([])


/**
 * 页面初始化/激活时入口：加载合同、套用选择、确保明细、刷新合计。
 */
const initializeContractState = async () => {
  try {
    // 更新过滤后的列表
    await updateFilteredRows()
  } catch (error) {
    console.error('initializeContractState failed:', error)
    fallbackDetailRows.value = []
  }
}
watch(
    () => useDataStore().items,
    async (newItems) => {
      if (Object.keys(newItems).length > 0) {
        initializeContractState();
      }
    },
    { immediate: true, deep: true }
)
/**
 * 保存数据到 IndexedDB
 * 保存时机：
 * 1. 单元格值变化时（handleCellValueChanged）
 * 2. 组件失活时（onDeactivated）- 切换页面
 * 3. 组件卸载前（onBeforeUnmount）- 关闭页面
 */
const saveToIndexedDB = async () => {
  try {
    // ✅ 使用 upsertBatch：存在则更新，不存在则新增
    const store = useDataStore()

    const typeKey = `${props.contractId}-zxFw`
    // 1. 查询当前 contractId 下所有已存在的 zx 记录
    const existingRows = await store.query([
      { field: 'type', value: typeKey, operator: 'eq' }
    ])

    // 2. 获取 fallbackDetailRows 中的所有 ID
    const methodPrefixes = ['hourly', 'investment']
    const fallbackIds = new Set<string>()
    for (const row of fallbackDetailRows.value) {
      const rowId = String(row.id)
      // 原始 ID
      fallbackIds.add(rowId)
      // 生成各计费方法格式的 ID
      for (const prefix of methodPrefixes) {
        fallbackIds.add(`${props.contractId}-${prefix}-${rowId}`)
      }
    }
    // 3. 找出需要删除的记录（在 store 中存在但不在 fallbackDetailRows 中）
    const idsToDelete = existingRows
        .filter(row => !fallbackIds.has(String(row.id)))
        .map(row => String(row.id))
    // 4. 如果有需要删除的记录，执行批量删除
    if (idsToDelete.length > 0) {
      const deletedCount = await store.deleteBatch(idsToDelete)
      console.log(`🗑️ 删除了 ${deletedCount} 条不再存在的记录:`, idsToDelete)
    }

    // 5. 使用 upsertBatch 保存/更新 fallbackDetailRows 中的数据
    const rows = fallbackDetailRows.value.map(row => ({ ...row, type: typeKey }))
    const stats = await store.upsertBatch(rows)
    const stats2 = await store.upsert({id: `${props.contractId}-checkedZx`, fallbackIds: [...fallbackIds], type: `${props.contractId}-checkedZx`})

    console.log('💾 数据保存成功:', stats)
    console.log('💾 2数据保存成功:', stats2)
    /*zxFwPricingStore.setServicePricingMethodState(
          props.contractId!,
          props.serviceId!,
          'hourly',
          payload,
          {force: true}
      )*/
    // if (useServicePricingState.value && serviceMethod.value) {
    //   zxFwPricingStore.setServicePricingMethodState(
    //       props.contractId!,
    //       props.serviceId!,
    //       serviceMethod.value,
    //       payload,
    //       {force: true}
    //   )
    // } else if (useHtMethodState.value) {
    //   zxFwPricingStore.setHtFeeMethodState(
    //       props.htMainStorageKey!,
    //       props.htRowId!,
    //       props.htMethodType!,
    //       payload,
    //       {force: true}
    //   )
    // } else {
    //   zxFwPricingStore.setKeyState(props.storageKey, payload)
    // }

  } catch (error) {
    console.error('❌ saveToIndexedDB 失败:', error)
  }
}

/** 读取项目行业，用于过滤可选服务词典。 */
const loadProjectIndustry = async () => {
  try {
    const data = await kvStore.getItem<XmBaseInfoState>(PROJECT_INFO_KEY.value)
    projectIndustry.value =
      typeof data?.projectIndustry === 'string' ? data.projectIndustry.trim() : ''
  } catch (error) {
    console.error('loadProjectIndustry failed:', error)
    projectIndustry.value = ''
  }
}

/**
 * 解析项目行业 ID
 * 将行业代码（E2/E3/E4）或数字字符串转换为对应的行业 ID
 * @returns 行业 ID（数字）或 null
 */
const resolveProjectIndustryId = () => {
  const raw = String(projectIndustry.value || '').trim()
  if (!raw) return null
  if (raw.toUpperCase() === 'E2') return 0
  if (raw.toUpperCase() === 'E3') return 1
  if (raw.toUpperCase() === 'E4') return 2
  const parsed = Number(raw)
  return Number.isFinite(parsed) ? parsed : null
}

/**
 * 为指定服务构建默认的工作内容行数据
 * @param serviceIdRaw 服务 ID（字符串格式）
 * @returns 工作内容行数据数组
 */
const buildDefaultWorkContentRowsForService = (serviceIdRaw: string) => {
  const serviceId = Number(serviceIdRaw)
  if (!Number.isFinite(serviceId)) return [] as WorkContentRowState[]
  const entries = getWorkListEntries(locale.value) as Array<{ text: string; serviceid: number; order: number; type: number }>
  const industryId = resolveProjectIndustryId()
  const wholeProcessGroup = wholeProcessTasks.find(
    item => Number(item.fid) === serviceId && Number(item.industry) === industryId
  )
  const filtered = wholeProcessGroup
    ? (() => {
      const groupedServiceIds = Array.isArray(wholeProcessGroup.sid)
        ? wholeProcessGroup.sid.map(id => Number(id)).filter(Number.isFinite)
        : []
      const groupedSet = new Set(groupedServiceIds)
      return entries
        .filter(item => groupedSet.has(Number(item.serviceid)))
        .sort((a, b) => {
          const indexA = groupedServiceIds.indexOf(Number(a.serviceid))
          const indexB = groupedServiceIds.indexOf(Number(b.serviceid))
          if (indexA !== indexB) return indexA - indexB
          return a.order - b.order
        })
    })()
    : entries.filter(item => Number(item.serviceid) === serviceId).sort((a, b) => a.order - b.order)

  const toTypeLabel = (type: number): WorkType => {
    if (type === 1) return t('workContent.type.optional') as WorkType
    if (type === 2) return t('workContent.type.daily') as WorkType
    if (type === 3) return t('workContent.type.special') as WorkType
    if (type === 4) return t('workContent.type.additional') as WorkType
    return t('workContent.type.basic') as WorkType
  }

  return filtered
    .map<WorkContentRowState | null>((entry) => {
      const content = String(entry.text || '').trim()
      if (!content) return null
      const serviceItem = getServiceDictItemById(entry.serviceid) as { code?: string; name?: string } | undefined
      const serviceGroup = serviceItem
        ? `${String(serviceItem.code || '').trim()} ${String(serviceItem.name || '').trim()}`.trim()
        : ''
      return {
        id: `dict-${entry.serviceid}-${entry.order}`,
        content,
        type: toTypeLabel(entry.type),
        dictOrder: entry.order,
        serviceGroup,
        serviceid: Number.isFinite(Number(entry.serviceid)) ? Number(entry.serviceid) : null,
        remark: '',
        checked: true,
        custom: false,
        path: serviceGroup ? [serviceGroup, content] : [toTypeLabel(entry.type), content]
      }
    })
    .filter((item): item is WorkContentRowState => Boolean(item))
}

/**
 * 确保单个服务的工作内容状态已初始化
 * 如果存储中已有数据则跳过，否则创建默认行
 * @param serviceId 服务 ID
 */
const ensureWorkContentStateForService = async (serviceId: string) => {
  const storageKey = `work-content-${props.contractId}-${serviceId}`
  const saved = await zxFwPricingStore.loadKeyState<{ detailRows?: unknown[] }>(storageKey)
  if (Array.isArray(saved?.detailRows) && saved.detailRows.length > 0) return
  const defaultRows = buildDefaultWorkContentRowsForService(serviceId)
  if (defaultRows.length === 0) return
  zxFwPricingStore.setKeyState(storageKey, { detailRows: defaultRows })
}

/**
 * 批量确保多个服务的工作内容状态已初始化
 * @param serviceIds 服务 ID 数组
 */
const ensureWorkContentStateForServices = async (serviceIds: string[]) => {
  const uniqueIds = Array.from(new Set(serviceIds.map(id => String(id || '').trim()).filter(Boolean)))
  if (uniqueIds.length === 0) return
  await Promise.all(uniqueIds.map(id => ensureWorkContentStateForService(id)))
}

/**
 * 监听服务词典变化：过滤失效的已选服务
 * 当服务词典更新时，移除不再存在的服务
 */
watch(serviceIdSignature, () => {
  const availableIds = new Set(serviceDict.value.map(item => item.id))
  const nextSelectedIds = selectedIds.value.filter(id => availableIds.has(id))
  if (nextSelectedIds.length !== selectedIds.value.length) {
    void applySelection(nextSelectedIds)
  }
})

/**
 * 根据语言环境更新行标签
 * 当语言切换时，同步更新服务名称和小计行文本
 */
const relabelRowsByLocale = async () => {
  const currentState = getCurrentContractState()
  if (!Array.isArray(currentState.detailRows) || currentState.detailRows.length === 0) return
  let changed = false
  const nextRows = currentState.detailRows.map(row => {
    if (isFixedRow(row)) {
      const nextName = t('htZxFw.subtotal')
      if (row.name === nextName) return row
      changed = true
      return { ...row, name: nextName }
    }
    const dictItem = serviceById.value.get(String(row.id))
    if (!dictItem) return row
    const nextCode = String(dictItem.code || '')
    const nextName = String(dictItem.name || '')
    if (row.code === nextCode && row.name === nextName) return row
    changed = true
    return { ...row, code: nextCode, name: nextName }
  })
  if (!changed) return
  await setCurrentContractState({
    ...currentState,
    detailRows: applyFixedRowSummary(nextRows)
  })
}

/**
 * 监听语言变化：更新行标签
 */
watch(
  () => locale.value,
  () => {
    void relabelRowsByLocale()
  }
)

/**
 * 监听明细行变化：触发行高自适应
 */
watch(
  () => detailRows.value.map(row => `${row.id}:${row.name}`).join('|'),
  () => {
    scheduleAutoRowHeights()
  }
)

onBeforeUnmount(() => {
  if (autoHeightSyncTimer) {
    clearTimeout(autoHeightSyncTimer)
    autoHeightSyncTimer = null
  }
  if (isGridApiAlive(gridApi.value)) {
    gridApi.value.stopEditing()
  }
  gridApi.value = null
})

/**
 * 处理表格单元格编辑：当前只接管 finalFee 列。
 * 编辑后仅重算固定行，避免覆盖用户刚输入的确认金额。
 */
let isBulkClipboardMutation = false

const commitFinalFeeGridChanges = async () => {
  const currentState = getCurrentContractState()
  const finalRows = applyFixedRowSummary(currentState.detailRows)
  await setCurrentContractState({
    ...currentState,
    detailRows: finalRows
  })
  const api = gridApi.value
  if (isGridApiAlive(api)) {
    const fixedRowData = finalRows.find(r => isFixedRow(r))
    const fixedNode = api.getRowNode(fixedBudgetRow.id)
    if (fixedNode && fixedRowData) {
      fixedNode.setData(fixedRowData)
    }
  }
}

const handleCellValueChanged = async (event: any) => {
  if (isBulkClipboardMutation) return
  
  const row = event.data as DetailRow | undefined
  if (!row || isFixedRow(row)) return
  
  const field = String(event.colDef?.field || '')
  if (field !== 'finalFee' && field !== 'remark') return
  
  // 更新 fallbackDetailRows 中对应的行
  const targetRow = fallbackDetailRows.value.find(r => r.code === row.code)
  if (targetRow) {
    if (field === 'remark') {
      targetRow.remark = String(event.newValue || '')
    } else if (field === 'finalFee') {
      const newValue = event.newValue != null ? roundTo(Number(event.newValue), 2) : null
      targetRow.finalFee = newValue
    }
    
    // 触发响应式更新
    fallbackDetailRows.value = [...fallbackDetailRows.value]
  }
  
  // 保存到 IndexedDB
  await saveToIndexedDB()
  
  console.log('✅ 单元格更新:', field, '->', event.newValue, 'Row ID:', row.id)
}


const pinnedTopRowData = computed(() => {
  const result = [
    {
      id: 'pinned-total-row',
      code: t('htZxFw.columns.subtotal'),
      process: null,
      investScale: sumByNumber(fallbackDetailRows.value, item => item.investScale),
      hourly: sumByNumber(fallbackDetailRows.value, item => item.hourly),
      serviceFee: sumByNumber(fallbackDetailRows.value, item => item.serviceFee),
      finalFee: sumByNumber(fallbackDetailRows.value, item => item.finalFee),
      subtotal: sumByNumber(fallbackDetailRows.value, item => item.subtotal),
      expertName: '',
      path: ['TOTAL']
    }
  ]

  return result
})

const autoGroupColumnDef: ColDef = {
  headerName: t('pricingScale.columns.number'),
  minWidth: 80,
  flex: 2,
  wrapText: true,
  autoHeight: true,
  cellStyle: {
    whiteSpace: 'normal',
    lineHeight: '1.4'
  },
  // 不使用默认的分组渲染器，避免显示折叠箭头
  cellRenderer: undefined,
  colSpan: params => {
    // 如果是固定行（小计行），跨越 2 列
    if (params.node?.rowPinned || params.data?.id === 'pinned-total-row') {
      return 2
    }
    return 1
  },
  valueFormatter: params => {
    if (params.node?.rowPinned) {
      return '小计'
    }
    const rowData = params.data as any
    // 直接显示编码和名称
    if (rowData?.majorCode) {
      return `${rowData.majorCode}`
    }
    return params.value || ''
  },
  tooltipValueGetter: params => {
    if (params.node?.rowPinned) return '小计'
    const rowData = params.data as any
    if (rowData?.majorCode && rowData?.majorName) {
      return `${rowData.majorCode} ${rowData.majorName}`
    }
    return params.value || ''
  }
}

const handleBulkMutationStart = () => {
  isBulkClipboardMutation = true
}

const handleBulkMutationEnd = () => {
  isBulkClipboardMutation = false
  void commitFinalFeeGridChanges()
}

onMounted(async () => {
  await loadProjectIndustry()
  await initializeContractState()
})

onActivated(async () => {
  await loadProjectIndustry()
  await initializeContractState()
})

onDeactivated(() => {
  console.log('🔄 组件失活，保存数据')
  void saveToIndexedDB()
})
</script>

<template>
  <TooltipProvider>
    <div class="h-full min-h-0 flex flex-col gap-2">
      <ServiceCheckboxSelector :services="serviceDict" :model-value="selectedIds"
        @update:model-value="handleServiceSelectionChange" />

      <div class="rounded-lg border bg-card xmMx flex min-h-0 flex-1 flex-col overflow-hidden">
        <div class="flex items-start justify-between gap-3 border-b px-3 py-2">
          <div class="min-w-0 space-y-1">
            <h3 class="text-xs font-semibold text-foreground leading-none">
              {{ t('htZxFw.title') }}
            </h3>
          
          </div>
          <p class="text-[11px] leading-none leading-4 !text-[brown]">{{ t('htZxFw.warning') }}</p>
        </div>

        <div :class="agGridWrapClass">
          <AgGridVue :style="agGridStyle" :rowData="detailRows" :columnDefs="gridColumnDefs"
                     :pinnedBottomRowData="pinnedTopRowData"
            :gridOptions="detailGridOptions" :theme="myTheme" @cell-value-changed="handleCellValueChanged"
            @paste-start="handleBulkMutationStart" @paste-end="handleBulkMutationEnd"
            @fill-start="handleBulkMutationStart" @fill-end="handleBulkMutationEnd"
            :animateRows="true"
            @grid-ready="onGridReady"
            @first-data-rendered="onFirstDataRendered"
            :enableClipboard="true" :localeText="AG_GRID_LOCALE_CN" :tooltipShowDelay="500" :headerHeight="30"
            :undoRedoCellEditing="true" :undoRedoCellEditingLimit="20" />
        </div>
      </div>

      <AlertDialogRoot :open="clearConfirmOpen" @update:open="handleClearConfirmOpenChange">
        <AlertDialogPortal>
          <AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
          <AlertDialogContent
            class="fixed left-1/2 top-1/2 z-[70] w-[92vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background p-5 shadow-xl">
            <AlertDialogTitle class="text-base font-semibold">{{ t('htZxFw.dialog.resetTitle') }}</AlertDialogTitle>
            <AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
              {{ t('htZxFw.dialog.resetDesc', { name: pendingClearServiceName }) }}
            </AlertDialogDescription>
            <div class="mt-4 flex items-center justify-end gap-2">
              <AlertDialogCancel as-child>
                <Button variant="outline">{{ t('common.cancel') }}</Button>
              </AlertDialogCancel>
              <AlertDialogAction as-child>
                <Button variant="destructive" @click="confirmClearRow">{{ t('htZxFw.dialog.confirmReset') }}</Button>
              </AlertDialogAction>
            </div>
          </AlertDialogContent>
        </AlertDialogPortal>
      </AlertDialogRoot>

      <AlertDialogRoot :open="deleteConfirmOpen" @update:open="handleDeleteConfirmOpenChange">
        <AlertDialogPortal>
          <AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
          <AlertDialogContent
            class="fixed left-1/2 top-1/2 z-[70] w-[92vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background p-5 shadow-xl">
            <AlertDialogTitle class="text-base font-semibold">{{ t('htZxFw.dialog.deleteTitle') }}</AlertDialogTitle>
            <AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
              {{ t('htZxFw.dialog.deleteDesc', { name: pendingDeleteServiceName }) }}
            </AlertDialogDescription>
            <div class="mt-4 flex items-center justify-end gap-2">
              <AlertDialogCancel as-child>
                <Button variant="outline">{{ t('common.cancel') }}</Button>
              </AlertDialogCancel>
              <AlertDialogAction as-child>
                <Button variant="destructive" @click="confirmDeleteRow">{{ t('common.delete') }}</Button>
              </AlertDialogAction>
            </div>
          </AlertDialogContent>
        </AlertDialogPortal>
      </AlertDialogRoot>

    </div>
  </TooltipProvider>
</template>

<style scoped>
:deep(.xmMx .ag-header-cell-label) {
  justify-content: center;
}

:deep(.xmMx .ag-header-cell-text) {
  text-align: center;
}

:deep(.zxfw-process-header .ag-header-cell-label) {
  justify-content: center;
}

:deep(.zxfw-process-header .ag-header-cell-text) {
  text-align: center;
}

:deep(.ag-cell:not(.ag-cell-auto-height)) {
  display: flex;
  align-items: center;
}

:deep(.zxfw-number-cell) {
  justify-content: flex-end;
  text-align: right;
}

:deep(.zxfw-name-cell.ag-cell-auto-height) {
  display: flex;
  align-items: center;
}

:deep(.zxfw-name-cell.ag-cell-auto-height .ag-cell-wrapper),
:deep(.zxfw-name-cell.ag-cell-auto-height .ag-cell-value) {
  display: flex;
  align-items: center;
  width: 100%;
  white-space: normal;
}

:deep(.zxfw-name-wrap) {
  display: flex;
  align-items: center;
  width: 100%;
  white-space: normal;
  word-break: break-word;
  overflow-wrap: anywhere;
  line-height: 1.6;
}

:deep(.zxfw-process-cell) {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: center;
  gap: 12px;
  font-size: 12px;
}

:deep(.zxfw-process-cell--en) {
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  padding: 2px 0;
}

:deep(.zxfw-process-option) {
  display: inline-flex;
  cursor: pointer;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

:deep(.zxfw-process-option--en) {
  width: 100%;
}
</style>
