<script setup lang="ts">
import { computed, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { AgGridVue } from 'ag-grid-vue3'
import type { ColDef, ColGroupDef } from 'ag-grid-community'
import localforage from 'localforage'
import { getMajorDictEntries, getServiceDictItemById, industryTypeList, isMajorIdInIndustryScope } from '@/sql'
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
import { formatThousands } from '@/lib/numberFormat'
import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync'
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
import { parseNumberOrNull } from '@/lib/number'
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
import { Button } from '@/components/ui/button'
import {
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogOverlay,
  AlertDialogPortal,
  AlertDialogRoot,
  AlertDialogTitle,
  AlertDialogTrigger,
} from 'reka-ui'

import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale';
// 精简的边框配置（细线条+浅灰色，弱化分割线视觉）

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

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

interface DetailRow {
  id: string
  groupCode: string
  groupName: string
  majorCode: string
  majorName: string
  hasCost: boolean
  hasArea: boolean
  amount: number | null
  benchmarkBudget: number | null
  benchmarkBudgetBasic: number | null
  benchmarkBudgetOptional: number | null
  benchmarkBudgetBasicChecked: boolean
  benchmarkBudgetOptionalChecked: boolean
  basicFormula: string
  optionalFormula: string
  consultCategoryFactor: number | null
  majorFactor: number | null
  workStageFactor: number | null
  workRatio: number | null
  budgetFee: number | null
  budgetFeeBasic: number | null
  budgetFeeOptional: number | null
  remark: string
  path: string[]
}

interface XmInfoState {
  projectName: string
  detailRows: DetailRow[]
}
interface XmBaseInfoState {
  projectIndustry?: string
}

interface ServiceLite {
  onlyCostScale?: boolean | null
}

const props = defineProps<{
  contractId: string,
  serviceId: string | number
}>()
const DB_KEY = computed(() => `tzGMF-${props.contractId}-${props.serviceId}`)
const HT_DB_KEY = computed(() => `ht-info-v3-${props.contractId}`)
const HT_CONSULT_FACTOR_KEY = computed(() => `ht-consult-category-factor-v1-${props.contractId}`)
const HT_MAJOR_FACTOR_KEY = computed(() => `ht-major-factor-v1-${props.contractId}`)
const BASE_INFO_KEY = 'xm-base-info-v1'
const activeIndustryCode = ref('')
const PRICING_CLEAR_SKIP_PREFIX = 'pricing-clear-skip:'
const PRICING_FORCE_DEFAULT_PREFIX = 'pricing-force-default:'
const pricingPaneReloadStore = usePricingPaneReloadStore()
const consultCategoryFactorMap = ref<Map<string, number | null>>(new Map())
const majorFactorMap = ref<Map<string, number | null>>(new Map())
let factorDefaultsLoaded = false
const paneInstanceCreatedAt = Date.now()
const ONLY_COST_SCALE_ROW_ID = '__only-cost-scale-total__'
const industryNameMap = new Map(
  industryTypeList.flatMap(item => [
    [String(item.id).trim(), item.name],
    [String(item.type).trim(), item.name]
  ])
)

const getDefaultConsultCategoryFactor = () =>
  consultCategoryFactorMap.value.get(String(props.serviceId)) ?? null

const getDefaultMajorFactorById = (id: string): number | null => majorFactorMap.value.get(id) ?? null
const isOnlyCostScaleService = computed(() => {
  const service = getServiceDictItemById(props.serviceId) as ServiceLite | undefined
  return service?.onlyCostScale === true
})
const totalLabel = computed(() => {
  const industryName = industryNameMap.get(activeIndustryCode.value.trim()) || ''
  return industryName ? `${industryName}总投资` : '总投资'
})

const loadFactorDefaults = async () => {
  const [consultMap, majorMap] = await Promise.all([
    loadConsultCategoryFactorMap(HT_CONSULT_FACTOR_KEY.value),
    loadMajorFactorMap(HT_MAJOR_FACTOR_KEY.value)
  ])
  consultCategoryFactorMap.value = consultMap
  majorFactorMap.value = majorMap
  factorDefaultsLoaded = true
}

const ensureFactorDefaultsLoaded = async () => {
  if (factorDefaultsLoaded) return
  await loadFactorDefaults()
}

const shouldSkipPersist = () => {
  const storageKey = `${PRICING_CLEAR_SKIP_PREFIX}${DB_KEY.value}`
  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 = `${PRICING_FORCE_DEFAULT_PREFIX}${DB_KEY.value}`
  const raw = sessionStorage.getItem(storageKey)
  if (!raw) return false
  const forceUntil = Number(raw)
  sessionStorage.removeItem(storageKey)
  return Number.isFinite(forceUntil) && Date.now() <= forceUntil
}

const detailRows = ref<DetailRow[]>([])
type majorLite = {
  code: string
  name: string
  defCoe: number | null
  hasCost?: boolean
  hasArea?: boolean
  industryId?: string | number | null
}
const serviceEntries = getMajorDictEntries().map(({ id, item }) => [id, item] as [string, majorLite])
const majorIdAliasMap = new Map(getMajorDictEntries().map(({ rawId, id }) => [rawId, id]))

const detailDict: DictGroup[] = (() => {
  const groupMap = new Map<string, DictGroup>()
  const groupOrder: string[] = []
  const codeLookup = new Map(serviceEntries.map(([key, item]) => [item.code, { id: key, code: item.code, name: item.name }]))

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

    const parentCode = 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: []
      })
    }

    const hasCost = item.hasCost !== false
    const hasArea = item.hasArea !== false
    // 特殊规则：投资规模法中，hasCost && hasArea 的专业不参与明细行
    if (hasCost && hasArea) continue

    groupMap.get(parentCode)!.children.push({
      id: key,
      code,
      name: item.name,
      hasCost,
      hasArea
    })
  }

  return groupOrder.map(code => groupMap.get(code)).filter((group): group is DictGroup => Boolean(group))
})()

const idLabelMap = new Map<string, string>()
for (const group of detailDict) {
  idLabelMap.set(group.id, `${group.code} ${group.name}`)
  for (const child of group.children) {
    idLabelMap.set(child.id, `${child.code} ${child.name}`)
  }
}

const buildDefaultRows = (): DetailRow[] => {
  if (!activeIndustryCode.value) return []
  const rows: DetailRow[] = []
  for (const group of detailDict) {
    if (activeIndustryCode.value && !isMajorIdInIndustryScope(group.id, activeIndustryCode.value)) continue
    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,
        benchmarkBudget: null,
        benchmarkBudgetBasic: null,
        benchmarkBudgetOptional: null,
        benchmarkBudgetBasicChecked: true,
        benchmarkBudgetOptionalChecked: true,
        basicFormula: '',
        optionalFormula: '',
        consultCategoryFactor: null,
        majorFactor: null,
        workStageFactor: 1,
        workRatio: 100,
        budgetFee: null,
        budgetFeeBasic: null,
        budgetFeeOptional: null,
        remark: '',
        path: [group.id, child.id]
      })
    }
  }
  return rows
}

const calcOnlyCostScaleAmountFromRows = (rows?: Array<{ amount?: unknown }>) =>
  sumByNumber(rows || [], row => (typeof row?.amount === 'number' ? row.amount : null))

const getOnlyCostScaleMajorFactorDefault = () => {
  const industryId = String(activeIndustryCode.value || '').trim()
  if (!industryId) return 1
  const industryMajor = serviceEntries.find(([, item]) => {
    const majorIndustryId = String(item?.industryId ?? '').trim()
    return majorIndustryId === industryId && !String(item?.code || '').includes('-')
  })
  if (!industryMajor) return 1
  const [majorId, majorItem] = industryMajor
  const fromMap = majorFactorMap.value.get(String(majorId))
  if (typeof fromMap === 'number' && Number.isFinite(fromMap)) return fromMap
  if (typeof majorItem?.defCoe === 'number' && Number.isFinite(majorItem.defCoe)) return majorItem.defCoe
  return 1
}

const buildOnlyCostScaleRow = (
  amount: number | null,
  fromDb?: Partial<Pick<DetailRow, 'consultCategoryFactor' | 'majorFactor' | 'workStageFactor' | 'workRatio' | 'remark'>>
): DetailRow => ({
  id: ONLY_COST_SCALE_ROW_ID,
  groupCode: 'TOTAL',
  groupName: '总投资',
  majorCode: 'TOTAL',
  majorName: '总投资',
  hasCost: true,
  hasArea: false,
  amount,
  benchmarkBudget: null,
  benchmarkBudgetBasic: null,
  benchmarkBudgetOptional: null,
  benchmarkBudgetBasicChecked: true,
  benchmarkBudgetOptionalChecked: true,
  basicFormula: '',
  optionalFormula: '',
  consultCategoryFactor:
    typeof fromDb?.consultCategoryFactor === 'number' ? fromDb.consultCategoryFactor : getDefaultConsultCategoryFactor(),
  majorFactor: typeof fromDb?.majorFactor === 'number' ? fromDb.majorFactor : getOnlyCostScaleMajorFactorDefault(),
  workStageFactor: typeof fromDb?.workStageFactor === 'number' ? fromDb.workStageFactor : 1,
  workRatio: typeof fromDb?.workRatio === 'number' ? fromDb.workRatio : 100,
  budgetFee: null,
  budgetFeeBasic: null,
  budgetFeeOptional: null,
  remark: typeof fromDb?.remark === 'string' ? fromDb.remark : '',
  path: [ONLY_COST_SCALE_ROW_ID]
})

const buildOnlyCostScaleRows = (
  rowsFromDb?: Array<Partial<DetailRow> & Pick<DetailRow, 'id'>>
): DetailRow[] => {
  const totalAmount = calcOnlyCostScaleAmountFromRows(rowsFromDb)
  const onlyRow = rowsFromDb?.find(row => String(row.id) === ONLY_COST_SCALE_ROW_ID)
  return [buildOnlyCostScaleRow(totalAmount, onlyRow)]
}

type SourceRow = Pick<DetailRow, 'id'> &
  Partial<
    Pick<
      DetailRow,
      | 'amount'
      | 'benchmarkBudget'
      | 'benchmarkBudgetBasic'
      | 'benchmarkBudgetOptional'
      | 'benchmarkBudgetBasicChecked'
      | 'benchmarkBudgetOptionalChecked'
      | 'basicFormula'
      | 'optionalFormula'
      | 'consultCategoryFactor'
      | 'majorFactor'
      | 'workStageFactor'
      | 'workRatio'
      | 'budgetFee'
      | 'budgetFeeBasic'
      | 'budgetFeeOptional'
      | 'remark'
    >
  >
const mergeWithDictRows = (
  rowsFromDb: SourceRow[] | undefined,
  options?: { includeAmount?: boolean; includeFactorValues?: boolean }
): DetailRow[] => {
  const includeAmount = options?.includeAmount ?? true
  const includeFactorValues = options?.includeFactorValues ?? true
  const dbValueMap = new Map<string, SourceRow>()
  for (const row of rowsFromDb || []) {
    const rowId = String(row.id)
    dbValueMap.set(rowId, row)
    const aliasId = majorIdAliasMap.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
    const hasConsultCategoryFactor = Object.prototype.hasOwnProperty.call(fromDb, 'consultCategoryFactor')
    const hasMajorFactor = Object.prototype.hasOwnProperty.call(fromDb, 'majorFactor')

    return {
      ...row,
      amount: includeAmount && row.hasCost && typeof fromDb.amount === 'number' ? fromDb.amount : null,
      benchmarkBudget: typeof fromDb.benchmarkBudget === 'number' ? fromDb.benchmarkBudget : null,
      benchmarkBudgetBasic: typeof fromDb.benchmarkBudgetBasic === 'number' ? fromDb.benchmarkBudgetBasic : null,
      benchmarkBudgetOptional: typeof fromDb.benchmarkBudgetOptional === 'number' ? fromDb.benchmarkBudgetOptional : null,
      benchmarkBudgetBasicChecked:
        typeof fromDb.benchmarkBudgetBasicChecked === 'boolean' ? fromDb.benchmarkBudgetBasicChecked : true,
      benchmarkBudgetOptionalChecked:
        typeof fromDb.benchmarkBudgetOptionalChecked === 'boolean' ? fromDb.benchmarkBudgetOptionalChecked : true,
      basicFormula: typeof fromDb.basicFormula === 'string' ? fromDb.basicFormula : '',
      optionalFormula: typeof fromDb.optionalFormula === 'string' ? fromDb.optionalFormula : '',
      consultCategoryFactor:
        !includeFactorValues
          ? null
          : typeof fromDb.consultCategoryFactor === 'number'
            ? fromDb.consultCategoryFactor
            : hasConsultCategoryFactor
              ? null
              : getDefaultConsultCategoryFactor(),
      majorFactor:
        !includeFactorValues
          ? null
          : typeof fromDb.majorFactor === 'number'
            ? fromDb.majorFactor
            : hasMajorFactor
              ? null
              : getDefaultMajorFactorById(row.id),
      workStageFactor: typeof fromDb.workStageFactor === 'number' ? fromDb.workStageFactor : row.workStageFactor,
      workRatio: typeof fromDb.workRatio === 'number' ? fromDb.workRatio : row.workRatio,
      budgetFee: typeof fromDb.budgetFee === 'number' ? fromDb.budgetFee : null,
      budgetFeeBasic: typeof fromDb.budgetFeeBasic === 'number' ? fromDb.budgetFeeBasic : null,
      budgetFeeOptional: typeof fromDb.budgetFeeOptional === 'number' ? fromDb.budgetFeeOptional : null,
      remark: typeof fromDb.remark === 'string' ? fromDb.remark : ''
    }
  })
}

const formatEditableNumber = (params: any) => {
  if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
    return '请输入'
  }
  if (params.value == null) return ''
  return Number(params.value).toFixed(2)
}

const formatConsultCategoryFactor = (params: any) => {
  return formatEditableNumber(params)
}

const formatMajorFactor = (params: any) => {
  return formatEditableNumber(params)
}

const formatEditableMoney = (params: any) => {
  if (isOnlyCostScaleService.value) {
    if (!params.node?.rowPinned) return ''
    if (params.value == null || params.value === '') return '点击输入'
    return formatThousands(params.value)
  }
  if (!params.node?.group && !params.node?.rowPinned && !params.data?.hasCost) {
    return ''
  }
  if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
    return '点击输入'
  }
  if (params.value == null) return ''
  return formatThousands(params.value)
}

const formatReadonlyMoney = (params: any) => {
  if (params.value == null || params.value === '') return ''
  return formatThousands(roundTo(params.value, 2))
}

type BudgetCheckField = 'benchmarkBudgetBasicChecked' | 'benchmarkBudgetOptionalChecked'

const createBudgetCellRendererWithCheck = (checkField: BudgetCheckField) => (params: any) => {
  const valueText = formatReadonlyMoney(params)
  const hasValue = params.value != null && params.value !== ''
  if (params.node?.group || params.node?.rowPinned || !params.data || !hasValue) {
    return valueText
  }

  const wrapper = document.createElement('div')
  wrapper.style.display = 'flex'
  wrapper.style.alignItems = 'center'
  wrapper.style.justifyContent = 'flex-end'
  wrapper.style.gap = '6px'
  wrapper.style.width = '100%'

  const checkbox = document.createElement('input')
  checkbox.type = 'checkbox'
  checkbox.className = 'cursor-pointer'

  checkbox.checked = params.data[checkField] !== false
  checkbox.addEventListener('click', event => event.stopPropagation())
  checkbox.addEventListener('change', () => {
    params.data[checkField] = checkbox.checked
    handleCellValueChanged()
  })

  const valueSpan = document.createElement('span')
  valueSpan.textContent = valueText
  wrapper.append(checkbox, valueSpan)

  return wrapper
}

const getBenchmarkBudgetSplitByAmount = (row?: Pick<DetailRow, 'amount'>) =>
  getBenchmarkBudgetSplitByScale(row?.amount, 'cost')

const getBudgetFee = (
  row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor' | 'workStageFactor' | 'workRatio'>
) => {
  const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row)
  if (!benchmarkBudgetSplit) return null

  const splitBudgetFee = getScaleBudgetFeeSplit({
    benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
    benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
    majorFactor: row?.majorFactor,
    consultCategoryFactor: row?.consultCategoryFactor,
    workStageFactor: row?.workStageFactor,
    workRatio: row?.workRatio
  })
  return splitBudgetFee ? splitBudgetFee.total : null
}

const getBudgetFeeSplit = (
  row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor' | 'workStageFactor' | 'workRatio'>
) => {
  const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row)
  if (!benchmarkBudgetSplit) return null
  return getScaleBudgetFeeSplit({
    benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
    benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
    majorFactor: row?.majorFactor,
    consultCategoryFactor: row?.consultCategoryFactor,
    workStageFactor: row?.workStageFactor,
    workRatio: row?.workRatio
  })
}

const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
  {
    headerName: '造价金额(万元)',
    field: 'amount',
    headerClass: 'ag-right-aligned-header',
    minWidth: 90,
    flex: 2,

    editable: params => {
      if (isOnlyCostScaleService.value) return Boolean(params.node?.rowPinned)
      return !params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasCost)
    },
    cellClass: params =>
      isOnlyCostScaleService.value && params.node?.rowPinned
        ? 'ag-right-aligned-cell editable-cell-line'
        : !params.node?.group && !params.node?.rowPinned && params.data?.hasCost
        ? 'ag-right-aligned-cell editable-cell-line'
        : 'ag-right-aligned-cell',
    cellClassRules: {
      'editable-cell-empty': params =>
        isOnlyCostScaleService.value && params.node?.rowPinned
          ? params.value == null || params.value === ''
          : !params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasCost) && (params.value == null || params.value === '')
    },
    valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
    valueFormatter: formatEditableMoney
  },
  {
    headerName: '基准预算(元)',
    marryChildren: true,
    children: [
      {
        headerName: '基本工作',
        field: 'benchmarkBudgetBasic',
        colId: 'benchmarkBudgetBasic',
        headerClass: 'ag-right-aligned-header',
        minWidth: 130,
        flex: 1,
        cellClass: 'ag-right-aligned-cell',
        valueGetter: params =>
          params.node?.rowPinned
            ? null
            : getBenchmarkBudgetSplitByAmount(params.data)?.basic ?? null,
        cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetBasicChecked'),
        valueFormatter: formatReadonlyMoney
      },
      {
        headerName: '可选工作',
        field: 'benchmarkBudgetOptional',
        colId: 'benchmarkBudgetOptional',
        headerClass: 'ag-right-aligned-header',
        minWidth: 130,
        flex: 1,
        cellClass: 'ag-right-aligned-cell',
        valueGetter: params =>
          params.node?.rowPinned
            ? null
            : getBenchmarkBudgetSplitByAmount(params.data)?.optional ?? null,
        cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetOptionalChecked'),
        valueFormatter: formatReadonlyMoney
      },
      {
        headerName: '小计',
        field: 'benchmarkBudget',
        colId: 'benchmarkBudgetTotal',
        headerClass: 'ag-right-aligned-header',
        minWidth: 100,
        flex: 1,
        cellClass: 'ag-right-aligned-cell',
        valueGetter: params =>
          params.node?.rowPinned
            ? null
            : getBenchmarkBudgetSplitByAmount(params.data)?.total ?? null,
        valueFormatter: formatReadonlyMoney
      }
    ]
  },
  {
    headerName: '预算费用',
    marryChildren: true,
    children: [
      {
        headerName: '咨询分类系数',
        field: 'consultCategoryFactor',
        colId: 'consultCategoryFactor',
        minWidth: 80,
        flex: 1,
        editable: params =>
          isOnlyCostScaleService.value
            ? Boolean(params.node?.rowPinned)
            : !params.node?.group && !params.node?.rowPinned,
        cellClass: params =>
          isOnlyCostScaleService.value && params.node?.rowPinned
            ? 'editable-cell-line'
            : !params.node?.group && !params.node?.rowPinned
              ? 'editable-cell-line'
              : '',
        cellClassRules: {
          'editable-cell-empty': params =>
            isOnlyCostScaleService.value && params.node?.rowPinned
              ? params.value == null || params.value === ''
              : !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
        },
        valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
        valueFormatter: formatConsultCategoryFactor
      },
      {
        headerName: '专业系数',
        field: 'majorFactor',
        colId: 'majorFactor',
        minWidth: 80,
        flex: 1,
        editable: params =>
          isOnlyCostScaleService.value
            ? Boolean(params.node?.rowPinned)
            : !params.node?.group && !params.node?.rowPinned,
        cellClass: params =>
          isOnlyCostScaleService.value && params.node?.rowPinned
            ? 'editable-cell-line'
            : !params.node?.group && !params.node?.rowPinned
              ? 'editable-cell-line'
              : '',
        cellClassRules: {
          'editable-cell-empty': params =>
            isOnlyCostScaleService.value && params.node?.rowPinned
              ? params.value == null || params.value === ''
              : !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
        },
        valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
        valueFormatter: formatMajorFactor
      },
      {
        headerName: '工作环节系数(编审系数)',
        field: 'workStageFactor',
        colId: 'workStageFactor',
        minWidth: 80,
        flex: 1,
        editable: params =>
          isOnlyCostScaleService.value
            ? Boolean(params.node?.rowPinned)
            : !params.node?.group && !params.node?.rowPinned,
        cellClass: params =>
          isOnlyCostScaleService.value && params.node?.rowPinned
            ? 'editable-cell-line'
            : !params.node?.group && !params.node?.rowPinned
              ? 'editable-cell-line'
              : '',
        cellClassRules: {
          'editable-cell-empty': params =>
            isOnlyCostScaleService.value && params.node?.rowPinned
              ? params.value == null || params.value === ''
              : !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
        },
        valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
        valueFormatter: formatEditableNumber
      },
      {
        headerName: '工作占比(%)',
        field: 'workRatio',
        colId: 'workRatio',
        minWidth: 80,
        flex: 1,
        editable: params =>
          isOnlyCostScaleService.value
            ? Boolean(params.node?.rowPinned)
            : !params.node?.group && !params.node?.rowPinned,
        cellClass: params =>
          isOnlyCostScaleService.value && params.node?.rowPinned
            ? 'editable-cell-line'
            : !params.node?.group && !params.node?.rowPinned
              ? 'editable-cell-line'
              : '',
        cellClassRules: {
          'editable-cell-empty': params =>
            isOnlyCostScaleService.value && params.node?.rowPinned
              ? params.value == null || params.value === ''
              : !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
        },
        valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
        valueFormatter: formatEditableNumber
      },
      {
        headerName: '合计',
        field: 'budgetFee',
        colId: 'budgetFeeTotal',
        headerClass: 'ag-right-aligned-header',
        minWidth: 120,
        flex: 1,
        cellClass: 'ag-right-aligned-cell',
        aggFunc: decimalAggSum,
        valueGetter: params => (params.node?.rowPinned ? params.data?.budgetFee ?? null : getBudgetFee(params.data)),
        valueFormatter: formatReadonlyMoney
      }
    ]
  },
  {
    headerName: '说明',
    field: 'remark',
    minWidth: 100,
    flex: 1.2,
    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 '点击输入'
      return params.value || ''
    },
    cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line remark-wrap-cell' : ''),
    cellClassRules: {
      'editable-cell-empty': params =>
        !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
    }
  }
]

const autoGroupColumnDef: ColDef = {
  headerName: '专业编码以及工程专业名称',
  minWidth: 250,
  pinned: 'left',
  flex: 2,
// wrapText: true,
    // cellStyle: { whiteSpace: 'normal', lineHeight: '1.5', padding: '2px' },

    // autoHeight: true,
  cellRendererParams: {
    suppressCount: true
  },
  valueFormatter: params => {
    if (params.node?.rowPinned) {
      return totalLabel.value
    }
    const nodeId = String(params.value || '')
    return idLabelMap.get(nodeId) || nodeId
  },
  tooltipValueGetter: params => {
    if (params.node?.rowPinned) return totalLabel.value
    const nodeId = String(params.value || '')
    return idLabelMap.get(nodeId) || nodeId
  }
}


const totalAmount = computed(() => sumByNumber(detailRows.value, row => row.amount))
const visibleDetailRows = computed(() => (isOnlyCostScaleService.value ? [] : detailRows.value))
const onlyCostScaleSourceRow = computed(() => detailRows.value[0] ?? buildOnlyCostScaleRow(null))

const totalBenchmarkBudgetBasic = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.basic))
const totalBenchmarkBudgetOptional = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.optional))
const totalBenchmarkBudget = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.total))

const totalBudgetFeeBasic = computed(() => sumByNumber(detailRows.value, row => getBudgetFeeSplit(row)?.basic))
const totalBudgetFeeOptional = computed(() => sumByNumber(detailRows.value, row => getBudgetFeeSplit(row)?.optional))
const totalBudgetFee = computed(() => sumByNumber(detailRows.value, row => getBudgetFee(row)))
const pinnedTopRowData = computed(() => [
  {
    id: 'pinned-total-row',
    groupCode: '',
    groupName: '',
    majorCode: '',
    majorName: '',
    hasCost: false,
    hasArea: false,
    amount: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.amount : null,
    benchmarkBudget: null,
    benchmarkBudgetBasic: null,
    benchmarkBudgetOptional: null,
    benchmarkBudgetBasicChecked: true,
    benchmarkBudgetOptionalChecked: true,
    basicFormula: '',
    optionalFormula: '',
    consultCategoryFactor: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.consultCategoryFactor : null,
    majorFactor: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.majorFactor : null,
    workStageFactor: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.workStageFactor : null,
    workRatio: isOnlyCostScaleService.value ? onlyCostScaleSourceRow.value.workRatio : null,
    budgetFee: totalBudgetFee.value,
    budgetFeeBasic: totalBudgetFeeBasic.value,
    budgetFeeOptional: totalBudgetFeeOptional.value,
    remark: '',
    path: ['TOTAL']
  }
])

const syncComputedValuesToDetailRows = () => {
  for (const row of detailRows.value) {
    const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row)
    const budgetFeeSplit = benchmarkBudgetSplit
      ? getScaleBudgetFeeSplit({
          benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
          benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
          majorFactor: row.majorFactor,
          consultCategoryFactor: row.consultCategoryFactor,
          workStageFactor: row.workStageFactor,
          workRatio: row.workRatio
        })
      : null

    row.benchmarkBudget = benchmarkBudgetSplit?.total ?? null
    row.benchmarkBudgetBasic = benchmarkBudgetSplit?.basic ?? null
    row.benchmarkBudgetOptional = benchmarkBudgetSplit?.optional ?? null
    row.basicFormula = benchmarkBudgetSplit?.basicFormula ?? ''
    row.optionalFormula = benchmarkBudgetSplit?.optionalFormula ?? ''
    row.budgetFee = budgetFeeSplit?.total ?? null
    row.budgetFeeBasic = budgetFeeSplit?.basic ?? null
    row.budgetFeeOptional = budgetFeeSplit?.optional ?? null
  }
}

const buildPersistDetailRows = () => {
  syncComputedValuesToDetailRows()
  return detailRows.value.map(row => ({ ...row }))
}

const saveToIndexedDB = async () => {
  if (shouldSkipPersist()) return
  try {
    const payload = {
      detailRows: JSON.parse(JSON.stringify(buildPersistDetailRows()))
    }
    console.log('Saving to IndexedDB:', payload)
    await localforage.setItem(DB_KEY.value, payload)
    const synced = await syncPricingTotalToZxFw({
      contractId: props.contractId,
      serviceId: props.serviceId,
      field: 'investScale',
      value: totalBudgetFee.value
    })
    if (synced) {
      pricingPaneReloadStore.markReload(props.contractId, ZXFW_RELOAD_SERVICE_KEY)
    }
  } catch (error) {
    console.error('saveToIndexedDB failed:', error)
  }
}

const loadFromIndexedDB = async () => {
  try {
    const baseInfo = await localforage.getItem<XmBaseInfoState>(BASE_INFO_KEY)
    activeIndustryCode.value =
      typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''

    
    await ensureFactorDefaultsLoaded()
    const applyContractDefaultRows = async () => {
      const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
      const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
      if (isOnlyCostScaleService.value) {
        detailRows.value = hasContractRows ? buildOnlyCostScaleRows(htData!.detailRows as any) : buildOnlyCostScaleRows()
      } else {
        detailRows.value = hasContractRows
          ? mergeWithDictRows(htData!.detailRows, { includeFactorValues: true })
          : buildDefaultRows().map(row => ({
              ...row,
              consultCategoryFactor: getDefaultConsultCategoryFactor(),
              majorFactor: getDefaultMajorFactorById(row.id)
            }))
      }
      syncComputedValuesToDetailRows()
    }
    if (shouldForceDefaultLoad()) {
      await applyContractDefaultRows()
      return
    }

    const data = await localforage.getItem<XmInfoState>(DB_KEY.value)
    if (data) {
      detailRows.value = isOnlyCostScaleService.value
        ? buildOnlyCostScaleRows(data.detailRows as any)
        : mergeWithDictRows(data.detailRows)
      syncComputedValuesToDetailRows()
      return
    }

    await applyContractDefaultRows()
  } catch (error) {
    console.error('loadFromIndexedDB failed:', error)
    detailRows.value = isOnlyCostScaleService.value ? buildOnlyCostScaleRows() : buildDefaultRows()
    syncComputedValuesToDetailRows()
  }
}

const importContractData = async () => {
  try {
    const baseInfo = await localforage.getItem<XmBaseInfoState>(BASE_INFO_KEY)
    activeIndustryCode.value =
      typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''

    // 使用默认数据时，强制读取最新的项目系数（预算取值优先，空值回退标准系数）
    await loadFactorDefaults()
    const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
    const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
    if (isOnlyCostScaleService.value) {
      detailRows.value = hasContractRows ? buildOnlyCostScaleRows(htData!.detailRows as any) : buildOnlyCostScaleRows()
    } else {
      detailRows.value = hasContractRows
        ? mergeWithDictRows(htData!.detailRows, { includeFactorValues: true })
        : buildDefaultRows().map(row => ({
            ...row,
            consultCategoryFactor: getDefaultConsultCategoryFactor(),
            majorFactor: getDefaultMajorFactorById(row.id)
          }))
    }
    await saveToIndexedDB()
  } catch (error) {
    console.error('importContractData failed:', error)
  }
}

const clearAllData = async () => {
  try {
    detailRows.value = isOnlyCostScaleService.value ? buildOnlyCostScaleRows() : buildDefaultRows()
    await saveToIndexedDB()
  } catch (error) {
    console.error('clearAllData failed:', error)
  }
}

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

let persistTimer: ReturnType<typeof setTimeout> | null = null




let gridPersistTimer: ReturnType<typeof setTimeout> | null = null
const applyOnlyCostScalePinnedValue = (field: string, rawValue: unknown) => {
  const parsedValue = parseNumberOrNull(rawValue, { precision: 2 })
  const current = detailRows.value[0]
  if (!current) {
    detailRows.value = [buildOnlyCostScaleRow(field === 'amount' ? parsedValue : null)]
    return
  }
  if (
    field !== 'amount' &&
    field !== 'consultCategoryFactor' &&
    field !== 'majorFactor' &&
    field !== 'workStageFactor' &&
    field !== 'workRatio'
  ) {
    return
  }
  detailRows.value = [{ ...current, [field]: parsedValue }]
}

const handleCellValueChanged = (event?: any) => {
  if (isOnlyCostScaleService.value && event?.node?.rowPinned && typeof event.colDef?.field === 'string') {
    applyOnlyCostScalePinnedValue(event.colDef.field, event.newValue)
  }
  syncComputedValuesToDetailRows()
  if (gridPersistTimer) clearTimeout(gridPersistTimer)
  gridPersistTimer = setTimeout(() => {
    void saveToIndexedDB()
  }, 300)
}

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

onActivated(() => {
  void loadFromIndexedDB()
})

onBeforeUnmount(() => {
  if (persistTimer) clearTimeout(persistTimer)
  if (gridPersistTimer) clearTimeout(gridPersistTimer)
  void saveToIndexedDB()
})
const processCellForClipboard = (params: any) => {
  if (Array.isArray(params.value)) {
    return JSON.stringify(params.value); // 数组转字符串复制
  }
  return params.value;
};

const processCellFromClipboard = (params: any) => {
  try {
    const parsed = JSON.parse(params.value);
    if (Array.isArray(parsed)) return parsed;
  } catch (e) {
    // 解析失败时返回原始值，无需额外处理
  }
  return params.value;
};


</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">投资规模明细</h3>
        <div class="flex items-center gap-2">
          <AlertDialogRoot>
            <AlertDialogTrigger as-child>
              <Button type="button" variant="outline" size="sm">清空</Button>
            </AlertDialogTrigger>
            <AlertDialogPortal>
              <AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
              <AlertDialogContent class="fixed left-1/2 top-1/2 z-50 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">
                  将清空当前投资规模明细，是否继续？
                </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="clearAllData">确认清空</Button>
                  </AlertDialogAction>
                </div>
              </AlertDialogContent>
            </AlertDialogPortal>
          </AlertDialogRoot>
          <AlertDialogRoot>
            <AlertDialogTrigger as-child>
              <Button type="button" variant="outline" size="sm">使用默认数据</Button>
            </AlertDialogTrigger>
            <AlertDialogPortal>
              <AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
              <AlertDialogContent class="fixed left-1/2 top-1/2 z-50 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">
                  将使用合同默认数据覆盖当前投资规模明细，是否继续？
                </AlertDialogDescription>
                <div class="mt-4 flex items-center justify-end gap-2">
                  <AlertDialogCancel as-child>
                    <Button variant="outline">取消</Button>
                  </AlertDialogCancel>
                  <AlertDialogAction as-child>
                    <Button @click="importContractData">确认覆盖</Button>
                  </AlertDialogAction>
                </div>
              </AlertDialogContent>
            </AlertDialogPortal>
          </AlertDialogRoot>
        </div>
      </div>

      <div class="ag-theme-quartz h-full min-h-0 w-full flex-1">
        <AgGridVue :style="{ height: '100%' }" :rowData="visibleDetailRows" :pinnedTopRowData="pinnedTopRowData"
          :columnDefs="columnDefs" :autoGroupColumnDef="autoGroupColumnDef" :gridOptions="gridOptions" :theme="myTheme"
          @cell-value-changed="handleCellValueChanged" :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" />
      </div>
    </div>
  </div>
</template>
