<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { AgGridVue } from 'ag-grid-vue3'
import type { ColDef, ColGroupDef, GridApi, GridOptions, GridReadyEvent } from 'ag-grid-community'
import { getIndustryDisplayName, getMajorDictEntries, getServiceDictItemById, isMajorIdInIndustryScope } from '@/sql'
import { myTheme, gridOptions, agGridWrapClass, agGridStyle } from '@/lib/diyAgGridOptions'
import { decimalAggSum, roundTo } from '@/lib/decimal'
import { formatThousandsFlexible } from '@/lib/numberFormat'
import { withReadonlyAutoHeight } from '@/lib/agGridReadonlyAutoHeight'
import { syncPricingTotalToZxFw } from '@/lib/zxFwPricingSync'
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
import { useKvStore } from '@/pinia/kv'
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
import { parseNumberOrNull } from '@/lib/number'
import {
  buildScaleDetailDict,
  buildScaleIdLabelMap,
  buildScaleRowsFromDict,
  type ScaleDictGroup
} from '@/lib/pricingScaleDict'
import {
  createScaleAutoGroupColumn,
  createScaleBenchmarkBudgetColumnGroup,
  createScaleBudgetFeeColumnGroup,
  createScaleRemarkColumn,
  createScaleValueColumn
} from '@/lib/pricingScaleColumns'
import {
  createScaleBudgetCellRendererToggleFactory,
  formatScaleEditableConditionalNumber,
  restoreScaleColumnDefaults,
  type ScaleBudgetCheckField,
  type ScaleBudgetHeaderCheckState,
  ScaleBudgetToggleHeader,
} from '@/lib/pricingScaleGrid'
import {
  getCheckedBenchmarkBudgetSplitByRow,
  getScaleBudgetFeeByRow,
  getScaleBudgetFeeSplitByRow,
  isBenchmarkBudgetFullyUnchecked,
  isSameNullableNumber,
  recomputeScaleDetailRowsInPlace
} from '@/lib/pricingScaleDetail'
import { sumNullableBy } from '@/lib/pricingScaleCalc'
import { createPinnedTopRowData, createScalePinnedTotalRow } from '@/lib/pricingPinnedRows'
import { usePricingPaneLifecycle } from '@/lib/pricingScalePaneLifecycle'
import {
  applyPricingScaleProjectCountChange,
  clearPricingScalePaneRows,
  importPricingScalePaneRows,
  loadPricingScalePaneRows
} from '@/lib/pricingScalePaneData'
import {
  buildScaleProjectGroupPathKey,
  buildScopedScaleRowId,
  getScaleProjectMajorKeyFromRow,
  inferScaleProjectCountFromRows,
  normalizeScaleProjectCount
} from '@/lib/pricingScaleProject'
import { mergeScaleRowsFromProjectMajorMap } from '@/lib/pricingScaleRowMap'
import { buildProjectScopedSessionKey } from '@/lib/pricingPersistControl'
import {
  buildContractScaleIdMap,
  buildContractScaleMap,
  buildContractScaleProjectTotals,
  getContractScaleRowByMajor,
  getContractScaleProjectTotalsByRow,
  makeProjectMajorKey,
  normalizeChangedScaleRowIds,
  parseProjectIndexFromPathKey,
  parseScopedRowId,
  resolveScaleRowMajorDictId as resolveRowMajorDictId,
  resolveScaleRowProjectIndex as resolveRowProjectIndex
} from '@/lib/pricingScaleLink'
import { AgGridResetHeader } from '@/lib/agGridResetHeader'
import { Button } from '@/components/ui/button'
import {
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogOverlay,
  AlertDialogPortal,
  AlertDialogRoot,
  NumberFieldDecrement,
  NumberFieldIncrement,
  NumberFieldInput,
  NumberFieldRoot,
  AlertDialogTitle,
  AlertDialogTrigger,
} from 'reka-ui'

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

interface DetailRow {
  id: string
  projectIndex?: number
  majorDictId?: string
  groupCode: string
  groupName: string
  majorCode: string
  majorName: string
  hasCost: boolean
  hasArea: boolean
  amount: number | null
  landArea: number | null
  benchmarkBudget: number | null
  benchmarkBudgetBasic: number | null
  benchmarkBudgetOptional: number | null
  benchmarkBudgetBasicChecked: boolean
  benchmarkBudgetOptionalChecked: boolean
  basicFormula: string | null
  optionalFormula: string | null
  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 ContractScaleChangeState {
  changedRowIds?: string[]
  updatedAt?: number
}

interface FactorChangeState {
  changedRowIds?: string[]
  updatedAt?: number
}

interface ServiceLite {
  mutiple?: boolean | null
}

const props = defineProps<{
  contractId: string,
  serviceId: string | number
  projectInfoKey?: string
}>()
const zxFwPricingStore = useZxFwPricingStore()
const { t, locale } = useI18n()
const kvStore = useKvStore()
const DB_KEY = computed(() => `ydGMF-${props.contractId}-${props.serviceId}`)
const HT_DB_KEY = computed(() => `ht-info-v3-${props.contractId}`)
const HT_SCALE_CHANGE_KEY = computed(() => `ht-info-scale-change-v1-${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 HT_CONSULT_FACTOR_CHANGE_KEY = computed(() => `${HT_CONSULT_FACTOR_KEY.value}-change`)
const HT_MAJOR_FACTOR_CHANGE_KEY = computed(() => `${HT_MAJOR_FACTOR_KEY.value}-change`)
const BASE_INFO_KEY = computed(() => props.projectInfoKey || 'xm-base-info-v1')
const activeIndustryCode = ref('')
const PRICING_CLEAR_SKIP_PREFIX = 'pricing-clear-skip:'
const PRICING_FORCE_DEFAULT_PREFIX = 'pricing-force-default:'
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 gridApi = ref<GridApi<DetailRow> | null>(null)
const lastAppliedConsultFactorChangeAt = ref(0)
const lastAppliedMajorFactorChangeAt = ref(0)
const totalLabel = computed(() => {
  const industryName = getIndustryDisplayName(activeIndustryCode.value.trim(), locale.value)
  return industryName ? t('pricingScale.totalInvestmentByIndustry', { industryName }) : t('pricingScale.totalInvestment')
})

const isMutipleService = computed(() => {
  const service = getServiceDictItemById(props.serviceId) as ServiceLite | undefined
  return service?.mutiple === true
})
const projectCount = ref<number>(1)
const normalizeProjectCount = normalizeScaleProjectCount
const getTargetProjectCount = () => (isMutipleService.value ? normalizeProjectCount(projectCount.value) : 1)
const buildProjectGroupPathKey = (projectIndex: number) => buildScaleProjectGroupPathKey(projectIndex)
const buildScopedRowId = (projectIndex: number, majorId: string) =>
  buildScopedScaleRowId(isMutipleService.value, projectIndex, majorId)
const inferProjectCountFromRows = (rows?: Array<Partial<DetailRow>>) => {
  return inferScaleProjectCountFromRows(rows, isMutipleService.value)
}

const getMethodState = () =>
  zxFwPricingStore.getServicePricingMethodState<DetailRow>(props.contractId, props.serviceId, 'landScale')

const detailRows = computed<DetailRow[]>({
  get: () => {
    const rows = getMethodState()?.detailRows
    return Array.isArray(rows) ? rows : []
  },
  set: rows => {
    const currentState = getMethodState()
    zxFwPricingStore.setServicePricingMethodState(props.contractId, props.serviceId, 'landScale', {
      detailRows: rows,
      projectCount: currentState?.projectCount ?? getTargetProjectCount()
    })
  }
})
const getDefaultConsultCategoryFactor = () =>
  consultCategoryFactorMap.value.get(String(props.serviceId)) ?? null

const getDefaultMajorFactorById = (id: string): number | null => majorFactorMap.value.get(id) ?? null

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 shouldForceDefaultLoad = () => {
  const storageKey = buildProjectScopedSessionKey(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 shouldSkipPersist = () => {
  const storageKey = buildProjectScopedSessionKey(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
}

type majorLite = { code: string; name: string; defCoe: number | null; hasCost?: boolean; hasArea?: boolean }
const serviceEntries: Array<[string, majorLite]> = []
const detailDict: ScaleDictGroup[] = []
const idLabelMap = new Map<string, string>()
const rebuildScaleDictCaches = () => {
  const nextServiceEntries = getMajorDictEntries().map(({ id, item }) => [id, item] as [string, majorLite])
  serviceEntries.splice(0, serviceEntries.length, ...nextServiceEntries)
  const nextDetailDict = buildScaleDetailDict(
    serviceEntries,
    ({ hasArea }) => hasArea
  )
  detailDict.splice(0, detailDict.length, ...nextDetailDict)
  const nextIdLabelMap = buildScaleIdLabelMap(nextDetailDict)
  idLabelMap.clear()
  nextIdLabelMap.forEach((label, id) => {
    idLabelMap.set(id, label)
  })
}
rebuildScaleDictCaches()

const buildDefaultRows = (projectCountValue = getTargetProjectCount()): DetailRow[] => {
  return buildScaleRowsFromDict({
    detailDict,
    projectCount: projectCountValue,
    activeIndustryCode: activeIndustryCode.value,
    isMajorInIndustryScope: isMajorIdInIndustryScope,
    buildScopedRowId,
    buildProjectGroupPathKey,
    isMutipleService: isMutipleService.value,
    createRow: ({ projectIndex, group, child, rowId, path }) => ({
      id: rowId,
      projectIndex,
      majorDictId: child.id,
      groupCode: group.code,
      groupName: group.name,
      majorCode: child.code,
      majorName: child.name,
      hasCost: child.hasCost,
      hasArea: child.hasArea,
      amount: null,
      landArea: null,
      benchmarkBudget: null,
      benchmarkBudgetBasic: null,
      benchmarkBudgetOptional: null,
      benchmarkBudgetBasicChecked: true,
      benchmarkBudgetOptionalChecked: true,
      basicFormula: '',
      optionalFormula: '',
      consultCategoryFactor: null,
      majorFactor: null,
      workStageFactor: 1,
      workRatio: 1,
      budgetFee: null,
      budgetFeeBasic: null,
      budgetFeeOptional: null,
      remark: '',
      path
    })
  })
}

type SourceRow = Pick<DetailRow, 'id'> &
  Partial<
    Pick<
      DetailRow,
      | 'projectIndex'
      | 'majorDictId'
      | 'amount'
      | 'landArea'
      | 'benchmarkBudget'
      | 'benchmarkBudgetBasic'
      | 'benchmarkBudgetOptional'
      | 'benchmarkBudgetBasicChecked'
      | 'benchmarkBudgetOptionalChecked'
      | 'basicFormula'
      | 'optionalFormula'
      | 'consultCategoryFactor'
      | 'majorFactor'
      | 'workStageFactor'
      | 'workRatio'
      | 'budgetFee'
      | 'budgetFeeBasic'
      | 'budgetFeeOptional'
      | 'remark'
    >
  >
const mergeWithDictRows = (
  rowsFromDb: SourceRow[] | undefined,
  options?: {
    includeScaleValues?: boolean
    includeFactorValues?: boolean
    projectCount?: number
    cloneFromProjectOne?: boolean
  }
): DetailRow[] => {
  const includeScaleValues = options?.includeScaleValues ?? true
  const includeFactorValues = options?.includeFactorValues ?? true
  const targetProjectCount = normalizeProjectCount(options?.projectCount ?? getTargetProjectCount())
  return mergeScaleRowsFromProjectMajorMap({
    rowsFromDb,
    projectCount: targetProjectCount,
    buildDefaultRows,
    resolveProjectIndex: row => resolveRowProjectIndex(row),
    resolveMajorDictId: row => resolveRowMajorDictId(row),
    cloneFromProjectOne: options?.cloneFromProjectOne,
    mergeRow: (row, fromDb) => {
    if (!fromDb) return row
    const nextMajorDictId = resolveRowMajorDictId(row)
    const hasConsultCategoryFactor = Object.prototype.hasOwnProperty.call(fromDb, 'consultCategoryFactor')
    const hasMajorFactor = Object.prototype.hasOwnProperty.call(fromDb, 'majorFactor')

    return {
      ...row,
      amount: includeScaleValues && row.hasCost && typeof fromDb.amount === 'number' ? fromDb.amount : null,
      landArea: includeScaleValues && row.hasArea && typeof fromDb.landArea === 'number' ? fromDb.landArea : 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(nextMajorDictId),
      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 createBudgetCellRendererWithCheck = createScaleBudgetCellRendererToggleFactory(
  () => detailRows.value,
  () => handleCellValueChanged()
)

const getBenchmarkBudgetHeaderCheckState = (field: ScaleBudgetCheckField): ScaleBudgetHeaderCheckState => {
  const targetRows = detailRows.value.filter(row => row.hasArea)
  if (!targetRows.length) return 'all'
  const checkedCount = targetRows.reduce((count, row) => (row[field] !== false ? count + 1 : count), 0)
  if (checkedCount === 0) return 'none'
  if (checkedCount === targetRows.length) return 'all'
  return 'partial'
}

const toggleBenchmarkBudgetColumnChecked = (field: ScaleBudgetCheckField, checked: boolean) => {
  const targetRows = detailRows.value.filter(row => row.hasArea)
  if (!targetRows.length) return
  for (const row of targetRows) {
    row[field] = checked
  }
  gridApi.value?.refreshHeader()
  gridApi.value?.refreshCells({ force: true })
  handleCellValueChanged()
}

const getBenchmarkBudgetHeaderParams = (field: ScaleBudgetCheckField) => ({
  field,
  getHeaderCheckState: getBenchmarkBudgetHeaderCheckState,
  onToggleAll: toggleBenchmarkBudgetColumnChecked
})

const getBenchmarkBudgetSplitByLandArea = (row?: Pick<DetailRow, 'landArea'>) =>
  getCheckedBenchmarkBudgetSplitByRow(
    {
      landArea: row?.landArea,
      benchmarkBudgetBasicChecked: true,
      benchmarkBudgetOptionalChecked: true
    },
    'area'
  )

const getCheckedBenchmarkBudgetSplitByLandArea = (
  row?: Pick<DetailRow, 'landArea' | 'benchmarkBudgetBasicChecked' | 'benchmarkBudgetOptionalChecked'>
) => getCheckedBenchmarkBudgetSplitByRow(row, 'area')

const getBudgetFee = (
  row?: Pick<
    DetailRow,
    | 'landArea'
    | 'benchmarkBudgetBasicChecked'
    | 'benchmarkBudgetOptionalChecked'
    | 'majorFactor'
    | 'consultCategoryFactor'
    | 'workStageFactor'
    | 'workRatio'
  >
) => getScaleBudgetFeeByRow(row, 'area')

const getBudgetFeeSplit = (
  row?: Pick<
    DetailRow,
    | 'landArea'
    | 'benchmarkBudgetBasicChecked'
    | 'benchmarkBudgetOptionalChecked'
    | 'majorFactor'
    | 'consultCategoryFactor'
    | 'workStageFactor'
    | 'workRatio'
  >
) => getScaleBudgetFeeSplitByRow(row, 'area')

const formatEditableFlexibleNumber = (params: any) =>
  formatScaleEditableConditionalNumber(params, {
    enabled: Boolean(params.data?.hasArea),
    precision: 3,
    emptyText: t('pricingScale.clickToInput')
  })

const restoreLandAreaColumnDefaults = async () => {
  const htData = await kvStore.getItem<{ detailRows: SourceRow[]; totalAmount?: number | null }>(HT_DB_KEY.value)
  const sourceRows = Array.isArray(htData?.detailRows) ? htData.detailRows : []
  const sourceRowMap = buildContractScaleMap(sourceRows)
  const sourceRowIdMap = buildContractScaleIdMap(sourceRows)
  const projectTotals = buildContractScaleProjectTotals(sourceRows, htData?.totalAmount)
  const useSummaryLandArea = detailRows.value.length === 1
  await restoreScaleColumnDefaults({
    gridApi: gridApi.value,
    rows: detailRows.value,
    getCurrentValue: row => row.landArea,
    getNextValue: row => {
      if (!row.hasArea) return null
      if (useSummaryLandArea) return getContractScaleProjectTotalsByRow(row, projectTotals).landArea
      const sourceRow = getContractScaleRowByMajor(row, sourceRowMap, sourceRowIdMap)
      return typeof sourceRow?.landArea === 'number' ? sourceRow.landArea : null
    },
    isSameValue: isSameNullableNumber,
    applyValue: (row, nextValue) => {
      row.landArea = nextValue
    },
    afterApply: async () => {
      syncComputedValuesToDetailRows()
      await saveToIndexedDB({ skipComputedSync: true })
    }
  })
}

const restoreConsultCategoryFactorColumnDefaults = async () => {
  await loadFactorDefaults()
  const nextConsultFactor = getDefaultConsultCategoryFactor()
  await restoreScaleColumnDefaults({
    gridApi: gridApi.value,
    rows: detailRows.value,
    getCurrentValue: row => row.consultCategoryFactor,
    getNextValue: () => nextConsultFactor,
    isSameValue: isSameNullableNumber,
    applyValue: (row, nextValue) => {
      row.consultCategoryFactor = nextValue
    },
    afterApply: async () => {
      syncComputedValuesToDetailRows()
      await saveToIndexedDB({ skipComputedSync: true })
    }
  })
}

const restoreMajorFactorColumnDefaults = async () => {
  await loadFactorDefaults()
  await restoreScaleColumnDefaults({
    gridApi: gridApi.value,
    rows: detailRows.value,
    getCurrentValue: row => row.majorFactor,
    getNextValue: row => getDefaultMajorFactorById(resolveRowMajorDictId(row)),
    isSameValue: isSameNullableNumber,
    applyValue: (row, nextValue) => {
      row.majorFactor = nextValue
    },
    afterApply: async () => {
      syncComputedValuesToDetailRows()
      await saveToIndexedDB({ skipComputedSync: true })
    }
  })
}

const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
  createScaleValueColumn<DetailRow>({
    headerName: t('pricingScale.columns.landArea'),
    field: 'landArea',
    headerTooltip: t('pricingScale.tooltip.resetLandArea'),
    headerComponent: AgGridResetHeader,
    onReset: restoreLandAreaColumnDefaults,
    resetTitle: t('pricingScale.tooltip.resetLandArea'),
    minWidth: 90,
    flex: 2,
    isEditable: row => Boolean(row?.hasArea),
    emptyTextPredicate: (row, value) => Boolean(row?.hasArea) && (value == null || value === ''),
    valueParser: params => {
      const value = parseNumberOrNull(params.newValue)
      return value == null ? null : roundTo(value, 3)
    },
    valueFormatter: formatEditableFlexibleNumber
  }),
  createScaleBenchmarkBudgetColumnGroup<DetailRow>({
    getCheckedSplit: getCheckedBenchmarkBudgetSplitByLandArea,
    createBudgetCellRendererWithCheck,
    getHeaderComponent: () => ScaleBudgetToggleHeader,
    getHeaderComponentParams: getBenchmarkBudgetHeaderParams
  }),
  createScaleBudgetFeeColumnGroup<DetailRow>({
    headerComponent: AgGridResetHeader,
    restoreConsultCategoryFactorColumnDefaults,
    restoreMajorFactorColumnDefaults,
    parseNumberOrNull,
    getBudgetFee,
    aggFunc: decimalAggSum
  }),
  createScaleRemarkColumn<DetailRow>()
]
const gridColumnDefs = computed(() => withReadonlyAutoHeight(columnDefs))

const autoGroupColumnDef: ColDef = createScaleAutoGroupColumn<DetailRow>({
  totalLabel: totalLabel.value,
  idLabelMap,
  parseProjectIndexFromPathKey
})



const totalBudgetFeeBasic = computed(() => sumNullableBy(detailRows.value, row => getBudgetFeeSplit(row)?.basic))
const totalBudgetFeeOptional = computed(() => sumNullableBy(detailRows.value, row => getBudgetFeeSplit(row)?.optional))
const totalBudgetFee = computed(() => sumNullableBy(detailRows.value, row => getBudgetFee(row)))
const pinnedTopRowData = computed(() =>
  createPinnedTopRowData(
    createScalePinnedTotalRow<DetailRow>({
      landArea: null,
      budgetFee: totalBudgetFee.value,
      budgetFeeBasic: totalBudgetFeeBasic.value,
      budgetFeeOptional: totalBudgetFeeOptional.value
    })
  )
)

const syncComputedValuesToDetailRows = () => {
  recomputeScaleDetailRowsInPlace(detailRows.value, 'area')
}

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

const saveToIndexedDB = async (options?: { skipComputedSync?: boolean }) => {
  if (shouldSkipPersist()) return
  try {
    if (!options?.skipComputedSync) {
      syncComputedValuesToDetailRows()
    }
    const payload = {
      detailRows: JSON.parse(JSON.stringify(buildPersistDetailRows())),
      projectCount: getTargetProjectCount()
    }
    zxFwPricingStore.setServicePricingMethodState(
      props.contractId,
      props.serviceId,
      'landScale',
      payload,
      { force: true }
    )
    const synced = await syncPricingTotalToZxFw({
      contractId: props.contractId,
      serviceId: props.serviceId,
      field: 'landScale',
      value: totalBudgetFee.value
    })
    if (!synced) return
  } catch (error) {
    console.error('saveToIndexedDB failed:', error)
  }
}

const syncLinkedFieldsFromContractAndFactors = async () => {
  if (detailRows.value.length === 0) return
  const consultChangeState =
    (zxFwPricingStore.keyedStates[HT_CONSULT_FACTOR_CHANGE_KEY.value]
      ?? kvStore.entries[HT_CONSULT_FACTOR_CHANGE_KEY.value]
      ?? null) as FactorChangeState | null
  const majorChangeState =
    (zxFwPricingStore.keyedStates[HT_MAJOR_FACTOR_CHANGE_KEY.value]
      ?? kvStore.entries[HT_MAJOR_FACTOR_CHANGE_KEY.value]
      ?? null) as FactorChangeState | null

  const consultUpdatedAt = Number(consultChangeState?.updatedAt)
  const majorUpdatedAt = Number(majorChangeState?.updatedAt)
  const hasNewConsultChange = Number.isFinite(consultUpdatedAt) && consultUpdatedAt > lastAppliedConsultFactorChangeAt.value
  const hasNewMajorChange = Number.isFinite(majorUpdatedAt) && majorUpdatedAt > lastAppliedMajorFactorChangeAt.value

  const consultChangedRowIds = new Set(
    (consultChangeState?.changedRowIds || []).map(id => String(id || '').trim()).filter(Boolean)
  )
  const majorChangedRowIdSet = normalizeChangedScaleRowIds(majorChangeState?.changedRowIds)
  const shouldSyncConsultFactor = hasNewConsultChange && consultChangedRowIds.has(String(props.serviceId).trim())
  const shouldSyncMajorFactor = hasNewMajorChange && majorChangedRowIdSet.size > 0

  if (hasNewConsultChange) lastAppliedConsultFactorChangeAt.value = consultUpdatedAt
  if (hasNewMajorChange) lastAppliedMajorFactorChangeAt.value = majorUpdatedAt
  if (!shouldSyncConsultFactor && !shouldSyncMajorFactor) return

  await loadFactorDefaults()
  const consultFactor = getDefaultConsultCategoryFactor()
  let changed = false
  for (const row of detailRows.value) {
    if (shouldSyncConsultFactor && !isSameNullableNumber(row.consultCategoryFactor, consultFactor)) {
      row.consultCategoryFactor = consultFactor
      changed = true
    }
    if (!shouldSyncMajorFactor) continue
    const majorDictId = resolveRowMajorDictId(row)
    if (!majorChangedRowIdSet.has(majorDictId)) continue
    const nextMajorFactor = getDefaultMajorFactorById(majorDictId)
    if (isSameNullableNumber(row.majorFactor, nextMajorFactor)) continue
    row.majorFactor = nextMajorFactor
    changed = true
  }
  if (!changed) return
  syncComputedValuesToDetailRows()
  await saveToIndexedDB({ skipComputedSync: true })
}

const syncLinkedScaleValuesFromContract = async (changedRowIds?: string[]) => {
  if (detailRows.value.length === 0) return
  const htData = await kvStore.getItem<{ detailRows: SourceRow[]; totalAmount?: number | null }>(HT_DB_KEY.value)
  const sourceRows = Array.isArray(htData?.detailRows) ? htData.detailRows : []
  const sourceRowMap = buildContractScaleMap(sourceRows)
  const sourceRowIdMap = buildContractScaleIdMap(sourceRows)
  const projectTotals = buildContractScaleProjectTotals(sourceRows, htData?.totalAmount)
  const useSummaryLandArea = detailRows.value.length === 1
  const changedRowIdSet = changedRowIds?.length ? normalizeChangedScaleRowIds(changedRowIds) : null

  let changed = false
  for (const row of detailRows.value) {
    if (changedRowIdSet && !useSummaryLandArea) {
      const rowMajorId = resolveRowMajorDictId(row)
      if (!changedRowIdSet.has(rowMajorId)) continue
    }
    const sourceRow = getContractScaleRowByMajor(row, sourceRowMap, sourceRowIdMap)
    const nextLandArea = row.hasArea
      ? (useSummaryLandArea
          ? getContractScaleProjectTotalsByRow(row, projectTotals).landArea
          : (typeof sourceRow?.landArea === 'number' ? sourceRow.landArea : null))
      : null
    if (isSameNullableNumber(row.landArea, nextLandArea)) continue
    row.landArea = nextLandArea
    changed = true
  }
  if (!changed) return
  syncComputedValuesToDetailRows()
  await saveToIndexedDB({ skipComputedSync: true })
}

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

const linkedSourceSignature = computed(() => JSON.stringify({
  consultFactorChange:
    zxFwPricingStore.keyedStates[HT_CONSULT_FACTOR_CHANGE_KEY.value]
    ?? kvStore.entries[HT_CONSULT_FACTOR_CHANGE_KEY.value]
    ?? null,
  majorFactorChange:
    zxFwPricingStore.keyedStates[HT_MAJOR_FACTOR_CHANGE_KEY.value]
    ?? kvStore.entries[HT_MAJOR_FACTOR_CHANGE_KEY.value]
    ?? null
}))

const linkedContractScaleSignature = computed(() => JSON.stringify(
  kvStore.entries[HT_SCALE_CHANGE_KEY.value] ?? null
))

const buildRowsFromImportDefaultSource = async (
  targetProjectCount: number
): Promise<DetailRow[]> => {
  // 与“使用默认数据”同源：先强制刷新系数，再按合同卡片默认带出。
  await loadFactorDefaults()
  const htData = await kvStore.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
  const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
  return hasContractRows
    ? mergeWithDictRows(htData!.detailRows, {
        includeFactorValues: true,
        projectCount: targetProjectCount,
        cloneFromProjectOne: true
      })
    : buildDefaultRows(targetProjectCount).map(row => ({
        ...row,
        consultCategoryFactor: getDefaultConsultCategoryFactor(),
        majorFactor: getDefaultMajorFactorById(row.majorDictId || row.id)
      }))
}

const readBaseIndustryCode = async () => {
  const baseInfo = await kvStore.getItem<XmBaseInfoState>(BASE_INFO_KEY.value)
  return typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
}

const buildRowsFromStoredState = (rows: DetailRow[]) =>
  mergeWithDictRows(rows as any, {
    projectCount: getTargetProjectCount(),
    cloneFromProjectOne: true
  })

const buildEmptyRows = (targetProjectCount: number) => buildDefaultRows(targetProjectCount)

const applyDetailRows = (rows: DetailRow[]) => {
  detailRows.value = rows.map(row => ({
    ...row,
    benchmarkBudgetBasicChecked:
      typeof row.benchmarkBudgetBasicChecked === 'boolean' ? row.benchmarkBudgetBasicChecked : true,
    benchmarkBudgetOptionalChecked:
      typeof row.benchmarkBudgetOptionalChecked === 'boolean' ? row.benchmarkBudgetOptionalChecked : true
  }))
}

const relabelDetailRowsFromDict = async () => {
  rebuildScaleDictCaches()
  if (detailRows.value.length === 0) {
    gridApi.value?.refreshCells({ force: true })
    return
  }
  const majorMetaMap = new Map<string, Pick<DetailRow, 'groupCode' | 'groupName' | 'majorCode' | 'majorName'>>()
  for (const group of detailDict) {
    majorMetaMap.set(group.id, {
      groupCode: group.code,
      groupName: group.name,
      majorCode: group.code,
      majorName: group.name
    })
    for (const child of group.children) {
      majorMetaMap.set(child.id, {
        groupCode: group.code,
        groupName: group.name,
        majorCode: child.code,
        majorName: child.name
      })
    }
  }
  let changed = false
  detailRows.value = detailRows.value.map(row => {
    const meta = majorMetaMap.get(resolveRowMajorDictId(row))
    if (!meta) return row
    if (
      row.groupCode === meta.groupCode &&
      row.groupName === meta.groupName &&
      row.majorCode === meta.majorCode &&
      row.majorName === meta.majorName
    ) {
      return row
    }
    changed = true
    return {
      ...row,
      groupCode: meta.groupCode,
      groupName: meta.groupName,
      majorCode: meta.majorCode,
      majorName: meta.majorName
    }
  })
  gridApi.value?.refreshCells({ force: true })
  if (!changed) return
  syncComputedValuesToDetailRows()
  await saveToIndexedDB({ skipComputedSync: true })
}

const mergeProjectExpandedRow = (defaultRow: DetailRow, existingRow: DetailRow): DetailRow => ({
  ...defaultRow,
  ...existingRow,
  id: defaultRow.id,
  projectIndex: defaultRow.projectIndex,
  majorDictId: defaultRow.majorDictId,
  groupCode: defaultRow.groupCode,
  groupName: defaultRow.groupName,
  majorCode: defaultRow.majorCode,
  majorName: defaultRow.majorName,
  hasCost: defaultRow.hasCost,
  hasArea: defaultRow.hasArea,
  path: defaultRow.path
})

const persistProjectCountChange = async () => {
  syncComputedValuesToDetailRows()
  await saveToIndexedDB({ skipComputedSync: true })
}

const applyProjectCountChange = async (nextValue: unknown) => {
  await applyPricingScaleProjectCountChange({
    nextValue,
    setProjectCount: count => {
      projectCount.value = count
    },
    isMutipleService: isMutipleService.value,
    currentRows: detailRows.value,
    cloneRows: rows => rows.map(row => ({ ...row })),
    normalizeProjectCount,
    inferProjectCountFromRows: rows => inferProjectCountFromRows(rows),
    buildRowsForReducedCount: (rows, targetProjectCount) =>
      mergeWithDictRows(rows as any, { projectCount: targetProjectCount }),
    buildRowsFromImportDefaultSource,
    getRowKey: row => getScaleProjectMajorKeyFromRow(row),
    getRowProjectIndex: row => resolveRowProjectIndex(row),
    mergeExistingRow: mergeProjectExpandedRow,
    applyRows: applyDetailRows,
    afterApplyRows: persistProjectCountChange
  })
}

const loadFromIndexedDB = async () => {
  await loadPricingScalePaneRows({
    industry: {
      readIndustryCode: readBaseIndustryCode,
      setIndustryCode: code => {
        activeIndustryCode.value = code
      }
    },
    setProjectCount: count => {
      projectCount.value = count
    },
    ensureFactorDefaultsLoaded,
    shouldForceDefaultLoad,
    buildContractDefaultRows: buildRowsFromImportDefaultSource,
    loadStoredState: () =>
      zxFwPricingStore.loadServicePricingMethodState<DetailRow>(props.contractId, props.serviceId, 'landScale'),
    isMutipleService: isMutipleService.value,
    normalizeProjectCount,
    inferProjectCountFromRows: rows => inferProjectCountFromRows(rows as any),
    buildRowsFromStoredState,
    buildEmptyRows,
    getTargetProjectCount,
    applyRows: applyDetailRows,
    afterApplyRows: syncComputedValuesToDetailRows,
    onError: error => {
      console.error('loadFromIndexedDB failed:', error)
    }
  })
}

const importContractData = async () => {
  await importPricingScalePaneRows({
    industry: {
      readIndustryCode: readBaseIndustryCode,
      setIndustryCode: code => {
        activeIndustryCode.value = code
      }
    },
    getTargetProjectCount,
    buildContractDefaultRows: buildRowsFromImportDefaultSource,
    applyRows: applyDetailRows,
    saveRows: () => saveToIndexedDB(),
    onError: error => {
      console.error('importContractData failed:', error)
    }
  })
}

const clearAllData = async () => {
  await clearPricingScalePaneRows({
    getTargetProjectCount,
    buildEmptyRows,
    applyRows: applyDetailRows,
    saveRows: () => saveToIndexedDB(),
    onError: error => {
      console.error('clearAllData failed:', error)
    }
  })
}

let isBulkClipboardMutation = false

const commitGridChanges = async (source: string) => {
  await nextTick()
  syncComputedValuesToDetailRows()
  gridApi.value?.refreshHeader()
  gridApi.value?.refreshCells({ force: true })
  await saveToIndexedDB({ skipComputedSync: true })
  await nextTick()
  gridApi.value?.refreshHeader()
  gridApi.value?.refreshCells({ force: true })
}

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

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

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

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

usePricingPaneLifecycle({
  gridApi,
  loadFromIndexedDB,
  syncLinkedFields: syncLinkedFieldsFromContractAndFactors,
  linkedSourceSignature,
  linkedSecondarySignature: linkedContractScaleSignature,
  syncSecondaryLinkedFields: () => {
    const state = (kvStore.entries[HT_SCALE_CHANGE_KEY.value] as ContractScaleChangeState | undefined) ?? undefined
    return syncLinkedScaleValuesFromContract(state?.changedRowIds)
  },
  saveToIndexedDB: () => saveToIndexedDB()
})
watch(
  () => locale.value,
  () => {
    void relabelDetailRowsFromDict()
  }
)
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 === 'landArea' || field === 'consultCategoryFactor' || field === 'majorFactor' || field === 'workStageFactor') {
    return parseNumberOrNull(params.value, { precision: 3 })
  }
  if (field === 'workRatio') {
    return parseNumberOrNull(params.value, { precision: 2 })
  }
  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">
        <div class="flex items-center gap-3">
          <h3 class="text-sm font-semibold text-foreground">{{ t('pricingPane.land.title') }}</h3>
          <div v-if="isMutipleService" class="flex items-center gap-2">
            <span class="text-xs text-muted-foreground">{{ t('pricingPane.projectCount') }}</span>
            <NumberFieldRoot
              v-model="projectCount"
              :min="1"
              :step="1"
              class="inline-flex items-center rounded-md border bg-background"
              @update:model-value="value => void applyProjectCountChange(value)"
            >
              <NumberFieldDecrement class="cursor-pointer px-2 py-1 text-xs text-muted-foreground hover:bg-muted">-</NumberFieldDecrement>
              <NumberFieldInput class="h-7 w-14 border-x bg-transparent px-2 text-center text-xs outline-none" />
              <NumberFieldIncrement class="cursor-pointer px-2 py-1 text-xs text-muted-foreground hover:bg-muted">+</NumberFieldIncrement>
            </NumberFieldRoot>
          </div>
        </div>
        <div class="flex items-center gap-2">
          <AlertDialogRoot>
            <AlertDialogTrigger as-child>
              <Button type="button" variant="outline" size="sm">{{ t('common.clear') }}</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">{{ t('pricingPane.clearTitle') }}</AlertDialogTitle>
                <AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
                  {{ t('pricingPane.land.clearDesc') }}
                </AlertDialogDescription>
                <div class="mt-4 flex items-center justify-end gap-2">
                  <AlertDialogCancel as-child>
                    <Button variant="outline">{{ t('common.cancel') }}</Button>
                  </AlertDialogCancel>
                  <AlertDialogAction as-child>
                    <Button variant="destructive" @click="clearAllData">{{ t('pricingPane.confirmClear') }}</Button>
                  </AlertDialogAction>
                </div>
              </AlertDialogContent>
            </AlertDialogPortal>
          </AlertDialogRoot>
          <AlertDialogRoot>
            <AlertDialogTrigger as-child>
              <Button type="button" variant="outline" size="sm">{{ t('pricingPane.useDefault') }}</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">{{ t('pricingPane.overrideTitle') }}</AlertDialogTitle>
                <AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
                  {{ t('pricingPane.land.overrideDesc') }}
                </AlertDialogDescription>
                <div class="mt-4 flex items-center justify-end gap-2">
                  <AlertDialogCancel as-child>
                    <Button variant="outline">{{ t('common.cancel') }}</Button>
                  </AlertDialogCancel>
                  <AlertDialogAction as-child>
                    <Button @click="importContractData">{{ t('pricingPane.confirmOverride') }}</Button>
                  </AlertDialogAction>
                </div>
              </AlertDialogContent>
            </AlertDialogPortal>
          </AlertDialogRoot>
        </div>
      </div>

      <div :class="agGridWrapClass">
        <AgGridVue :style="agGridStyle" :rowData="detailRows" :pinnedTopRowData="pinnedTopRowData"
          :columnDefs="gridColumnDefs" :autoGroupColumnDef="autoGroupColumnDef" :gridOptions="detailGridOptions" :theme="myTheme"
          :animateRows="true"
          @grid-ready="handleGridReady"
          @cell-value-changed="handleCellValueChanged" :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"
          :processCellForClipboard="processCellForClipboard" :processCellFromClipboard="processCellFromClipboard"
          :undoRedoCellEditing="true" :undoRedoCellEditingLimit="20" />
      </div>
    </div>
  </div>
</template>
