<script setup lang="ts">
import { computed, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { AgGridVue } from 'ag-grid-vue3'
import type { CellValueChangedEvent, ColDef, GridApi, GridOptions, GridReadyEvent } from 'ag-grid-community'
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
import { myTheme, gridOptions, agGridWrapClass, agGridStyle } from '@/lib/diyAgGridOptions'
import { withReadonlyAutoHeight } from '@/lib/agGridReadonlyAutoHeight'
import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
import { parseNumberOrNull } from '@/lib/number'
import { formatThousandsFlexible } from '@/lib/numberFormat'
import { getIndustryDisplayName, getMajorDictEntries, isMajorIdInIndustryScope, xmProjectConfig } from '@/sql'
import { syncContractScaleToPricing, type ContractScaleSyncResult } from '@/lib/zxFwPricingSync'
import { SwitchRoot, SwitchThumb } from 'reka-ui'
import { useKvStore } from '@/pinia/kv'
import {
  ToastAction,
  ToastDescription,
  ToastProvider,
  ToastRoot,
  ToastTitle,
  ToastViewport
} from 'reka-ui'
import { Button } from '@/components/ui/button'



interface DictLeaf {
  id: string
  code: string
  name: string
  hasCost: boolean

  hasArea: boolean
}

interface DictGroup {
  id: string
  code: string
  name: string
  children: DictLeaf[]
}



interface XmScaleState {
  detailRows?: DetailRow[]
  totalAmount: number
  roughCalcEnabled: boolean

}
interface XmBaseInfoState {
  projectIndustry?: string
}

const XM_SCALE_FLUSH_EVENT = 'jgjs:xm-scale-flush-request'
const CONTRACT_SCALE_KEY_PREFIX = 'ht-info-v3-'
const CONTRACT_SCALE_CHANGE_KEY_PREFIX = 'ht-info-scale-change-v1-'
type MajorLite = { code: string; name: string; hasCost?: boolean; hasArea?: boolean }
const kvStore = useKvStore()
const { t, locale } = useI18n()

const detailRows = ref<DetailRow[]>([])
const detailDict = ref<DictGroup[]>([])

const majorEntries = ref<Array<[string, MajorLite]>>([])
const majorIdAliasMap = ref<Map<string, string>>(new Map())
const refreshMajorDictCaches = () => {
  const entries = getMajorDictEntries()
  majorEntries.value = entries.map(({ id, item }) => [id, item] as [string, MajorLite])
  majorIdAliasMap.value = new Map(entries.map(({ rawId, id }) => [rawId, id]))
}
refreshMajorDictCaches()

const buildDetailDict = (entries: Array<[string, MajorLite]>) => {
  const groupMap = new Map<string, DictGroup>()
  const groupOrder: string[] = []
  const codeLookup = new Map(entries.map(([key, item]) => [item.code, { id: key, ...item }]))

  for (const [key, item] of entries) {
    const isGroup = !item.code.includes('-')
    if (isGroup) {
      if (!groupMap.has(item.code)) groupOrder.push(item.code)
      groupMap.set(item.code, {
        id: key,
        code: item.code,
        name: item.name,
        children: []
      })
      continue
    }

    const parentCode = item.code.split('-')[0]
    if (!groupMap.has(parentCode)) {
      const parent = codeLookup.get(parentCode)
      if (!groupOrder.includes(parentCode)) groupOrder.push(parentCode)
      groupMap.set(parentCode, {
        id: parent?.id || `group-${parentCode}`,
        code: parentCode,
        name: parent?.name || parentCode,
        children: []
      })
    }
    groupMap.get(parentCode)!.children.push({
      id: key,
      code: item.code,
      name: item.name,

      hasCost: item.hasCost !== false,
      hasArea: item.hasArea !== false
    })
  }
  const result = groupOrder.map(code => groupMap.get(code)).filter((group): group is DictGroup => Boolean(group))
  return result
}



const buildDefaultRows = (): DetailRow[] => {
  const rows: DetailRow[] = []

  // 1. 首先添加所有分组行（即使没有子项）
  for (const group of detailDict.value) {
    if (group.code != 'C0') {
      rows.push({
        id: group.id,
        groupCode: group.code,
        groupName: group.name,
        majorCode: group.code,
        majorName: group.name,
        hasCost: true,
        hasArea: true,
        amount: null,
        landArea: null,
        path: [`${group.code} ${group.name}`],
        isGroupRow: true,
        hide: false
      })
    }
  }

  for (const group of detailDict.value) {
    for (const child of group.children) {

      rows.push({
        id: child.id,
        groupCode: group.code,
        groupName: group.name,
        majorCode: child.code,
        majorName: child.name,
        hasCost: child.hasCost,
        hasArea: child.hasArea,
        amount: null,
        landArea: null,
        path: [`${group.code} ${group.name}`, `${child.code} ${child.name}`]
      })
    }
  }
  return rows
}

const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] => {
  const dbValueMap = new Map<string, DetailRow>()
  for (const row of rowsFromDb || []) {
    if (row?.isGroupRow === true) continue
    const rowId = String(row.id)
    dbValueMap.set(rowId, row)
    const aliasId = majorIdAliasMap.value.get(rowId)
    if (aliasId && !dbValueMap.has(aliasId)) {
      dbValueMap.set(aliasId, row)
    }
  }

  return buildDefaultRows().map(row => {
    const fromDb = dbValueMap.get(row.id)
    if (!fromDb) return row

    return {
      ...row,
      hide: fromDb.hide,
      amount: row.hasCost && typeof fromDb.amount === 'number' ? fromDb.amount : null,
      landArea: row.hasArea && typeof fromDb.landArea === 'number' ? fromDb.landArea : null
    }
  })
}

const buildGroupRows = (rows: DetailRow[]): DetailRow[] => {
  const rowById = new Map(rows.map(row => [String(row.id || ''), row] as const))
  const groupRows: DetailRow[] = []
  for (const group of detailDict.value) {
    let amountTotal = 0
    let hasAmount = false
    let landAreaTotal = 0
    let hasLandArea = false
    for (const child of group.children) {
      const leaf = rowById.get(String(child.id || ''))
      const amount = leaf?.amount
      if (typeof amount === 'number' && Number.isFinite(amount)) {
        amountTotal += amount
        hasAmount = true
      }
      const landArea = leaf?.landArea
      if (typeof landArea === 'number' && Number.isFinite(landArea)) {
        landAreaTotal += landArea
        hasLandArea = true
      }
    }
    groupRows.push({
      id: group.id,
      groupCode: group.code,
      groupName: group.name,
      majorCode: group.code,
      majorName: group.name,
      hasCost: true,
      hasArea: true,
      amount: hasAmount ? roundTo(amountTotal, 3) : null,
      landArea: hasLandArea ? roundTo(landAreaTotal, 3) : null,
      path: [`${group.code} ${group.name}`],
      hide: false,
      isGroupRow: true
    })
  }
  return groupRows
}


const applyPinnedTotalAmount = (
  api: GridApi<DetailRow> | null | undefined,
  totalAmount: number | null | undefined
) => {
  const normalized = typeof totalAmount === 'number' && Number.isFinite(totalAmount)
    ? roundTo(totalAmount, 2)
    : null
  pinnedTopRowData.value[0].amount = normalized
  const pinnedTopNode = api?.getPinnedTopRow(0)
  if (pinnedTopNode) {
    pinnedTopNode.setDataValue('amount', normalized)
  }
}

const loadFromIndexedDB = async (api: GridApi<DetailRow>) => {
  try {
    const [baseInfo, contractData] = await Promise.all([
      kvStore.getItem<XmBaseInfoState>(props.baseInfoKey || 'xm-base-info-v1'),
      kvStore.getItem<XmScaleState>(props.dbKey)
    ])

    activeIndustryId.value =
      typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''

    if (!activeIndustryId.value) {
      detailDict.value = []
      detailRows.value = []
      lastPersistedLeafRows = []
      roughCalcEnabled.value = false
      applyPinnedTotalAmount(api, null)
      return
    }

    const filteredEntries = majorEntries.value.filter(([id]) =>
      isMajorIdInIndustryScope(id, activeIndustryId.value)
    )
    detailDict.value = buildDetailDict(filteredEntries)

    roughCalcEnabled.value = Boolean(contractData?.roughCalcEnabled)
    applyPinnedTotalAmount(api, contractData?.totalAmount)
    const contractRows = Array.isArray(contractData?.detailRows) ? contractData.detailRows : []
    const hasContractRows = contractRows.length > 0
    const hasContractScaleValue = hasContractRows
      ? contractRows.some(row => {
          const amount = row?.amount
          const landArea = row?.landArea
          return (
            (typeof amount === 'number' && Number.isFinite(amount)) ||
            (typeof landArea === 'number' && Number.isFinite(landArea))
          )
        })
      : false
    const isLegacyEmptyScaleRows =
      hasContractRows &&
      !hasContractScaleValue &&
      !roughCalcEnabled.value &&
      typeof contractData?.totalAmount === 'number' &&
      Number.isFinite(contractData.totalAmount)
    if (hasContractRows && !isLegacyEmptyScaleRows) {
      detailRows.value = mergeWithDictRows(contractRows)
      lastPersistedLeafRows = cloneLeafRows(detailRows.value)
      return
    }

    if (props.xmInfoKey) {
      // 首次创建合同段时，默认继承项目规模信息（同一套专业字典，按 id 对齐）
      const xmData = await kvStore.getItem<XmScaleState>(props.xmInfoKey)
      roughCalcEnabled.value = Boolean(xmData?.roughCalcEnabled)
      applyPinnedTotalAmount(api, xmData?.totalAmount)

      if (Array.isArray(xmData?.detailRows) && xmData.detailRows.length > 0) {
        detailRows.value = mergeWithDictRows(xmData.detailRows)
        lastPersistedLeafRows = cloneLeafRows(detailRows.value)
        return
      }
    }
    detailRows.value = buildDefaultRows()
    lastPersistedLeafRows = cloneLeafRows(detailRows.value)


    void saveToIndexedDB()
  } catch (error) {
    console.error('loadFromIndexedDB failed:', error)
    activeIndustryId.value = ''
    detailRows.value = []
    lastPersistedLeafRows = []
    roughCalcEnabled.value = false
    applyPinnedTotalAmount(api, null)
  }
}

interface DetailRow {
  id: string
  groupCode: string
  groupName: string
  majorCode: string
  majorName: string
  hasCost: boolean
  hasArea: boolean
  amount: number | null
  landArea: number | null
  path: string[]
  hide?: boolean
  isGroupRow?: boolean
}

interface XmBaseInfoState {
  projectIndustry?: string
}

interface GridPersistState {
  detailRows?: DetailRow[]
  roughCalcEnabled?: boolean
  totalAmount?: number | null
}

interface ContractScaleChangeState {
  changedRowIds: string[]
  updatedAt: number
}

const XM_PROJECT_PHASE_KEY = 'xm-project-phase-v1'

interface ProjectPhaseState {
  feePhase?: string
  feeStage?: string
}

const props = defineProps<{
  title: string
  dbKey: string
  xmInfoKey?: string | null
  baseInfoKey?: string
}>()

let persistTimer: ReturnType<typeof setTimeout> | null = null
const gridApi = ref<GridApi<DetailRow> | null>(null)
const activeIndustryId = ref('')
const totalLabel = ref(xmProjectConfig.pinnedTotalLabel)
const roughCalcEnabled = ref(false)
const visibleRowData = computed(() => { return detailRows.value.filter(row => !row.hide) })

const feePhaseOptions = xmProjectConfig.feePhaseOptions
const feeStageOptions = xmProjectConfig.feeStageOptions
const feePhase = ref('')
const feeStage = ref('')

const loadPhaseState = async () => {
  try {
    const state = await kvStore.getItem<ProjectPhaseState>(XM_PROJECT_PHASE_KEY)
    if (state) {
      feePhase.value = state.feePhase || ''
      feeStage.value = state.feeStage || ''
    }
  } catch (error) {
    console.error('load phase state failed:', error)
  }
}

const savePhaseState = async () => {
  try {
    await kvStore.setItem<ProjectPhaseState>(XM_PROJECT_PHASE_KEY, {
      feePhase: feePhase.value,
      feeStage: feeStage.value
    })
  } catch (error) {
    console.error('save phase state failed:', error)
  }
}

watch([feePhase, feeStage], () => {
  void savePhaseState()
})

void loadPhaseState()

const refreshPinnedTotalLabelCell = () => {
  if (!gridApi.value) return
  const pinnedTopNode = gridApi.value.getPinnedTopRow(0)
  if (!pinnedTopNode) return
  gridApi.value.refreshCells({
    rowNodes: [pinnedTopNode],
    force: true
  })
}

const columnDefs: ColDef<DetailRow>[] = [
  {
    headerName: t('pricingScale.columns.investAmount'),
    field: 'amount',
    headerClass: 'ag-right-aligned-header',
    minWidth: 100,
    flex: 1,
    editable: params => {
      if (roughCalcEnabled.value) return Boolean(params.node?.rowPinned)
      return !params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasCost)
    },
    cellClass: params =>
      roughCalcEnabled.value && params.node?.rowPinned
        ? 'editable-cell-line'
        : !roughCalcEnabled.value && !params.node?.group && !params.node?.rowPinned && params.data?.hasCost
          ? 'editable-cell-line'
          : '',
    cellClassRules: {
      'ag-right-aligned-cell': () => true,
      'editable-cell-empty': params =>
        roughCalcEnabled.value && params.node?.rowPinned
          ? params.value == null || params.value === ''
          : !params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasCost) && (params.value == null || params.value === '')
    },
    aggFunc: decimalAggSum,
    valueParser: params => parseNumberOrNull(params.newValue, { precision: 3 }),
    valueFormatter: params => {
      if (roughCalcEnabled.value) {
        if (!params.node?.rowPinned) return ''
        if (params.value == null || params.value === '') return t('pricingScale.clickToInput')
        return formatThousandsFlexible(params.value, 3)
      }
      if (!params.node?.group && !params.node?.rowPinned && !params.data?.hasCost) {
        return ''
      }
      if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
        return t('pricingScale.clickToInput')
      }
      if (params.value == null) return ''
      return formatThousandsFlexible(params.value, 3)
    }
  },
  {
    headerName: '造价金额（元）',
    field: 'amountYuan',
    headerClass: 'ag-right-aligned-header',
    minWidth: 120,
    flex: 1,
    editable: false,
    aggFunc: decimalAggSum,
    valueGetter: params => params.data?.amount,
    cellClassRules: {
      'ag-right-aligned-cell': () => true
    },
    valueFormatter: params => {
      if (roughCalcEnabled.value) {
        return ''
      }
      const amount = params.value
      if (typeof amount !== 'number' || !Number.isFinite(amount)) {
        return ''
      }
      return formatThousandsFlexible(roundTo(amount * 10000, 0), 0)
    }
  }
]
const gridColumnDefs = computed(() => withReadonlyAutoHeight(columnDefs))

const autoGroupColumnDef: ColDef = {
  headerName: t('pricingScale.columns.majorGroup'),
  minWidth: 200,
  flex: 2,
  cellRendererParams: {
    suppressCount: true
  },
  valueFormatter: params => {
    if (params.node?.rowPinned) return totalLabel.value
    return String(params.value || '')
  },
  tooltipValueGetter: params => {
    if (params.node?.rowPinned) return totalLabel.value
    return String(params.value || '')
  }
}

const pinnedTopRowData = ref<DetailRow[]>([
  {
    id: 'pinned-total-row',
    groupCode: '',
    groupName: '',
    majorCode: '',
    majorName: '',
    hasCost: false,
    hasArea: false,
    amount: null,
    landArea: null,
    path: ['TOTAL']
  }
])
const syncToastOpen = ref(false)
const syncToastText = ref('')
let lastPersistedLeafRows: DetailRow[] | null = null

const cloneLeafRows = (rows: DetailRow[]) =>
  rows.map(row => ({
    ...JSON.parse(JSON.stringify(row)),
    hide: Boolean(row.hide),
    isGroupRow: false
  }))

const getChangedScaleRowIds = (previousLeafRows: DetailRow[], nextLeafRows: DetailRow[]) => {
  const previousRowMap = new Map(previousLeafRows.map(row => [String(row?.id || '').trim(), row] as const))
  const nextRowMap = new Map(nextLeafRows.map(row => [String(row?.id || '').trim(), row] as const))
  return Array.from(new Set([
    ...previousRowMap.keys(),
    ...nextRowMap.keys()
  ])).filter(rowId => {
    const prevRow = previousRowMap.get(rowId)
    const nextRow = nextRowMap.get(rowId)
    const prevAmount = typeof prevRow?.amount === 'number' && Number.isFinite(prevRow.amount) ? roundTo(prevRow.amount, 6) : null
    const nextAmount = typeof nextRow?.amount === 'number' && Number.isFinite(nextRow.amount) ? roundTo(nextRow.amount, 6) : null
    const prevLandArea = typeof prevRow?.landArea === 'number' && Number.isFinite(prevRow.landArea) ? roundTo(prevRow.landArea, 6) : null
    const nextLandArea = typeof nextRow?.landArea === 'number' && Number.isFinite(nextRow.landArea) ? roundTo(nextRow.landArea, 6) : null
    return prevAmount !== nextAmount || prevLandArea !== nextLandArea
  })
}

const showScaleSyncToast = (result: ContractScaleSyncResult) => {
  if (result.updatedMethodCount <= 0) return
  syncToastText.value = t('xmScaleGrid.syncToastDesc', {
    serviceCount: result.updatedServiceCount,
    methodCount: result.updatedMethodCount,
    rowCount: result.updatedRowCount
  })
  syncToastOpen.value = false
  requestAnimationFrame(() => {
    syncToastOpen.value = true
  })
}

const saveToIndexedDB = async () => {
  try {
    const leafRows = cloneLeafRows(detailRows.value)
    const totalAmountFromRows = (() => {
      let hasValue = false
      let total = 0
      for (const row of leafRows) {
        const amount = row?.amount
        if (typeof amount !== 'number' || !Number.isFinite(amount)) continue
        total += amount
        hasValue = true
      }
      return hasValue ? roundTo(total, 2) : null
    })()
    const pinnedAmount = pinnedTopRowData.value[0].amount
    const normalizedPinnedAmount =
      typeof pinnedAmount === 'number' && Number.isFinite(pinnedAmount)
        ? roundTo(pinnedAmount, 2)
        : null
    const normalizedTotalAmount = roughCalcEnabled.value ? normalizedPinnedAmount : totalAmountFromRows
    pinnedTopRowData.value[0].amount = normalizedTotalAmount
    const payload: GridPersistState = {
      detailRows: [...leafRows, ...buildGroupRows(leafRows)]
    }
    payload.roughCalcEnabled = roughCalcEnabled.value
    payload.totalAmount = normalizedTotalAmount
    await kvStore.setItem(props.dbKey, payload)
    const previousLeafRows = lastPersistedLeafRows || leafRows
    const changedRowIds = getChangedScaleRowIds(previousLeafRows, leafRows)
    lastPersistedLeafRows = cloneLeafRows(leafRows)
    if (props.dbKey.startsWith(CONTRACT_SCALE_KEY_PREFIX)) {
      const contractId = props.dbKey.slice(CONTRACT_SCALE_KEY_PREFIX.length).trim()
      if (contractId && changedRowIds.length > 0) {
        await kvStore.setItem<ContractScaleChangeState>(`${CONTRACT_SCALE_CHANGE_KEY_PREFIX}${contractId}`, {
          changedRowIds,
          updatedAt: Date.now()
        })
        const syncResult = await syncContractScaleToPricing(contractId, { changedRowIds })
        showScaleSyncToast(syncResult)
      }
    }
  } catch (error) {
    console.error('saveToIndexedDB failed:', error)
  }
}

const schedulePersist = () => {
  if (persistTimer) clearTimeout(persistTimer)
  persistTimer = setTimeout(() => {
    void saveToIndexedDB()
  }, 600)
}

const getRowId = (params: { data?: DetailRow }) => String(params.data?.id || '')
const detailGridOptions: GridOptions<DetailRow> = {
  ...gridOptions,
  getRowId
}

const handleFlushPersistRequest = (event: Event) => {
  const customEvent = event as CustomEvent<{ done?: () => void }>
  const done = customEvent?.detail?.done
  if (persistTimer) {
    clearTimeout(persistTimer)
    persistTimer = null
  }
  void saveToIndexedDB().finally(() => {
    done?.()
  })
}

const setDetailRowsHidden = (hidden: boolean) => {
  for (const row of detailRows.value) {
    row.hide = hidden
  }
}

let oldValue:number|null
let isBulkClipboardMutation = false

const commitGridChanges = () => {
  if (roughCalcEnabled.value) {
    const rawAmount = pinnedTopRowData.value[0]?.amount
    const parsed = typeof rawAmount === 'number' ? rawAmount : Number(rawAmount)
    const nextAmount = Number.isFinite(parsed) ? roundTo(parsed, 2) : null
    pinnedTopRowData.value[0].amount = nextAmount
    const pinnedTopNode = gridApi.value?.getPinnedTopRow(0)
    if (pinnedTopNode) {
      pinnedTopNode.setDataValue('amount', nextAmount)
    }
  } else {
    syncPinnedTotalForNormalMode()
  }
  schedulePersist()
}

const onRoughCalcSwitch = (checked: boolean) => {
  gridApi.value?.stopEditing(true)
  roughCalcEnabled.value = checked
  setDetailRowsHidden(checked)
  if (!checked) {
    oldValue=pinnedTopRowData.value[0].amount
    syncPinnedTotalForNormalMode()
  } else {
    pinnedTopRowData.value[0].amount = oldValue
    const pinnedTopNode = gridApi.value?.getPinnedTopRow(0)
    if (pinnedTopNode) {
      pinnedTopNode.setDataValue('amount', oldValue)
    }
  }
  schedulePersist()
}



const onCellValueChanged = (event: CellValueChangedEvent) => {
  if (isBulkClipboardMutation) return
  if (roughCalcEnabled.value && event.node?.rowPinned && event.colDef.field === 'amount') {
    if (typeof event.newValue === 'number') {
      pinnedTopRowData.value[0].amount = roundTo(event.newValue, 2)
    } else {
      const parsed = Number(event.newValue)
      pinnedTopRowData.value[0].amount = Number.isFinite(parsed) ? roundTo(parsed, 2) : null
    }

  } else if (!roughCalcEnabled.value) {
    syncPinnedTotalForNormalMode()
  }
  schedulePersist()
}

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

const handleBulkMutationEnd = () => {
  isBulkClipboardMutation = false
  commitGridChanges()
}

const onGridReady = (event: GridReadyEvent<DetailRow>) => {
  gridApi.value = event.api

  void loadFromIndexedDB(event.api)

  void refreshPinnedTotalLabelCell()

}

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 === 'amount' || field === 'landArea') {
    return parseNumberOrNull(params.value, { precision: 3 })
  }
  try {
    const parsed = JSON.parse(params.value)
    if (Array.isArray(parsed)) return parsed
  } catch (error) {
    // no-op
  }
  return params.value
}







const relabelRowsFromMajorDict = async () => {
  refreshMajorDictCaches()
  if (!activeIndustryId.value) return
  const filteredEntries = majorEntries.value.filter(([id]) =>
    isMajorIdInIndustryScope(id, activeIndustryId.value)
  )
  detailDict.value = buildDetailDict(filteredEntries)
  if (detailRows.value.length === 0) {
    gridApi.value?.refreshCells({ force: true })
    return
  }
  const nextRows = mergeWithDictRows(detailRows.value)
  const changed = nextRows.some((row, index) => {
    const current = detailRows.value[index]
    return (
      current?.groupCode !== row.groupCode ||
      current?.groupName !== row.groupName ||
      current?.majorCode !== row.majorCode ||
      current?.majorName !== row.majorName
    )
  })
  detailRows.value = nextRows
  gridApi.value?.refreshCells({ force: true })
  void refreshPinnedTotalLabelCell()
  if (!changed) return
  await saveToIndexedDB()
}

watch(totalLabel, () => {
  refreshPinnedTotalLabelCell()
})

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

const syncPinnedTotalForNormalMode = () => {
  if (roughCalcEnabled.value) return

  if (!gridApi.value) {
    pinnedTopRowData.value[0].amount = sumByNumber(detailRows.value, row => row.amount)
    return
  }
  let total = 0
  let hasValue = false

  detailRows.value.forEach(node => {

    const amount = node.amount
    if (typeof amount === 'number' && Number.isFinite(amount)) {
      total += amount
      hasValue = true
    }
  })
  pinnedTopRowData.value[0].amount = hasValue ? roundTo(total, 2) : null
  const pinnedTopNode = gridApi.value.getPinnedTopRow(0)
  if (pinnedTopNode) {
    pinnedTopNode.setDataValue('amount', hasValue ? roundTo(total, 2) : null)
  }
}

onBeforeUnmount(() => {
  if (persistTimer) clearTimeout(persistTimer)
  window.removeEventListener(XM_SCALE_FLUSH_EVENT, handleFlushPersistRequest as EventListener)
  gridApi.value = null
  void saveToIndexedDB()
})

onMounted(() => {
  window.addEventListener(XM_SCALE_FLUSH_EVENT, handleFlushPersistRequest as EventListener)
})
</script>

<template>
  <ToastProvider>
    <div class="h-full">
      <div class="rounded-lg border bg-card xmMx scroll-mt-3 flex flex-col overflow-hidden h-full">
        <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">
            {{ props.title }}
          </h3>
          <!-- <div class="flex items-center gap-2">
            <span class=" text-xs text-muted-foreground">简要计算</span>
            <SwitchRoot
              class="cursor-pointer peer h-5 w-9 shrink-0 rounded-full border border-transparent bg-muted shadow-sm transition-colors data-[state=checked]:bg-primary"
              :modelValue="roughCalcEnabled" @update:modelValue="onRoughCalcSwitch">
              <SwitchThumb
                class="block h-4 w-4 translate-x-0.5 rounded-full bg-background shadow transition-transform data-[state=checked]:translate-x-4" />
            </SwitchRoot>
          </div> -->
        </div>

        <!-- Project Phase Selection -->
        <div class="border-b px-4 py-3 space-y-3 bg-card">
          <div class="flex items-center gap-4">
            <span class="shrink-0 text-sm font-medium text-foreground w-28">项目费用阶段</span>
            <div class="flex-1 flex flex-wrap gap-2">
              <button
                v-for="option in feePhaseOptions"
                :key="option"
                class="px-3 py-1.5 text-xs rounded-lg border transition-colors"
                :class="feePhase === option
                  ? 'bg-blue-600 border-blue-600 text-white font-medium'
                  : 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'"
                @click="feePhase = option"
              >
                {{ option }}
              </button>
            </div>
          </div>
          <div class="flex items-center gap-4">
            <span class="shrink-0 text-sm font-medium text-foreground w-28">项目费用环节</span>
            <div class="flex-1 flex flex-wrap gap-2">
              <button
                v-for="option in feeStageOptions"
                :key="option"
                class="px-3 py-1.5 text-xs rounded-lg border transition-colors"
                :class="feeStage === option
                  ? 'bg-blue-600 border-blue-600 text-white font-medium'
                  : 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'"
                @click="feeStage = option"
              >
                {{ option }}
              </button>
            </div>
          </div>
        </div>

        <div :class="agGridWrapClass">
          <AgGridVue :style="agGridStyle" :rowData="visibleRowData" :pinnedTopRowData="pinnedTopRowData"
            :columnDefs="gridColumnDefs" :autoGroupColumnDef="autoGroupColumnDef" :gridOptions="detailGridOptions" :theme="myTheme"
            :animateRows="true"
            @grid-ready="onGridReady" @cell-value-changed="onCellValueChanged" :suppressColumnVirtualisation="true"
            @paste-start="handleBulkMutationStart" @paste-end="handleBulkMutationEnd"
            @fill-start="handleBulkMutationStart" @fill-end="handleBulkMutationEnd"
            :suppressRowVirtualisation="true" :cellSelection="{ handle: { mode: 'range' } }" :enableClipboard="true"
            :localeText="AG_GRID_LOCALE_CN" :tooltipShowDelay="500" :headerHeight="50" :suppressHorizontalScroll="true"
            :processCellForClipboard="processCellForClipboard" :processCellFromClipboard="processCellFromClipboard"
            :undoRedoCellEditing="true" :undoRedoCellEditingLimit="20" />
        </div>
      </div>
      <ToastRoot
        v-model:open="syncToastOpen"
        class="group pointer-events-auto rounded-lg border bg-background px-4 py-3 text-sm shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in data-[state=closed]:fade-out data-[state=open]:slide-in-from-bottom-2"
      >
        <div class="flex items-start justify-between gap-3">
          <div class="space-y-1">
            <ToastTitle class="text-sm font-semibold text-foreground">{{ t('xmScaleGrid.syncToastTitle') }}</ToastTitle>
            <ToastDescription class="text-xs text-muted-foreground">{{ syncToastText }}</ToastDescription>
          </div>
          <ToastAction as-child :alt-text="t('common.close')">
            <Button variant="ghost" size="sm" class="h-7 px-2 text-xs" @click="syncToastOpen = false">
              {{ t('common.close') }}
            </Button>
          </ToastAction>
        </div>
      </ToastRoot>
      <ToastViewport class="fixed bottom-5 right-5 z-[85] flex w-[420px] max-w-[92vw] flex-col gap-2 outline-none" />
    </div>
  </ToastProvider>
</template>
