<script setup lang="ts">
import { computed, nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { AgGridVue } from 'ag-grid-vue3'
import type {
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GridApi,
  GridReadyEvent,
  RowDataUpdatedEvent
} from 'ag-grid-community'
import { expertList } from '@/sql'
import { myTheme, gridOptions, agGridWrapClass, agGridStyle } from '@/lib/diyAgGridOptions'
import { decimalAggSum, roundTo, sumByNumber, sumNullableNumbers, toDecimal } from '@/lib/decimal'
import { formatThousandsFlexible } from '@/lib/numberFormat'
import { parseNumberOrNull } from '@/lib/number'
import { syncPricingTotalToZxFw, type ZxFwPricingField } from '@/lib/zxFwPricingSync'
import { useZxFwPricingStore, type HtFeeMethodType, type ServicePricingMethod } from '@/pinia/zxFwPricing'
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
import { buildProjectScopedSessionKey } from '@/lib/pricingPersistControl'
import { withReadonlyAutoHeight } from '@/lib/agGridReadonlyAutoHeight'

interface DetailRow {
  id: string
  expertCode: string
  expertName: string
  laborBudgetUnitPrice: string
  compositeBudgetUnitPrice: string
  adoptedBudgetUnitPrice: number | null
  personnelCount: number | null
  workdayCount: number | null
  serviceBudget: number | null
  remark: string
  path: string[]
}

interface GridState {
  detailRows: DetailRow[]
}
const props = withDefaults(
  defineProps<{
    storageKey: string
    title?: string
    contractId?: string
    serviceId?: string | number
    enableZxFwSync?: boolean
    syncField?: ZxFwPricingField
    htMainStorageKey?: string
    htRowId?: string
    htMethodType?: HtFeeMethodType
  }>(),
  {
    title: undefined,
    enableZxFwSync: false,
    syncField: 'hourly'
  }
)
const zxFwPricingStore = useZxFwPricingStore()
const { t, locale } = useI18n()

const PRICING_CLEAR_SKIP_PREFIX = 'pricing-clear-skip:'
const PRICING_FORCE_DEFAULT_PREFIX = 'pricing-force-default:'
const paneInstanceCreatedAt = Date.now()

const shouldSkipPersist = () => {
  const storageKey = buildProjectScopedSessionKey(PRICING_CLEAR_SKIP_PREFIX, props.storageKey)
  const raw = sessionStorage.getItem(storageKey)
  if (!raw) return false
  const now = Date.now()

  if (raw.includes(':')) {
    const [issuedRaw, untilRaw] = raw.split(':')
    const issuedAt = Number(issuedRaw)
    const skipUntil = Number(untilRaw)
    if (Number.isFinite(issuedAt) && Number.isFinite(skipUntil) && now <= skipUntil) {
      return paneInstanceCreatedAt <= issuedAt
    }
    sessionStorage.removeItem(storageKey)
    return false
  }

  const skipUntil = Number(raw)
  if (Number.isFinite(skipUntil) && now <= skipUntil) return true
  sessionStorage.removeItem(storageKey)
  return false
}

const shouldForceDefaultLoad = () => {
  const storageKey = buildProjectScopedSessionKey(PRICING_FORCE_DEFAULT_PREFIX, props.storageKey)
  const raw = sessionStorage.getItem(storageKey)
  if (!raw) return false
  const forceUntil = Number(raw)
  sessionStorage.removeItem(storageKey)
  return Number.isFinite(forceUntil) && Date.now() <= forceUntil
}

const fallbackDetailRows = ref<DetailRow[]>([])
const gridApi = ref<GridApi<DetailRow> | null>(null)
const serviceMethod = computed<ServicePricingMethod | null>(() => {
  if (props.syncField === 'investScale') return 'investScale'
  if (props.syncField === 'landScale') return 'landScale'
  if (props.syncField === 'workload') return 'workload'
  if (props.syncField === 'hourly') return 'hourly'
  return null
})
const useServicePricingState = computed(
  () => Boolean(props.enableZxFwSync && props.contractId && props.serviceId != null && serviceMethod.value)
)
const useHtMethodState = computed(
  () => Boolean(props.htMainStorageKey && props.htRowId && props.htMethodType)
)
const getServiceMethodState = () => {
  if (!useServicePricingState.value || !serviceMethod.value) return null
  return zxFwPricingStore.getServicePricingMethodState<DetailRow>(props.contractId!, props.serviceId!, serviceMethod.value)
}
const getHtMethodState = () => {
  if (!useHtMethodState.value) return null
  return zxFwPricingStore.getHtFeeMethodState<GridState>(
    props.htMainStorageKey!,
    props.htRowId!,
    props.htMethodType!
  )
}
const detailRows = computed<DetailRow[]>({
  get: () => {
    if (useServicePricingState.value) {
      const rows = getServiceMethodState()?.detailRows
      return Array.isArray(rows) ? rows : []
    }
    if (useHtMethodState.value) {
      const rows = getHtMethodState()?.detailRows
      return Array.isArray(rows) ? rows : []
    }
    return fallbackDetailRows.value
  },
  set: rows => {
    if (useServicePricingState.value && serviceMethod.value) {
      const currentState = getServiceMethodState()
      zxFwPricingStore.setServicePricingMethodState(props.contractId!, props.serviceId!, serviceMethod.value, {
        detailRows: rows,
        projectCount: currentState?.projectCount ?? null
      })
      return
    }
    if (useHtMethodState.value) {
      zxFwPricingStore.setHtFeeMethodState(
        props.htMainStorageKey!,
        props.htRowId!,
        props.htMethodType!,
        { detailRows: rows }
      )
      return
    }
    fallbackDetailRows.value = rows
  }
})

type ExpertLite = {
  code: string
  name: string
  maxPrice: number | null
  minPrice: number | null
  defPrice: number | null
  manageCoe: number | null
}

const getExpertDisplayName = (expert: ExpertLite | undefined) => {
  if (!expert) return ''
  return String(locale.value).toLowerCase().startsWith('en')
    ? (expert as ExpertLite & { nameEn?: string }).nameEn || expert.name
    : expert.name
}

const expertEntries = Object.entries(expertList as Record<string, ExpertLite>)
  .sort((a, b) => Number(a[0]) - Number(b[0]))
  .filter((entry): entry is [string, ExpertLite] => {
    const item = entry[1]
    return Boolean(item?.code && item?.name)
  })

const formatPriceRange = (min: number | null, max: number | null) => {
  const hasMin = typeof min === 'number' && Number.isFinite(min)
  const hasMax = typeof max === 'number' && Number.isFinite(max)
  if (hasMin && hasMax) return `${min}-${max}`
  if (hasMin) return String(min)
  if (hasMax) return String(max)
  return ''
}

const getCompositeBudgetUnitPriceRange = (expert: ExpertLite) => {
  if (typeof expert.manageCoe !== 'number' || !Number.isFinite(expert.manageCoe)) return ''
  const min =
    typeof expert.minPrice === 'number' && Number.isFinite(expert.minPrice)
      ? roundTo(toDecimal(expert.minPrice).mul(expert.manageCoe), 2)
      : null
  const max =
    typeof expert.maxPrice === 'number' && Number.isFinite(expert.maxPrice)
      ? roundTo(toDecimal(expert.maxPrice).mul(expert.manageCoe), 2)
      : null
  return formatPriceRange(min, max)
}

const getDefaultAdoptedBudgetUnitPrice = (expert: ExpertLite) => {
  if (
    typeof expert.defPrice !== 'number' ||
    !Number.isFinite(expert.defPrice) ||
    typeof expert.manageCoe !== 'number' ||
    !Number.isFinite(expert.manageCoe)
  ) {
    return null
  }
  return roundTo(toDecimal(expert.defPrice).mul(expert.manageCoe), 2)
}

const buildDefaultRows = (): DetailRow[] => {
  const rows: DetailRow[] = []
  for (const [expertId, expert] of expertEntries) {
    const rowId = `expert-${expertId}`
    rows.push({
      id: rowId,
      expertCode: expert.code,
      expertName: getExpertDisplayName(expert),
      laborBudgetUnitPrice: formatPriceRange(expert.minPrice, expert.maxPrice),
      compositeBudgetUnitPrice: getCompositeBudgetUnitPriceRange(expert),
      adoptedBudgetUnitPrice: getDefaultAdoptedBudgetUnitPrice(expert),
      personnelCount: null,
      workdayCount: null,
      serviceBudget: null,
      remark: '',
      path: [rowId]
    })
  }
  return rows
}

const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] => {
  const dbValueMap = new Map<string, DetailRow>()
  for (const row of rowsFromDb || []) {
    dbValueMap.set(row.id, row)
  }

  return buildDefaultRows().map(row => {
    const fromDb = dbValueMap.get(row.id)
    if (!fromDb) return row
    return {
      ...row,
      adoptedBudgetUnitPrice:
        typeof fromDb.adoptedBudgetUnitPrice === 'number' ? fromDb.adoptedBudgetUnitPrice : null,
      personnelCount: typeof fromDb.personnelCount === 'number' ? fromDb.personnelCount : null,
      workdayCount: typeof fromDb.workdayCount === 'number' ? fromDb.workdayCount : null,
      serviceBudget: typeof fromDb.serviceBudget === 'number' ? fromDb.serviceBudget : null,
      remark: typeof fromDb.remark === 'string' ? fromDb.remark : ''
    }
  })
}

const parseNonNegativeIntegerOrNull = (value: unknown) => {
  if (value === '' || value == null) return null
  const parsed = parseNumberOrNull(value, { sanitize: true, precision: 0 })
  if (parsed == null) return null
  if (!Number.isSafeInteger(parsed) || parsed < 0) return null
  return parsed
}

const formatEditableNumber = (params: any) => {
  if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
    return t('hourlyFeeGrid.clickToInput')
  }
  if (params.value == null) return ''
  return formatThousandsFlexible(params.value, 3)
}

const formatEditableInteger = (params: any) => {
  if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
    return t('hourlyFeeGrid.clickToInput')
  }
  if (params.value == null) return ''
  return String(Number(params.value))
}

const calcServiceBudget = (row: DetailRow | undefined) => {
  const adopted = row?.adoptedBudgetUnitPrice
  const personnel = row?.personnelCount
  const workday = row?.workdayCount
  if (
    typeof adopted !== 'number' ||
    !Number.isFinite(adopted) ||
    typeof personnel !== 'number' ||
    !Number.isFinite(personnel) ||
    typeof workday !== 'number' ||
    !Number.isFinite(workday)
  ) {
    return null
  }
  return roundTo(toDecimal(adopted).mul(personnel).mul(workday), 2)
}

const syncServiceBudgetToRows = () => {
  for (const row of detailRows.value) {
    row.serviceBudget = calcServiceBudget(row)
  }
}

const editableNumberCol = <K extends keyof DetailRow>(
  field: K,
  headerName: string,
  extra: Partial<ColDef<DetailRow>> = {}
): ColDef<DetailRow> => ({
  headerName,
  field,
  minWidth: 120,
  flex: 1,
  editable: params => !params.node?.group && !params.node?.rowPinned,
  cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
  cellClassRules: {
    'ag-right-aligned-cell':()=>true,
    'editable-cell-empty': params =>
      !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
  },
  valueParser: params => parseNumberOrNull(params.newValue, { precision: 3 }),
  valueFormatter: formatEditableNumber,
  ...extra
})

const editableMoneyCol = <K extends keyof DetailRow>(
  field: K,
  headerName: string,
  extra: Partial<ColDef<DetailRow>> = {}
): ColDef<DetailRow> => ({
  headerName,
  field,
  headerClass: 'ag-right-aligned-header',
  minWidth: 120,
  flex: 1,
  editable: params => !params.node?.group && !params.node?.rowPinned,
  cellClass: params =>
    !params.node?.group && !params.node?.rowPinned
      ? 'editable-cell-line'
      : '',
  cellClassRules: {
    'ag-right-aligned-cell': () => true,
    'editable-cell-empty': params =>
      !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
  },
  valueParser: params => parseNumberOrNull(params.newValue, { precision: 3 }),
    valueFormatter: params => {
      if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
      return t('hourlyFeeGrid.clickToInput')
    }
    if (params.value == null) return ''
    return formatThousandsFlexible(params.value, 3)
  },
  ...extra
})

const readonlyTextCol = <K extends keyof DetailRow>(
  field: K,
  headerName: string,
  extra: Partial<ColDef<DetailRow>> = {}
): ColDef<DetailRow> => ({
  headerName,
  field,
  minWidth: 120,
  flex: 1,
  editable: false,
  valueFormatter: params => params.value || '',
  ...extra
})

const columnDefs: (ColDef<DetailRow> | ColGroupDef<DetailRow>)[] = [
  {
    headerName: t('hourlyFeeGrid.columns.code'),
    field: 'expertCode',
    minWidth: 90,
    width: 100,
    pinned: 'left',
    colSpan: params => (params.node?.rowPinned ? 2 : 1),
    cellClassRules: {
      'ag-summary-label-cell': params => Boolean(params.node?.rowPinned)
    },
    valueFormatter: params => (params.node?.rowPinned ? t('hourlyFeeGrid.total') : params.value || '')
  },
  {
    headerName: t('hourlyFeeGrid.columns.name'),
    field: 'expertName',
    minWidth: 210,
    width: 230,
    pinned: 'left',
    tooltipField: 'expertName',
    wrapText: true,
    autoHeight: true,
    cellClass: 'hourly-fee-name-cell',
    cellStyle: { whiteSpace: 'normal', lineHeight: '1.2' },
    valueFormatter: params => (params.node?.rowPinned ? '' : params.value || '')
  },
  {
    headerName: t('hourlyFeeGrid.columns.referenceUnitPrice'),
    marryChildren: true,
    children: [
      readonlyTextCol('laborBudgetUnitPrice', t('hourlyFeeGrid.columns.laborBudgetUnitPrice'), {
        colSpan: params => (params.node?.rowPinned ? 3 : 1),
        valueFormatter: params => (params.node?.rowPinned ? '' : params.value || '')
      }),
      readonlyTextCol('compositeBudgetUnitPrice', t('hourlyFeeGrid.columns.compositeBudgetUnitPrice'))
    ]
  },
  editableMoneyCol('adoptedBudgetUnitPrice', t('hourlyFeeGrid.columns.adoptedBudgetUnitPrice')),
  editableNumberCol('personnelCount', t('hourlyFeeGrid.columns.personnelCount'), {
    aggFunc: decimalAggSum,
    valueParser: params => parseNonNegativeIntegerOrNull(params.newValue),
    valueFormatter: formatEditableInteger
  }),
  editableNumberCol('workdayCount', t('hourlyFeeGrid.columns.workdayCount'), { aggFunc: decimalAggSum }),
  {
    headerName: t('hourlyFeeGrid.columns.serviceBudget'),
    field: 'serviceBudget',
    headerClass: 'ag-right-aligned-header',
    minWidth: 120,
    flex: 1,
    editable: false,
    cellClassRules: {
      'ag-right-aligned-cell': () => true
    },
    aggFunc: decimalAggSum,
    valueGetter: params => (params.node?.rowPinned ? params.data?.serviceBudget ?? null : calcServiceBudget(params.data)),
    valueFormatter: params => {
      if (params.value == null || params.value === '') return ''
      return formatThousandsFlexible(params.value, 3)
    }
  },
  {
    headerName: t('hourlyFeeGrid.columns.remark'),
    field: 'remark',
    minWidth: 120,
    flex: 1,
    cellEditor: 'agLargeTextCellEditor',
    wrapText: true,
    autoHeight: true,
    cellStyle: { whiteSpace: 'normal', lineHeight: '1.4' },
    editable: params => !params.node?.group && !params.node?.rowPinned,
    valueFormatter: params => {
      if (!params.node?.group && !params.node?.rowPinned && !params.value) return t('hourlyFeeGrid.clickToInput')
      return params.value || ''
    },
    cellClass: params => (!params.node?.group && !params.node?.rowPinned ? ' remark-wrap-cell' : ''),
    cellClassRules: {
      'editable-cell-empty': params =>
        !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
    }
  }
]
const gridColumnDefs = computed(() => withReadonlyAutoHeight(columnDefs))

const totalPersonnelCount = computed(() => sumByNumber(detailRows.value, row => row.personnelCount))
const totalWorkdayCount = computed(() => sumByNumber(detailRows.value, row => row.workdayCount))
const totalServiceBudget = computed(() => sumNullableNumbers(detailRows.value.map(row => calcServiceBudget(row))))
const pinnedTopRowData = computed(() => [
  {
    id: 'pinned-total-row',
    expertCode: t('hourlyFeeGrid.total'),
    expertName: '',
    laborBudgetUnitPrice: '',
    compositeBudgetUnitPrice: '',
    adoptedBudgetUnitPrice: null,
    personnelCount: totalPersonnelCount.value,
    workdayCount: totalWorkdayCount.value,
    serviceBudget: totalServiceBudget.value,
    remark: '',
    path: ['TOTAL']
  }
])

const saveToIndexedDB = async () => {
  if (shouldSkipPersist()) return
  try {
    syncServiceBudgetToRows()
    const payload: GridState = {
      detailRows: JSON.parse(JSON.stringify(detailRows.value))
    }

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

    if (props.enableZxFwSync && props.contractId && props.serviceId != null) {
      const synced = await syncPricingTotalToZxFw({
        contractId: props.contractId,
        serviceId: props.serviceId,
        field: props.syncField,
        value: totalServiceBudget.value
      })
      if (!synced) return
    }

  } catch (error) {
    console.error('saveToIndexedDB failed:', error)
  }
}

const loadFromIndexedDB = async () => {
  try {
    if (shouldForceDefaultLoad()) {
      detailRows.value = buildDefaultRows()
      syncServiceBudgetToRows()
      return
    }
    const data = useServicePricingState.value && serviceMethod.value
      ? await zxFwPricingStore.loadServicePricingMethodState<DetailRow>(props.contractId!, props.serviceId!, serviceMethod.value)
      : useHtMethodState.value
        ? await zxFwPricingStore.loadHtFeeMethodState<GridState>(
            props.htMainStorageKey!,
            props.htRowId!,
            props.htMethodType!
          )
        : await zxFwPricingStore.loadKeyState<GridState>(props.storageKey)
    if (data) {
      detailRows.value = mergeWithDictRows(data.detailRows)
      syncServiceBudgetToRows()
      return
    }
    detailRows.value = buildDefaultRows()
    syncServiceBudgetToRows()
  } catch (error) {
    console.error('loadFromIndexedDB failed:', error)
    detailRows.value = buildDefaultRows()
    syncServiceBudgetToRows()
  }
}

const relabelRowsFromExpertDict = async () => {
  if (detailRows.value.length === 0) return
  let changed = false
  detailRows.value = detailRows.value.map(row => {
    const match = String(row.id || '').match(/^expert-(\d+)$/)
    if (!match) return row
    const expert = (expertList as Record<string, ExpertLite | undefined>)[match[1]]
    if (!expert) return row
    const nextName = getExpertDisplayName(expert)
    if (row.expertName === nextName) return row
    changed = true
    return {
      ...row,
      expertName: nextName
    }
  })
  gridApi.value?.refreshCells({ force: true })
  if (!changed) return
  await saveToIndexedDB()
}

let isBulkClipboardMutation = false

const commitGridChanges = (source: string) => {
  syncServiceBudgetToRows()
  gridApi.value?.refreshCells({ force: true })
  scheduleAutoRowHeights()
  void saveToIndexedDB()
}

const handleCellValueChanged = (event?: any) => {
  if (isBulkClipboardMutation) return
  commitGridChanges('cell-value-changed')
}

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

const handleBulkMutationEnd = (event?: any) => {
  isBulkClipboardMutation = false
  commitGridChanges(event?.type || 'bulk-end')
}

const handleGridReady = (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 forceRefreshCellsOnLiveApi = () => {
  // 再次触发一轮强制刷新，覆盖 AG Grid 异步布局后的高度计算。
  setTimeout(() => {
    const liveApi = gridApi.value
    if (!isGridApiAlive(liveApi)) return
    liveApi.refreshCells({ force: true })
    liveApi.redrawRows()
  }, 16)
}

const syncAutoRowHeights = async () => {
  await nextTick()
  const api = gridApi.value
  if (!isGridApiAlive(api)) return
  api.onRowHeightChanged()
  api.refreshCells({ force: true })
  api.redrawRows()
  forceRefreshCellsOnLiveApi()
}
const scheduleAutoRowHeights = () => {
  if (autoHeightSyncTimer) clearTimeout(autoHeightSyncTimer)
  autoHeightSyncTimer = setTimeout(() => {
    autoHeightSyncTimer = null
    if (!isGridApiAlive(gridApi.value)) return
    void syncAutoRowHeights()
  }, 0)
}

const onGridSizeChanged = () => {
  scheduleAutoRowHeights()
}

const onColumnResized = () => {
  scheduleAutoRowHeights()
}

const onFirstDataRendered = (_event: FirstDataRenderedEvent<DetailRow>) => {
  scheduleAutoRowHeights()
}

const onRowDataUpdated = (_event: RowDataUpdatedEvent<DetailRow>) => {
  scheduleAutoRowHeights()
}

const processCellForClipboard = (params: any) => {
  if (Array.isArray(params.value)) return JSON.stringify(params.value)
  return params.value
}

const processCellFromClipboard = (params: any) => {
  const field = String(params.column?.getColDef?.().field || '')
  if (field === 'personnelCount') {
    return parseNonNegativeIntegerOrNull(params.value)
  }
  if (field === 'adoptedBudgetUnitPrice' || field === 'workdayCount') {
    return parseNumberOrNull(params.value, { precision: 3 })
  }
  try {
    const parsed = JSON.parse(params.value)
    if (Array.isArray(parsed)) return parsed
  } catch (_error) {
    return params.value
  }
  return params.value
}

onMounted(async () => {
  await loadFromIndexedDB()
  scheduleAutoRowHeights()
})

onActivated(async () => {
  await loadFromIndexedDB()
  scheduleAutoRowHeights()
})

watch(
  () => props.storageKey,
  () => {
    void loadFromIndexedDB()
    scheduleAutoRowHeights()
  }
)

watch(
  () => detailRows.value.length,
  () => {
    scheduleAutoRowHeights()
  }
)

watch(
  () => locale.value,
  () => {
    void relabelRowsFromExpertDict()
  }
)

onDeactivated(() => {
  gridApi.value?.stopEditing()
  void saveToIndexedDB()
})

onBeforeUnmount(() => {
  gridApi.value?.stopEditing()
  gridApi.value = null
  if (autoHeightSyncTimer) {
    clearTimeout(autoHeightSyncTimer)
    autoHeightSyncTimer = null
  }
  void saveToIndexedDB()
})
</script>

<template>
  <div class="h-full min-h-0 flex flex-col">
    <div class="rounded-lg border bg-card xmMx flex min-h-0 flex-1 flex-col">
      <div class="flex items-center justify-between border-b px-4 py-3">
        <h3 class="text-sm font-semibold text-foreground">{{ props.title || t('hourlyFeeGrid.title') }}</h3>
        <div class="text-xs text-muted-foreground"></div>
      </div>

      <div :class="agGridWrapClass">
        <AgGridVue
          :style="agGridStyle"
          :rowData="detailRows"
          :pinnedTopRowData="pinnedTopRowData"
          :columnDefs="gridColumnDefs"
          :gridOptions="gridOptions"
          :theme="myTheme"
          :animateRows="true"
          :treeData="false"
          @cell-value-changed="handleCellValueChanged"
          @paste-start="handleBulkMutationStart"
          @paste-end="handleBulkMutationEnd"
          @fill-start="handleBulkMutationStart"
          @fill-end="handleBulkMutationEnd"
          :suppressColumnVirtualisation="true"
          :suppressRowVirtualisation="true"
          :cellSelection="{ handle: { mode: 'range' } }"
          :enableClipboard="true"
          :localeText="AG_GRID_LOCALE_CN"
          :tooltipShowDelay="500"
          :headerHeight="50"
          :processCellForClipboard="processCellForClipboard"
          :processCellFromClipboard="processCellFromClipboard"
          :undoRedoCellEditing="true"
          :undoRedoCellEditingLimit="20"
          @grid-ready="handleGridReady"
          @first-data-rendered="onFirstDataRendered"
          @row-data-updated="onRowDataUpdated"
          @grid-size-changed="onGridSizeChanged"
          @column-resized="onColumnResized"
        />
      </div>
    </div>
  </div>
</template>

<style scoped>
:deep(.hourly-fee-name-cell.ag-cell-auto-height) {
  display: flex;
  align-items: center;
}

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