<script setup lang="ts">
import { computed, defineComponent, h, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import type { ComponentPublicInstance, PropType } from 'vue'
import { AgGridVue } from 'ag-grid-vue3'
import type { ColDef, GridOptions, ICellRendererParams } from 'ag-grid-community'
import localforage from 'localforage'
import {  myTheme ,gridOptions} from '@/lib/diyAgGridOptions'
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
import { addNumbers } from '@/lib/decimal'
import { formatThousands, formatThousandsFlexible } from '@/lib/numberFormat'
import { getPricingMethodTotalsForServices } from '@/lib/pricingMethodTotals'
import { ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync'
import { Search } from 'lucide-vue-next'
import {
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogOverlay,
  AlertDialogPortal,
  AlertDialogRoot,
  AlertDialogTitle,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogOverlay,
  DialogPortal,
  DialogRoot,
  DialogTitle,
  DialogTrigger
} from 'reka-ui'
import { Button } from '@/components/ui/button'
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/ui/tooltip'
import { serviceList } from '@/sql'
import { useTabStore } from '@/pinia/tab'
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'

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

interface DetailRow {
  id: string
  code: string
  name: string
  investScale: number | null
  landScale: number | null
  workload: number | null
  hourly: number | null
  subtotal?: number | null
  actions?: unknown
}

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

const props = defineProps<{
  contractId: string
}>()
const tabStore = useTabStore()
const pricingPaneReloadStore = usePricingPaneReloadStore()
const DB_KEY = computed(() => `zxFW-${props.contractId}`)
const PRICING_CLEAR_SKIP_PREFIX = 'pricing-clear-skip:'
const PRICING_FORCE_DEFAULT_PREFIX = 'pricing-force-default:'
const PRICING_CLEAR_SKIP_TTL_MS = 5000

type ServiceListItem = { code?: string; ref?: string; name: string; defCoe: number | null }
const serviceDict: ServiceItem[] = Object.entries(serviceList as Record<string, ServiceListItem>)
  .sort((a, b) => Number(a[0]) - Number(b[0]))
  .filter((entry): entry is [string, ServiceListItem] => {
    const item = entry[1]
    const itemCode = item?.code || item?.ref
    return Boolean(itemCode && item?.name) && item.defCoe !== null
  })
  .map(([key, item]) => ({
    id: key,
    code: item.code || item.ref || '',
    name: item.name
  }))

const serviceById = new Map(serviceDict.map(item => [item.id, item]))
const serviceIdByCode = new Map(serviceDict.map(item => [item.code, item.id]))
const fixedBudgetRow: Pick<DetailRow, 'id' | 'code' | 'name'> = { id: 'fixed-budget-c', code: 'C', name: '合同预算' }
const isFixedRow = (row?: DetailRow | null) => row?.id === fixedBudgetRow.id

const selectedIds = ref<string[]>([])
const detailRows = ref<DetailRow[]>([])
const rootRef = ref<HTMLElement | null>(null)
const gridSectionRef = ref<HTMLElement | null>(null)
const agGridRef = ref<HTMLElement | null>(null)
const agGridHeight = ref(580)
let snapScrollHost: HTMLElement | null = null
let snapTimer: ReturnType<typeof setTimeout> | null = null
let snapLockTimer: ReturnType<typeof setTimeout> | null = null
let isSnapping = false
let hostResizeObserver: ResizeObserver | null = null

const updateGridCardHeight = () => {
  if (!snapScrollHost || !rootRef.value) return
  const contentWrap = rootRef.value.parentElement
  const style = contentWrap ? window.getComputedStyle(contentWrap) : null
  const paddingTop = style ? Number.parseFloat(style.paddingTop || '0') || 0 : 0
  const paddingBottom = style ? Number.parseFloat(style.paddingBottom || '0') || 0 : 0
  const nextHeight = Math.max(360, Math.floor(snapScrollHost.clientHeight - paddingTop - paddingBottom))
  agGridHeight.value = nextHeight-40
}


const bindSnapScrollHost = () => {
  snapScrollHost = rootRef.value?.closest('[data-slot="scroll-area-viewport"]') as HTMLElement | null
  if (!snapScrollHost) return
  snapScrollHost.addEventListener('scroll', handleSnapHostScroll, { passive: true })
  hostResizeObserver?.disconnect()
  hostResizeObserver = new ResizeObserver(() => {
    updateGridCardHeight()
  })
  hostResizeObserver.observe(snapScrollHost)
  updateGridCardHeight()
}

const unbindSnapScrollHost = () => {
  if (snapScrollHost) {
    snapScrollHost.removeEventListener('scroll', handleSnapHostScroll)
  }
  hostResizeObserver?.disconnect()
  hostResizeObserver = null
  snapScrollHost = null
}

const trySnapToGrid = () => {
  if (isSnapping || !snapScrollHost || !agGridRef.value) return

  const hostRect = snapScrollHost.getBoundingClientRect()
  const gridRect = agGridRef.value.getBoundingClientRect()
  const offsetTop = gridRect.top - hostRect.top
  const inVisibleBand = gridRect.bottom > hostRect.top + 40 && gridRect.top < hostRect.bottom - 40
  const inSnapRange = offsetTop > -120 && offsetTop < 180

  if (!inVisibleBand || !inSnapRange) return

  isSnapping = true
  agGridRef.value.scrollIntoView({ behavior: 'smooth', block: 'start' })
  if (snapLockTimer) clearTimeout(snapLockTimer)
  snapLockTimer = setTimeout(() => {
    isSnapping = false
  }, 420)
}

function handleSnapHostScroll() {
  if (isSnapping) return
  if (snapTimer) clearTimeout(snapTimer)
  snapTimer = setTimeout(() => {
    trySnapToGrid()
  }, 90)
}

const pickerOpen = ref(false)
const pickerTempIds = ref<string[]>([])
const pickerSearch = ref('')
const clearConfirmOpen = ref(false)
const pendingClearServiceId = ref<string | null>(null)
const dragSelecting = ref(false)
const dragMoved = ref(false)
let dragSelectChecked = false
const dragBaseIds = ref<string[]>([])
const dragStartPoint = ref({ x: 0, y: 0 })
const dragCurrentPoint = ref({ x: 0, y: 0 })
const pickerItemElMap = new Map<string, HTMLElement>()

const selectedServiceText = computed(() => {
  if (selectedIds.value.length === 0) return ''
  const names = selectedIds.value
    .map(id => serviceById.get(id)?.name || '')
    .filter(Boolean)
  if (names.length <= 2) return names.join('、')
  return `${names.slice(0, 2).join('、')} 等 ${names.length} 项`
})

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.get(pendingClearServiceId.value)
  if (dict) return `${dict.code}${dict.name}`
  return pendingClearServiceId.value
})

const handleClearConfirmOpenChange = (open: boolean) => {
  clearConfirmOpen.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 filteredServiceDict = computed(() => {
  const keyword = pickerSearch.value.trim()
  if (!keyword) return serviceDict
  return serviceDict.filter(item => item.code.includes(keyword) || item.name.includes(keyword))
})

const dragRectStyle = computed(() => {
  if (!dragSelecting.value) return {}
  const left = Math.min(dragStartPoint.value.x, dragCurrentPoint.value.x)
  const top = Math.min(dragStartPoint.value.y, dragCurrentPoint.value.y)
  const width = Math.abs(dragCurrentPoint.value.x - dragStartPoint.value.x)
  const height = Math.abs(dragCurrentPoint.value.y - dragStartPoint.value.y)
  return {
    left: `${left}px`,
    top: `${top}px`,
    width: `${width}px`,
    height: `${height}px`
  }
})

const numericParser = (newValue: any): number | null => {
  if (newValue === '' || newValue == null) return null
  const num = Number(newValue)
  return Number.isFinite(num) ? num : null
}

const valueOrZero = (v: number | null | undefined) => (typeof v === 'number' ? v : 0)

const getMethodTotalFromRows = (
  rows: DetailRow[],
  field: 'investScale' | 'landScale' | 'workload' | 'hourly'
) =>
  rows.reduce((sum, row) => {
    if (isFixedRow(row)) return sum
    return addNumbers(sum, valueOrZero(row[field]))
  }, 0)

const getMethodTotal = (field: 'investScale' | 'landScale' | 'workload' | 'hourly') =>
  getMethodTotalFromRows(detailRows.value, field)

const getFixedRowSubtotal = () =>
  addNumbers(
    getMethodTotal('investScale'),
    getMethodTotal('landScale'),
    getMethodTotal('workload'),
    getMethodTotal('hourly')
  )

const getPricingPaneStorageKeys = (serviceId: string) => [
  `tzGMF-${props.contractId}-${serviceId}`,
  `ydGMF-${props.contractId}-${serviceId}`,
  `gzlF-${props.contractId}-${serviceId}`,
  `hourlyPricing-${props.contractId}-${serviceId}`
]

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))
  }
  await Promise.all(keys.map(key => localforage.removeItem(key)))
  pricingPaneReloadStore.markReload(props.contractId, serviceId)
}

const clearRowValues = async (row: DetailRow) => {
  if (isFixedRow(row)) return

  // 若该服务编辑页已打开，先关闭，避免子页面卸载时把旧数据写回缓?  tabStore.removeTab(`zxfw-edit-${props.contractId}-${row.id}`)
  await nextTick()
  await clearPricingPaneValues(row.id)
  // const totals = await getPricingMethodTotalsForService({
  //   contractId: props.contractId,
  //   serviceId: row.id
  // })
  const clearedRows = detailRows.value.map(item =>
    item.id !== row.id
      ? item
      : {
          ...item,
          investScale: null,
          landScale: null,
          workload: null,
          hourly: null
        }
  )
  const nextInvestScale = getMethodTotalFromRows(clearedRows, 'investScale')
  const nextLandScale = getMethodTotalFromRows(clearedRows, 'landScale')
  const nextWorkload = getMethodTotalFromRows(clearedRows, 'workload')
  const nextHourly = getMethodTotalFromRows(clearedRows, 'hourly')
  detailRows.value = clearedRows.map(item =>
    isFixedRow(item)
      ? {
          ...item,
          investScale: nextInvestScale,
          landScale: nextLandScale,
          workload: nextWorkload,
          hourly: nextHourly,
          subtotal: addNumbers(nextInvestScale, nextLandScale, nextWorkload, nextHourly)
        }
      : item
  )
  await saveToIndexedDB()
}

const openEditTab = (row: DetailRow) => {
  tabStore.openTab({
    id: `zxfw-edit-${props.contractId}-${row.id}`,
    title: `服务编辑-${row.code}${row.name}`,
    componentName: 'ZxFwView',
    props: { contractId: props.contractId, contractName: row.name ,serviceId: row.id,fwName:row.code+row.name}
  })
}

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(TooltipRoot, null, {
          default: () => [
            h(TooltipTrigger, { asChild: true }, {
              default: () =>
                h('button', { class: 'zxfw-action-btn', 'data-action': 'edit' }, '✏️')
            }),
            h(TooltipContent, { side: 'top' }, { default: () => '编辑' })
          ]
        }),
        h(TooltipRoot, null, {
          default: () => [
            h(TooltipTrigger, { asChild: true }, {
              default: () =>
                h('button', { class: 'zxfw-action-btn', 'data-action': 'clear' }, '🧹')
            }),
            h(TooltipContent, { side: 'top' }, { default: () => '清空' })
          ]
        })
      ])
    }
  }
})

const columnDefs: ColDef<DetailRow>[] = [
  { headerName: '编码', field: 'code', minWidth: 50, maxWidth: 100 },
  { headerName: '名称', field: 'name', minWidth: 250, flex: 3, tooltipField: 'name' },
  {
    headerName: '投资规模法',
    field: 'investScale',
    headerClass: 'ag-right-aligned-header',
    minWidth: 140,
    flex: 2,
    cellClass: 'ag-right-aligned-cell',
    editable: false,

    valueGetter: params => {
      if (!params.data) return null
      if (isFixedRow(params.data)) return getMethodTotal('investScale')
      return params.data.investScale
    },
    valueParser: params => numericParser(params.newValue),
    valueFormatter: params => (params.value == null ? '' : formatThousands(params.value))
  },
  {
    headerName: '用地规模法',
    field: 'landScale',
    headerClass: 'ag-right-aligned-header',
    minWidth: 140,
    flex: 2,
    cellClass: 'ag-right-aligned-cell',
    editable: false,

    valueGetter: params => {
      if (!params.data) return null
      if (isFixedRow(params.data)) return getMethodTotal('landScale')
      return params.data.landScale
    },
    valueParser: params => numericParser(params.newValue),
    valueFormatter: params => (params.value == null ? '' : formatThousandsFlexible(params.value, 2))
  },
  {
    headerName: '工作量法',
    field: 'workload',
    headerClass: 'ag-right-aligned-header',
    minWidth: 120,
    flex: 2,
    cellClass: 'ag-right-aligned-cell',
    editable: false,

    valueGetter: params => {
      if (!params.data) return null
      if (isFixedRow(params.data)) return getMethodTotal('workload')
      return params.data.workload
    },
    // editable: params => !params.node?.rowPinned && !isFixedRow(params.data),
    valueParser: params => numericParser(params.newValue),
    valueFormatter: params => (params.value == null ? '' : formatThousands(params.value))
  },
  {
    headerName: '工时法',
    field: 'hourly',
    headerClass: 'ag-right-aligned-header',
    minWidth: 120,
    flex: 2,
    cellClass: 'ag-right-aligned-cell',
    editable: false,

    valueGetter: params => {
      if (!params.data) return null
      if (isFixedRow(params.data)) return getMethodTotal('hourly')
      return params.data.hourly
    },
    // editable: params => !params.node?.rowPinned && !isFixedRow(params.data),
    valueParser: params => numericParser(params.newValue),
    valueFormatter: params => (params.value == null ? '' : formatThousands(params.value))
  },
  {
    headerName: '小计',
    field: 'subtotal',
    headerClass: 'ag-right-aligned-header',
    flex: 3,

    minWidth: 120,
    cellClass: 'ag-right-aligned-cell',
    editable: false,
    valueGetter: params => {
      if (!params.data) return null
      if (isFixedRow(params.data)) return getFixedRowSubtotal()
      return addNumbers(
        valueOrZero(params.data.investScale),
        valueOrZero(params.data.landScale),
        valueOrZero(params.data.workload),
        valueOrZero(params.data.hourly)
      )
    },
    valueFormatter: params => (params.value == null ? '' : formatThousands(params.value))
  },
  {
    headerName: '操作',
    field: 'actions',
    minWidth: 50,
    flex: 1,
    maxWidth: 120,
    editable: false,
    sortable: false,
    filter: false,
    suppressMovable: true,
    cellRenderer: ActionCellRenderer
  }
]

const detailGridOptions: GridOptions<DetailRow> = {
  ...gridOptions,
  treeData: false,
  getDataPath: undefined,
  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)
    }
  }
}

const fillPricingTotalsForSelectedRows = async () => {
  const serviceRows = detailRows.value.filter(row => !isFixedRow(row))
  if (serviceRows.length === 0) return

  const totalsByServiceId = await getPricingMethodTotalsForServices({
    contractId: props.contractId,
    serviceIds: serviceRows.map(row => row.id)
  })

  detailRows.value = detailRows.value.map(row => {
    if (isFixedRow(row)) return row
    const totals = totalsByServiceId.get(String(row.id))
    if (!totals) return row
    return {
      ...row,
      investScale: totals.investScale,
      landScale: totals.landScale,
      workload: totals.workload,
      hourly: totals.hourly
    }
  })
}

const applySelection = (codes: string[]) => {
  const prevSelectedSet = new Set(selectedIds.value)
  const uniqueIds = Array.from(new Set(codes)).filter(
    id => serviceById.has(id) && id !== fixedBudgetRow.id
  )
  const existingMap = new Map(detailRows.value.map(row => [row.id, row]))

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

      const old = existingMap.get(id)
      return {
        id: old?.id || id,
        code: dictItem.code,
        name: dictItem.name,
        investScale: old?.investScale ?? null,
        landScale: old?.landScale ?? null,
        workload: old?.workload ?? null,
        hourly: old?.hourly ?? null
      }
    })
    .filter((row): row is DetailRow => Boolean(row))

  const orderMap = new Map(serviceDict.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,
    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: 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}`)
  }

  selectedIds.value = uniqueIds
  detailRows.value = [...baseRows, fixedRow]
}

const preparePickerOpen = () => {
  pickerTempIds.value = [...selectedIds.value]
  pickerSearch.value = ''
}

const closePicker = () => {
  stopDragSelect()
  pickerOpen.value = false
}

const handlePickerOpenChange = (open: boolean) => {
  if (open) {
    preparePickerOpen()
  } else {
    stopDragSelect()
  }
  pickerOpen.value = open
}

const confirmPicker = async () => {
  applySelection(pickerTempIds.value)
  try {
    // await fillPricingTotalsForSelectedRows()
    await saveToIndexedDB()
  } catch (error) {
    console.error('confirmPicker failed:', error)
    await saveToIndexedDB()
  }
}

const clearPickerSelection = () => {
  pickerTempIds.value = []
}



const applyTempChecked = (code: string, checked: boolean) => {
  const exists = pickerTempIds.value.includes(code)
  if (checked && !exists) {
    pickerTempIds.value = [...pickerTempIds.value, code]
    return
  }
  if (!checked && exists) {
    pickerTempIds.value = pickerTempIds.value.filter(item => item !== code)
  }
}

const setPickerItemRef = (
  code: string,
  el: Element | ComponentPublicInstance | null
) => {
  if (el instanceof HTMLElement) {
    pickerItemElMap.set(code, el)
    return
  }
  pickerItemElMap.delete(code)
}

const isRectIntersect = (
  a: { left: number; right: number; top: number; bottom: number },
  b: { left: number; right: number; top: number; bottom: number }
) => !(a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom)

const applyDragSelectionByRect = () => {
  const rect = {
    left: Math.min(dragStartPoint.value.x, dragCurrentPoint.value.x),
    right: Math.max(dragStartPoint.value.x, dragCurrentPoint.value.x),
    top: Math.min(dragStartPoint.value.y, dragCurrentPoint.value.y),
    bottom: Math.max(dragStartPoint.value.y, dragCurrentPoint.value.y)
  }

  const nextSelectedSet = new Set(dragBaseIds.value)
  for (const [code, el] of pickerItemElMap.entries()) {
    const itemRect = el.getBoundingClientRect()
    const hit = isRectIntersect(rect, itemRect)
    if (hit) {
      if (dragSelectChecked) {
        nextSelectedSet.add(code)
      } else {
        nextSelectedSet.delete(code)
      }
    }
  }

  pickerTempIds.value = serviceDict
    .map(item => item.id)
    .filter(id => nextSelectedSet.has(id))
}

const stopDragSelect = () => {
  dragSelecting.value = false
  dragMoved.value = false
  dragBaseIds.value = []
  window.removeEventListener('mousemove', onDragSelectingMove)
  window.removeEventListener('mouseup', stopDragSelect)
}

const onDragSelectingMove = (event: MouseEvent) => {
  dragCurrentPoint.value = { x: event.clientX, y: event.clientY }
  if (!dragMoved.value) {
    const dx = Math.abs(event.clientX - dragStartPoint.value.x)
    const dy = Math.abs(event.clientY - dragStartPoint.value.y)
    if (dx >= 3 || dy >= 3) {
      dragMoved.value = true
    }
  }
  applyDragSelectionByRect()
}

const startDragSelect = (event: MouseEvent, code: string) => {
  dragSelecting.value = true
  dragMoved.value = false
  dragBaseIds.value = [...pickerTempIds.value]
  dragSelectChecked = !pickerTempIds.value.includes(code)
  dragStartPoint.value = { x: event.clientX, y: event.clientY }
  dragCurrentPoint.value = { x: event.clientX, y: event.clientY }
  applyTempChecked(code, dragSelectChecked)
  window.addEventListener('mousemove', onDragSelectingMove)
  window.addEventListener('mouseup', stopDragSelect)
}

const handleDragHover = (_code: string) => {
  if (!dragSelecting.value || !dragMoved.value) return
  applyDragSelectionByRect()
}

const saveToIndexedDB = async () => {
  try {
    const payload: ZxFwState = {
      selectedIds: [...selectedIds.value],
      detailRows: JSON.parse(JSON.stringify(detailRows.value))
    }
    await localforage.setItem(DB_KEY.value, payload)
  } catch (error) {
    console.error('saveToIndexedDB failed:', error)
  }
}

const loadFromIndexedDB = async () => {
  try {
    const data = await localforage.getItem<ZxFwState>(DB_KEY.value)
    if (!data) {
      selectedIds.value = []
      detailRows.value = []
      return
    }

    const idsFromStorage = data.selectedIds
      || (data.selectedCodes || []).map(code => serviceIdByCode.get(code)).filter((id): id is string => Boolean(id))
    applySelection(idsFromStorage)

    const savedRowMap = new Map((data.detailRows || []).map(row => [row.id, row]))
    detailRows.value = detailRows.value.map(row => {
      const old = savedRowMap.get(row.id)
      if (!old) return row
      return {
        ...row,
        investScale: typeof old.investScale === 'number' ? old.investScale : null,
        landScale: typeof old.landScale === 'number' ? old.landScale : null,
        workload: typeof old.workload === 'number' ? old.workload : null,
        hourly: typeof old.hourly === 'number' ? old.hourly : null
      }
    })

    try {
      // await fillPricingTotalsForSelectedRows()
    } catch (error) {
      console.error('fillPricingTotalsForSelectedRows failed:', error)
    }
  } catch (error) {
    console.error('loadFromIndexedDB failed:', error)
    selectedIds.value = []
    detailRows.value = []
  }
}

watch(
  () => pricingPaneReloadStore.getReloadVersion(props.contractId, ZXFW_RELOAD_SERVICE_KEY),
  (nextVersion, prevVersion) => {
    if (nextVersion === prevVersion || nextVersion === 0) return
    void loadFromIndexedDB()
  }
)

let gridPersistTimer: ReturnType<typeof setTimeout> | null = null
const handleCellValueChanged = () => {
  if (gridPersistTimer) clearTimeout(gridPersistTimer)
  gridPersistTimer = setTimeout(() => {
    void saveToIndexedDB()
  }, 1000)
}

const scrollToGridSection = () => {
  const target = gridSectionRef.value || agGridRef.value
  target?.scrollIntoView({ behavior: 'smooth', block: 'start' })
}

onMounted(async () => {
  await loadFromIndexedDB()
  bindSnapScrollHost()
  requestAnimationFrame(() => {
    updateGridCardHeight()
  })
})

onBeforeUnmount(() => {
  unbindSnapScrollHost()
  stopDragSelect()
  if (gridPersistTimer) clearTimeout(gridPersistTimer)
  if (snapTimer) clearTimeout(snapTimer)
  if (snapLockTimer) clearTimeout(snapLockTimer)
  void saveToIndexedDB()
})
</script>

<template>
  <TooltipProvider>
    <div ref="rootRef" class="space-y-6">
    <DialogRoot v-model:open="pickerOpen" @update:open="handlePickerOpenChange">
      <div class="rounded-lg border bg-card p-4 shadow-sm shrink-0">
        <div class="mb-2 flex items-center justify-between gap-3">
          <label class="block text-sm font-medium text-foreground">选择服务</label>
        </div>
        <div class="flex items-center gap-2">
          <input :value="selectedServiceText" readonly placeholder="请点击右侧“浏览”选择服务"
            class="h-10 w-full rounded-md border bg-background px-3 text-sm text-foreground outline-none" />
          <TooltipRoot>
            <TooltipTrigger as-child>
              <DialogTrigger as-child>
                <button type="button"
                  class="inline-flex h-10 w-10 items-center justify-center rounded-md border text-sm hover:bg-accent cursor-pointer">
                  <Search class="h-4 w-4" />
                </button>
              </DialogTrigger>
            </TooltipTrigger>
            <TooltipContent side="top">浏览服务词典</TooltipContent>
          </TooltipRoot>
        </div>
      </div>
      <DialogPortal>
        <DialogOverlay class="fixed inset-0 z-50 bg-black/40" />
        <DialogContent
          class="fixed left-1/2 top-1/2 z-[60] w-[96vw] max-w-2xl -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background shadow-xl p-0">
          <DialogTitle class="sr-only">选择服务词典</DialogTitle>
          <DialogDescription class="sr-only">浏览并选择服务词典</DialogDescription>
          <div class="flex items-center justify-between border-b px-5 py-4">
            <h4 class="text-base font-semibold">选择服务词典</h4>
            <DialogClose as-child>
              <button type="button"
                class="inline-flex cursor-pointer h-8 items-center rounded-md border px-3 text-sm hover:bg-accent">
                关闭
              </button>
            </DialogClose>
          </div>

          <div class="max-h-[420px] overflow-auto px-5 py-4">
            <div class="mb-3">
              <input v-model="pickerSearch" type="text" placeholder="输入编码或名称过滤"
                class="h-9 w-full rounded-md border bg-background px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring" />
            </div>
            <div class="grid grid-cols-1 gap-2 md:grid-cols-2">
              <label v-for="item in filteredServiceDict" :key="item.id" :ref="el => setPickerItemRef(item.id, el)"
                :class="[
                  'picker-item-clickable flex select-none items-center gap-2 rounded-md border px-3 py-2 text-sm',
                  dragMoved ? 'cursor-default picker-item-dragging' : 'cursor-pointer',
                  pickerTempIds.includes(item.id) ? 'picker-item-selected' : '',
                  dragSelecting && pickerTempIds.includes(item.id) ? 'picker-item-selected-drag' : ''
                ]"
                @mousedown.prevent="startDragSelect($event, item.id)" @mouseenter="handleDragHover(item.id)"
                @click.prevent>
                <input type="checkbox" :checked="pickerTempIds.includes(item.id)" class="pointer-events-none" />
                <span class="text-muted-foreground">{{ item.code }}</span>
                <span>{{ item.name }}</span>
              </label>
            </div>
          </div>

          <div class="flex items-center justify-end gap-2 border-t px-5 py-3">
            <Button type="button" variant="outline" @click="clearPickerSelection">
              清空
            </Button>
            <DialogClose as-child>
              <Button type="button" @click="confirmPicker">
                确认选择
              </Button>
            </DialogClose>
          </div>
        </DialogContent>
        <div v-if="dragSelecting"
          class="pointer-events-none fixed z-[70] rounded-sm border border-sky-500/90 bg-sky-400/10"
          :style="dragRectStyle" />
      </DialogPortal>
    </DialogRoot>

    <div
      ref="gridSectionRef"
      class="rounded-lg border bg-card xmMx scroll-mt-3" :style="{ height: `${agGridHeight}px` }"
    >
      <div class="flex items-center justify-between border-b px-4 py-3">
        <h3
          class="text-sm font-semibold text-foreground cursor-pointer select-none transition-colors hover:text-primary"
          @click="scrollToGridSection"
        >
          咨询服务明细
        </h3>
        <div class="text-xs text-muted-foreground">按服务词典生成</div>
      </div>

      <div ref="agGridRef" class="ag-theme-quartz w-full h-full" >
        <AgGridVue :style="{ height: '100%' }" :rowData="detailRows" :columnDefs="columnDefs" :gridOptions="detailGridOptions"
          :theme="myTheme" @cell-value-changed="handleCellValueChanged" :enableClipboard="true"
          :localeText="AG_GRID_LOCALE_CN" :tooltipShowDelay="500" :headerHeight="50" :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>

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