<script setup lang="ts">
import { computed, defineComponent, h, nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'
import type { PropType } from 'vue'
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 { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
import { addNumbers, roundTo } 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, isIndustryEnabledByType, getIndustryTypeValue } 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'

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
  workload: number | null
  hourly: number | null
  subtotal?: number | null
  finalFee?: number | null
  actions?: unknown
}

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

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 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: '小计' }

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

/**
 * 统一把 store/raw row 转成页面使用的 DetailRow 结构。
 * 被 detailRows 计算属性和 getCurrentContractState 复用，避免重复映射逻辑。
 */
const normalizeDetailRow = (row: Partial<DetailRow>): DetailRow => ({
  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,
  workload: typeof row.workload === 'number' ? row.workload : 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,
  actions: row.actions
})

const selectedIds = computed<string[]>(() => zxFwPricingStore.contracts[props.contractId]?.selectedIds || [])
const detailRows = computed<DetailRow[]>(() =>
  (zxFwPricingStore.contracts[props.contractId]?.detailRows || []).map(row => normalizeDetailRow(row as Partial<DetailRow>))
)

/**
 * 获取当前合同状态的“可变副本”。
 * 所有写回前都先读这里，避免直接修改 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，供编辑后局部刷新固定行使用。 */
const onGridReady = (event: GridReadyEvent<DetailRow>) => {
  gridApi.value = event.api
  scheduleAutoRowHeights()
}

let autoHeightSyncTimer: ReturnType<typeof setTimeout> | null = null
const isGridApiAlive = (api: GridApi<DetailRow> | null | undefined): api is GridApi<DetailRow> =>
  Boolean(api && !api.isDestroyed?.())

const syncAutoRowHeights = async () => {
  await nextTick()
  const api = gridApi.value
  if (!isGridApiAlive(api)) return
  api.onRowHeightChanged()
}

const scheduleAutoRowHeights = () => {
  if (autoHeightSyncTimer) clearTimeout(autoHeightSyncTimer)
  autoHeightSyncTimer = setTimeout(() => {
    autoHeightSyncTimer = null
    if (!isGridApiAlive(gridApi.value)) return
    void syncAutoRowHeights()
  }, 0)
}

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 || isFixedRow(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 })
}

/** 类型守卫：有限数字。 */
const isFiniteNumberValue = (value: unknown): value is number =>
  typeof value === 'number' && Number.isFinite(value)

/** 可空数字求和：全为空返回 null。 */
const sumNullableNumbers = (values: Array<number | null | undefined>): number | null => {
  const validValues = values.filter(isFiniteNumberValue)
  if (validValues.length === 0) return null
  return addNumbers(...validValues)
}

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' | 'workload' | 'hourly'

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

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

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

/** 获取某服务对应四个计价页的存储键。 */
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(`${PRICING_CLEAR_SKIP_PREFIX}${key}`, skipToken)
    sessionStorage.setItem(`${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: `服务编辑-${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) => {
  if (isFixedRow(row)) return
  const nextIds = selectedIds.value.filter(id => id !== row.id)
  await handleServiceSelectionChange(nextIds)
}

const ActionCellRenderer = defineComponent({
  name: 'ActionCellRenderer',
  props: {
    params: {
      type: Object as PropType<ICellRendererParams<DetailRow>>,
      required: true
    }
  },
  setup(props) {
    return () => {
      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', '编辑')
          ]),
          h('button', { class: 'zxfw-action-btn', 'data-action': 'clear', type: 'button' }, [
            h(Eraser, { size: 13, 'aria-hidden': 'true' }),
            h('span', '恢复默认')
          ]),
          h('button', { class: 'zxfw-action-btn zxfw-action-btn--danger', 'data-action': 'delete', type: 'button' }, [
            h(Trash2, { size: 13, 'aria-hidden': 'true' }),
            h('span', '删除')
          ])
        ])
      ])
    }
  }
})

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 processValue = row.process === 1 ? 1 : 0
      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: 'flex items-center justify-center gap-4 w-full text-sm' }, [
        h('label', { class: 'inline-flex items-center gap-1.5 cursor-pointer' }, [
          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: 'inline-flex items-center gap-1.5 cursor-pointer' }, [
          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', '审核')
        ])
      ])
    }
  }
})

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: '编码',
    field: 'code',
    minWidth: 50,
    maxWidth: 100,
    valueGetter: params => {
      if (!params.data) return ''
      if (isFixedRow(params.data)) return '小计'
      return params.data.code
    },
    colSpan: params => (params.data && isFixedRow(params.data) ? 3 : 1)
  },
  {
    headerName: '名称',
    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: '工作环节',
    field: 'process',
    headerClass: 'ag-center-header zxfw-process-header',
    minWidth: 150,
    maxWidth: 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('投资规模法', 'investScale', 100),
  createMethodColumn('用地规模法', 'landScale', 100),
  createMethodColumn('工作量法', 'workload', 90),
  createMethodColumn('工时法', 'hourly', 90),
  {
    headerName: '小计',
    field: 'subtotal',
    headerClass: 'ag-right-aligned-header',
    flex: 2,
    minWidth: 100,
    editable: false,
    cellClassRules: {
      '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: '确认金额 ✎',
    field: 'finalFee',
    headerClass: 'ag-right-aligned-header',
    headerTooltip: '该列支持手动修改，修改后会自动汇总到固定小计行',
    flex: 2,
    minWidth: 110,
    editable: params => !isFixedRow(params.data),
    cellClassRules: {
      '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: '操作',
    field: 'actions',
    minWidth: 200,
    flex: 1.5,
    maxWidth: 220,
    editable: false,
    sortable: false,
    filter: false,
    suppressMovable: true,
    cellRenderer: ActionCellRenderer
  }
]

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
      }
    })
    .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,
    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])
  })
}

/**
 * 服务勾选变化入口：先更新行，再刷新新增服务的计价汇总。
 */
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 fillPricingTotalsForServiceIds(addedIds)
  await ensurePricingDetailRowsForCurrentSelection()
}

/**
 * 页面初始化/激活时入口：加载合同、套用选择、确保明细、刷新合计。
 */
const initializeContractState = async () => {
  try {
    await zxFwPricingStore.loadContract(props.contractId)
    const data = zxFwPricingStore.getContractState(props.contractId)
    const idsFromStorage = data?.selectedIds
      || (data?.selectedCodes || []).map(code => serviceIdByCode.value.get(code)).filter((id): id is string => Boolean(id))
    await applySelection(idsFromStorage || [])
    await ensurePricingDetailRowsForCurrentSelection()
    // 重新获取所有已选服务的计价总额，确保从编辑页返回后 finalFee 和小计行都更新
    const allServiceIds = getSelectedServiceIdsWithoutFixed()
    if (allServiceIds.length > 0) {
      await fillPricingTotalsForServiceIds(allServiceIds)
    }
  } catch (error) {
    console.error('initializeContractState failed:', error)
    await setCurrentContractState({
      selectedIds: [],
      detailRows: applyFixedRowTotals([{
        id: fixedBudgetRow.id,
        code: fixedBudgetRow.code,
        name: fixedBudgetRow.name,
        process: null,
        investScale: null,
        landScale: null,
        workload: null,
        hourly: null,
        subtotal: null,
        finalFee: null,
        actions: null
      }])
    })
  }
}

/** 读取项目行业，用于过滤可选服务词典。 */
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 = ''
  }
}

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)
  }
})

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
  if (event.colDef?.field !== 'finalFee') return
  const row = event.data as DetailRow | undefined
  if (!row || isFixedRow(row)) return
  const newValue = event.newValue != null ? roundTo(Number(event.newValue), 2) : null
  const currentState = getCurrentContractState()
  
  const nextRows = currentState.detailRows.map(item =>
    item.id === row.id ? { ...item, finalFee: newValue } : item
  )
  const finalRows = applyFixedRowSummary(nextRows)
  await setCurrentContractState({
    ...currentState,
    detailRows: finalRows
  })
  // 真实更新小计行的 rowNode.data，确保 AG Grid 显示最新值
  const api = gridApi.value
  if (api) {
    const fixedRowData = finalRows.find(r => isFixedRow(r))
    const fixedNode = api.getRowNode(fixedBudgetRow.id)
    if (fixedNode && fixedRowData) {
      fixedNode.setData(fixedRowData)
    }
  }
}

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

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

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

onActivated(async () => {
  await loadProjectIndustry()
  await initializeContractState()
})
</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">
              咨询服务明细
            </h3>
          
          </div>
          <p class="text-[11px] text-muted-foreground leading-none leading-4 text-amber-700/90">※ 请注意检查并修改《规范》建议的限值或特殊值，并在确认金额栏修改</p>
        </div>

        <div :class="agGridWrapClass">
          <AgGridVue :style="agGridStyle" :rowData="detailRows" :columnDefs="columnDefs"
            :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">确认恢复默认数据</AlertDialogTitle>
            <AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
              会使用合同卡片里面最新填写的规模信息以及系数，自动计算默认数据，覆盖“{{ pendingClearServiceName }}”当前数据，是否继续？
            </AlertDialogDescription>
            <div class="mt-4 flex items-center justify-end gap-2">
              <AlertDialogCancel as-child>
                <Button variant="outline">取消</Button>
              </AlertDialogCancel>
              <AlertDialogAction as-child>
                <Button variant="destructive" @click="confirmClearRow">确认恢复</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">确认删除服务</AlertDialogTitle>
            <AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
              将逻辑删除“{{ pendingDeleteServiceName }}”，已填写的数据不会清空，重新勾选后会恢复，是否继续？
            </AlertDialogDescription>
            <div class="mt-4 flex items-center justify-end gap-2">
              <AlertDialogCancel as-child>
                <Button variant="outline">取消</Button>
              </AlertDialogCancel>
              <AlertDialogAction as-child>
                <Button variant="destructive" @click="confirmDeleteRow">确认删除</Button>
              </AlertDialogAction>
            </div>
          </AlertDialogContent>
        </AlertDialogPortal>
      </AlertDialogRoot>

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

<style scoped>
: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-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;
}
</style>
