import {
  expertList,
  getMajorDictEntries,
  getMajorIdAliasMap,
  getServiceDictById,
  taskList
} from '@/sql'
import { roundTo, sumByNumber, toDecimal, toFiniteNumberOrNull } from '@/lib/decimal'
import { getScaleBudgetFee } from '@/lib/pricingScaleFee'
import { getScaleBudgetFeeByRow } from '@/lib/pricingScaleDetail'
import { useZxFwPricingStore, type ServicePricingMethod } from '@/pinia/zxFwPricing'
import { useKvStore } from '@/pinia/kv'

interface StoredDetailRowsState<T = any> {
  detailRows?: T[]
  totalAmount?: number | null
}

interface StoredFactorState {
  detailRows?: Array<{
    id: string
    standardFactor?: number | null
    budgetValue?: number | null
  }>
}

type MaybeNumber = number | null | undefined

const sumByNumberNullable = <T>(list: T[], pick: (item: T) => MaybeNumber): number | null => {
  let hasValid = false
  const total = sumByNumber(list, item => {
    const value = toFiniteNumberOrNull(pick(item))
    if (value == null) return null
    hasValid = true
    return value
  })
  return hasValid ? total : null
}

const getOnlyCostScaleSummaryAmount = (
  rows?: Array<{ amount?: unknown; isGroupRow?: unknown }>,
  totalAmount?: unknown
) => {
  if (typeof totalAmount === 'number' && Number.isFinite(totalAmount)) return totalAmount
  const summaryRow = (rows || []).find(row => row?.isGroupRow === true)
  if (typeof summaryRow?.amount === 'number' && Number.isFinite(summaryRow.amount)) return summaryRow.amount
  return null
}

interface ScaleRow {
  id: string
  amount: number | null
  landArea: number | null
  benchmarkBudgetBasicChecked: boolean
  benchmarkBudgetOptionalChecked: boolean
  consultCategoryFactor: number | null
  majorFactor: number | null
  workStageFactor: number | null
  workRatio: number | null
}

interface WorkloadRow {
  id: string
  conversion: number | null
  workload: number | null
  basicFee: number | null
  budgetAdoptedUnitPrice: number | null
  consultCategoryFactor: number | null
}

interface HourlyRow {
  id: string
  adoptedBudgetUnitPrice: number | null
  personnelCount: number | null
  workdayCount: number | null
}

interface MajorLite {
  code: string
  defCoe: number | null
  hasCost?: boolean
  hasArea?: boolean
  industryId?: string | number | null
}

interface ServiceLite {
  defCoe: number | null
  onlyCostScale?: boolean | null
}

interface TaskLite {
  serviceID: number
  conversion: number | null
  defPrice: number | null
}

interface ExpertLite {
  defPrice: number | null
  manageCoe: number | null
}

interface XmBaseInfoState {
  projectIndustry?: string
}

export interface PricingMethodTotals {
  investScale: number | null
  landScale: number | null
  workload: number | null
  hourly: number | null
}

interface PricingMethodTotalsOptions {
  excludeInvestmentCostAndAreaRows?: boolean
}

interface PricingMethodDetailDbKeys {
  investScale: string
  landScale: string
  workload: string
  hourly: string
}

interface PricingMethodDefaultDetailRows {
  investScale: unknown[]
  landScale: unknown[]
  workload: unknown[]
  hourly: unknown[]
}

interface PricingMethodDefaultBuildContext {
  htData: StoredDetailRowsState | null
  consultCategoryFactorMap: Map<string, number | null>
  majorFactorMap: Map<string, number | null>
  industryId: string
  excludeInvestmentCostAndAreaRows: boolean
}

const ONLY_COST_SCALE_ROW_ID = '__only-cost-scale-total__'
const SERVICE_PRICING_METHODS: ServicePricingMethod[] = ['investScale', 'landScale', 'workload', 'hourly']

const getZxFwStoreSafely = () => {
  try {
    return useZxFwPricingStore()
  } catch {
    return null
  }
}

const getKvStoreSafely = () => {
  try {
    return useKvStore()
  } catch {
    return null
  }
}

const kvGetItem = async <T = unknown>(key: string): Promise<T | null> => {
  const store = getKvStoreSafely()
  if (!store) return null
  return store.getItem<T>(key)
}

const kvSetItem = async <T = unknown>(key: string, value: T): Promise<void> => {
  const store = getKvStoreSafely()
  if (!store) return
  await store.setItem(key, value)
}

const toStoredDetailRowsState = <TRow = unknown>(state: { detailRows?: TRow[] } | null | undefined): StoredDetailRowsState<TRow> | null => {
  if (!state || !Array.isArray(state.detailRows)) return null
  return {
    detailRows: JSON.parse(JSON.stringify(state.detailRows))
  }
}

const hasOwn = (obj: unknown, key: string) =>
  Object.prototype.hasOwnProperty.call(obj || {}, key)

const isGroupScaleRow = (row: unknown) =>
  Boolean(row && typeof row === 'object' && (row as Record<string, unknown>).isGroupRow === true)

const stripGroupScaleRows = <TRow>(rows: TRow[] | undefined): TRow[] =>
  (rows || []).filter(row => !isGroupScaleRow(row))

const getRowNumberOrFallback = (
  row: Record<string, unknown> | undefined,
  key: string,
  fallback: number | null
) => {
  if (!row) return fallback
  const value = toFiniteNumberOrNull(row[key])
  if (value != null) return value
  return hasOwn(row, key) ? null : fallback
}

const toRowMap = <TRow extends { id: string }>(rows?: TRow[]) => {
  const map = new Map<string, TRow>()
  for (const row of rows || []) {
    map.set(String(row.id), row)
  }
  return map
}

const parseScopedMajorId = (value: unknown) => {
  const raw = String(value || '').trim()
  const scoped = /^\d+::(.+)$/.exec(raw)
  return (scoped ? String(scoped[1] || '').trim() : raw) || raw
}

const hasScopedScaleRows = (rows?: Array<Record<string, unknown>>) =>
  (rows || []).some(row => {
    const id = String(row?.id || '')
    if (/^\d+::/.test(id)) return true
    const projectIndex = Number((row as { projectIndex?: unknown })?.projectIndex)
    return Number.isFinite(projectIndex) && projectIndex > 1
  })

const getDefaultConsultCategoryFactor = (serviceId: string | number) => {
  const service = (getServiceDictById() as Record<string, ServiceLite | undefined>)[String(serviceId)]
  return toFiniteNumberOrNull(service?.defCoe)
}

const isOnlyCostScaleService = (serviceId: string | number) => {
  const service = (getServiceDictById() as Record<string, ServiceLite | undefined>)[String(serviceId)]
  return service?.onlyCostScale === true
}

const majorById = new Map(getMajorDictEntries().map(({ id, item }) => [id, item as MajorLite]))
const majorIdAliasMap = getMajorIdAliasMap()

const getDefaultMajorFactorById = (id: string) => {
  const resolvedId = majorById.has(id) ? id : majorIdAliasMap.get(id) || id
  const major = majorById.get(resolvedId)
  return toFiniteNumberOrNull(major?.defCoe)
}

const isCostMajorById = (id: string) => {
  const resolvedId = majorById.has(id) ? id : majorIdAliasMap.get(id) || id
  const major = majorById.get(resolvedId)
  if (!major) return false
  return major.hasCost !== false
}

const isAreaMajorById = (id: string) => {
  const resolvedId = majorById.has(id) ? id : majorIdAliasMap.get(id) || id
  const major = majorById.get(resolvedId)
  if (!major) return false
  return major.hasArea !== false
}

const isDualScaleMajorById = (id: string) => {
  const resolvedId = majorById.has(id) ? id : majorIdAliasMap.get(id) || id
  const major = majorById.get(resolvedId)
  if (!major) return false
  const hasCost = major.hasCost !== false
  const hasArea = major.hasArea !== false
  return hasCost && hasArea
}

const getIndustryMajorEntryByIndustryId = (industryId: string | null | undefined) => {
  const key = String(industryId || '').trim()
  if (!key) return null
  for (const [id, item] of majorById.entries()) {
    const majorIndustryId = String(item?.industryId ?? '').trim()
    if (majorIndustryId === key && !String(item?.code || '').includes('-')) {
      return { id, item }
    }
  }
  return null
}

const resolveFactorValue = (
  row: { budgetValue?: number | null; standardFactor?: number | null } | undefined,
  fallback: number | null
) => {
  if (!row) return fallback
  if (hasOwn(row, 'budgetValue')) {
    return toFiniteNumberOrNull(row.budgetValue)
  }
  if (hasOwn(row, 'standardFactor')) {
    return toFiniteNumberOrNull(row.standardFactor)
  }
  return fallback
}

const buildConsultCategoryFactorMap = (state: StoredFactorState | null) => {
  const map = new Map<string, number | null>()
  const serviceDict = getServiceDictById() as Record<string, ServiceLite | undefined>
  for (const [id, item] of Object.entries(serviceDict)) {
    map.set(String(id), toFiniteNumberOrNull(item?.defCoe))
  }
  for (const row of state?.detailRows || []) {
    if (!row?.id) continue
    const id = String(row.id)
    map.set(id, resolveFactorValue(row, map.get(id) ?? null))
  }
  return map
}

const buildMajorFactorMap = (state: StoredFactorState | null) => {
  const map = new Map<string, number | null>()
  for (const [id, item] of majorById.entries()) {
    map.set(String(id), toFiniteNumberOrNull(item?.defCoe))
  }
  for (const row of state?.detailRows || []) {
    if (!row?.id) continue
    const rowId = String(row.id)
    const id = map.has(rowId) ? rowId : majorIdAliasMap.get(rowId) || rowId
    map.set(id, resolveFactorValue(row, map.get(id) ?? null))
  }
  return map
}

const getMajorLeafIds = () =>
  getMajorDictEntries()
    .filter(({ item }) => Boolean(item?.code && String(item.code).includes('-')))
    .map(({ id }) => id)

const buildDefaultScaleRows = (
  serviceId: string | number,
  consultCategoryFactorMap?: Map<string, number | null>,
  majorFactorMap?: Map<string, number | null>
): ScaleRow[] => {
  const defaultConsultCategoryFactor =
    consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId)
  return getMajorLeafIds().map(id => ({
    id,
    amount: null,
    landArea: null,
    benchmarkBudgetBasicChecked: true,
    benchmarkBudgetOptionalChecked: true,
    consultCategoryFactor: defaultConsultCategoryFactor,
    majorFactor: majorFactorMap?.get(id) ?? getDefaultMajorFactorById(id),
    workStageFactor: 1,
    workRatio: 100
  }))
}

const mergeScaleRows = (
  serviceId: string | number,
  rowsFromDb: Array<Partial<ScaleRow> & Pick<ScaleRow, 'id'>> | undefined,
  consultCategoryFactorMap?: Map<string, number | null>,
  majorFactorMap?: Map<string, number | null>
): ScaleRow[] => {
  const sourceRows = stripGroupScaleRows(rowsFromDb)
  const dbValueMap = toRowMap(sourceRows)
  for (const row of sourceRows) {
    const rowId = String(row.id)
    const nextId = majorIdAliasMap.get(rowId)
    if (nextId && !dbValueMap.has(nextId)) {
      dbValueMap.set(nextId, row as ScaleRow)
    }
  }

  const defaultConsultCategoryFactor =
    consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId)
  return buildDefaultScaleRows(serviceId, consultCategoryFactorMap, majorFactorMap).map(row => {
    const fromDb = dbValueMap.get(row.id)
    if (!fromDb) return row

    const hasConsultCategoryFactor = hasOwn(fromDb, 'consultCategoryFactor')
    const hasMajorFactor = hasOwn(fromDb, 'majorFactor')
    const hasWorkStageFactor = hasOwn(fromDb, 'workStageFactor')
    const hasWorkRatio = hasOwn(fromDb, 'workRatio')

    return {
      ...row,
      amount: toFiniteNumberOrNull(fromDb.amount),
      landArea: toFiniteNumberOrNull(fromDb.landArea),
      benchmarkBudgetBasicChecked:
        typeof (fromDb as { benchmarkBudgetBasicChecked?: unknown }).benchmarkBudgetBasicChecked === 'boolean'
          ? Boolean((fromDb as { benchmarkBudgetBasicChecked?: unknown }).benchmarkBudgetBasicChecked)
          : true,
      benchmarkBudgetOptionalChecked:
        typeof (fromDb as { benchmarkBudgetOptionalChecked?: unknown }).benchmarkBudgetOptionalChecked === 'boolean'
          ? Boolean((fromDb as { benchmarkBudgetOptionalChecked?: unknown }).benchmarkBudgetOptionalChecked)
          : true,
      consultCategoryFactor:
        toFiniteNumberOrNull(fromDb.consultCategoryFactor) ??
        (hasConsultCategoryFactor ? null : defaultConsultCategoryFactor),
      majorFactor:
        toFiniteNumberOrNull(fromDb.majorFactor) ??
        (hasMajorFactor ? null : (majorFactorMap?.get(row.id) ?? getDefaultMajorFactorById(row.id))),
      workStageFactor:
        toFiniteNumberOrNull((fromDb as Partial<ScaleRow>).workStageFactor) ??
        (hasWorkStageFactor ? null : row.workStageFactor),
      workRatio:
        toFiniteNumberOrNull((fromDb as Partial<ScaleRow>).workRatio) ??
        (hasWorkRatio ? null : row.workRatio)
    }
  })
}

const getInvestmentBudgetFee = (row: ScaleRow) => getScaleBudgetFeeByRow(row, 'cost')

const getOnlyCostScaleBudgetFee = (
  serviceId: string,
  rowsFromDb: Array<Record<string, unknown>> | undefined,
  consultCategoryFactorMap?: Map<string, number | null>,
  majorFactorMap?: Map<string, number | null>,
  industryId?: string | null,
  totalAmount?: number | null
) => {
  const industryMajorEntry = getIndustryMajorEntryByIndustryId(industryId)
  const rawRows = rowsFromDb || []
  const sourceRows = stripGroupScaleRows(rowsFromDb)
  const defaultConsultCategoryFactor =
    consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId)
  const defaultMajorFactor =
    (industryMajorEntry ? majorFactorMap?.get(industryMajorEntry.id) ?? null : null) ??
    toFiniteNumberOrNull(industryMajorEntry?.item?.defCoe) ??
    1

  // 新版 onlyCostScale 支持“按项目行”存储（如 1::majorId、2::majorId），每行需独立计费后求和。
  const usePerRowCalculation = sourceRows.some(row => {
    if (typeof row?.projectIndex === 'number' && Number.isFinite(row.projectIndex)) return true
    const id = String(row?.id || '')
    return /^\d+::/.test(id)
  })
  if (usePerRowCalculation) {
    return sumByNumberNullable(sourceRows, row => {
      const amount = toFiniteNumberOrNull(row?.amount)
      if (amount == null) return null
      return getScaleBudgetFeeByRow({
        amount,
        benchmarkBudgetBasicChecked: typeof row?.benchmarkBudgetBasicChecked === 'boolean' ? row.benchmarkBudgetBasicChecked : true,
        benchmarkBudgetOptionalChecked: typeof row?.benchmarkBudgetOptionalChecked === 'boolean' ? row.benchmarkBudgetOptionalChecked : true,
        majorFactor: getRowNumberOrFallback(row, 'majorFactor', defaultMajorFactor),
        consultCategoryFactor: getRowNumberOrFallback(row, 'consultCategoryFactor', defaultConsultCategoryFactor),
        workStageFactor: getRowNumberOrFallback(row, 'workStageFactor', 1),
        workRatio: getRowNumberOrFallback(row, 'workRatio', 100)
      }, 'cost')
    })
  }

  const resolvedTotalAmount = getOnlyCostScaleSummaryAmount(rawRows as Array<{ amount?: unknown; isGroupRow?: unknown }>, totalAmount)
  if (resolvedTotalAmount == null) return null
  const onlyRow =
    rawRows.find(row => String(row?.id || '') === ONLY_COST_SCALE_ROW_ID) ||
    sourceRows.find(row => hasOwn(row, 'consultCategoryFactor') || hasOwn(row, 'majorFactor')) ||
    sourceRows[0]
  const consultCategoryFactor = getRowNumberOrFallback(onlyRow, 'consultCategoryFactor', defaultConsultCategoryFactor)
  const majorFactor = getRowNumberOrFallback(onlyRow, 'majorFactor', defaultMajorFactor)
  const workStageFactor = getRowNumberOrFallback(onlyRow, 'workStageFactor', 1)
  const workRatio = getRowNumberOrFallback(onlyRow, 'workRatio', 100)
  return getScaleBudgetFeeByRow({
    amount: resolvedTotalAmount,
    benchmarkBudgetBasicChecked: typeof onlyRow?.benchmarkBudgetBasicChecked === 'boolean' ? onlyRow.benchmarkBudgetBasicChecked : true,
    benchmarkBudgetOptionalChecked: typeof onlyRow?.benchmarkBudgetOptionalChecked === 'boolean' ? onlyRow.benchmarkBudgetOptionalChecked : true,
    majorFactor,
    consultCategoryFactor,
    workStageFactor,
    workRatio
  }, 'cost')
}

const buildOnlyCostScaleDetailRows = (
  serviceId: string,
  rowsFromDb: Array<Record<string, unknown>> | undefined,
  consultCategoryFactorMap?: Map<string, number | null>,
  majorFactorMap?: Map<string, number | null>,
  industryId?: string | null,
  totalAmount?: number | null
) => {
  const rawRows = rowsFromDb || []
  const sourceRows = stripGroupScaleRows(rowsFromDb)
  const resolvedTotalAmount = getOnlyCostScaleSummaryAmount(rawRows as Array<{ amount?: unknown; isGroupRow?: unknown }>, totalAmount)
  const industryMajorEntry = getIndustryMajorEntryByIndustryId(industryId)
  const onlyCostRowId = industryMajorEntry?.id || ONLY_COST_SCALE_ROW_ID
  const onlyRow =
    rawRows.find(row => String(row?.id || '') === ONLY_COST_SCALE_ROW_ID) ||
    sourceRows.find(row => String(row?.id || '') === onlyCostRowId)
  const consultCategoryFactor = getRowNumberOrFallback(
    onlyRow,
    'consultCategoryFactor',
    consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId)
  )
  const majorFactor = getRowNumberOrFallback(
    onlyRow,
    'majorFactor',
    (industryMajorEntry ? majorFactorMap?.get(industryMajorEntry.id) ?? null : null) ??
      toFiniteNumberOrNull(industryMajorEntry?.item?.defCoe) ??
      1
  )
  const workStageFactor = getRowNumberOrFallback(onlyRow, 'workStageFactor', 1)
  const workRatio = getRowNumberOrFallback(onlyRow, 'workRatio', 100)

  return [
    {
      id: onlyCostRowId,
      amount: resolvedTotalAmount,
      landArea: null,
      consultCategoryFactor,
      majorFactor,
      workStageFactor,
      workRatio,
      benchmarkBudgetBasicChecked: typeof onlyRow?.benchmarkBudgetBasicChecked === 'boolean' ? onlyRow.benchmarkBudgetBasicChecked : true,
      benchmarkBudgetOptionalChecked: typeof onlyRow?.benchmarkBudgetOptionalChecked === 'boolean' ? onlyRow.benchmarkBudgetOptionalChecked : true
    }
  ]
}

const getLandBudgetFee = (row: ScaleRow) => getScaleBudgetFeeByRow(row, 'area')

const getTaskEntriesByServiceId = (serviceId: string | number) =>
  Object.entries(taskList as Record<string, TaskLite>)
    .sort((a, b) => Number(a[0]) - Number(b[0]))
    .filter(([, task]) => Number(task.serviceID) === Number(serviceId))

const buildDefaultWorkloadRows = (
  serviceId: string | number,
  consultCategoryFactorMap?: Map<string, number | null>
): WorkloadRow[] => {
  const defaultConsultCategoryFactor =
    consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId)
  return getTaskEntriesByServiceId(serviceId).map(([taskId, task], order) => ({
    id: `task-${taskId}-${order}`,
    conversion: toFiniteNumberOrNull(task.conversion),
    workload: null,
    basicFee: null,
    budgetAdoptedUnitPrice: toFiniteNumberOrNull(task.defPrice),
    consultCategoryFactor: defaultConsultCategoryFactor
  }))
}

const mergeWorkloadRows = (
  serviceId: string | number,
  rowsFromDb: Array<Partial<WorkloadRow> & Pick<WorkloadRow, 'id'>> | undefined,
  consultCategoryFactorMap?: Map<string, number | null>
): WorkloadRow[] => {
  const dbValueMap = toRowMap(rowsFromDb)

  return buildDefaultWorkloadRows(serviceId, consultCategoryFactorMap).map(row => {
    const fromDb = dbValueMap.get(row.id)
    if (!fromDb) return row

    return {
      ...row,
      workload: toFiniteNumberOrNull(fromDb.workload),
      basicFee: toFiniteNumberOrNull(fromDb.basicFee),
      budgetAdoptedUnitPrice: toFiniteNumberOrNull(fromDb.budgetAdoptedUnitPrice),
      consultCategoryFactor: toFiniteNumberOrNull(fromDb.consultCategoryFactor)
    }
  })
}

const calcWorkloadBasicFee = (row: WorkloadRow) => {
  if (
    row.budgetAdoptedUnitPrice == null ||
    row.conversion == null ||
    row.workload == null
  ) {
    return null
  }
  return roundTo(
    toDecimal(row.budgetAdoptedUnitPrice).mul(row.conversion).mul(row.workload),
    2
  )
}

const calcWorkloadServiceFee = (row: WorkloadRow) => {
  if (row.consultCategoryFactor == null) {
    return null
  }
  const basicFee = row.basicFee ?? calcWorkloadBasicFee(row)
  if (basicFee == null) return null
  return roundTo(
    toDecimal(basicFee).mul(row.consultCategoryFactor),
    2
  )
}

const getExpertEntries = () =>
  Object.entries(expertList as Record<string, ExpertLite>).sort((a, b) => Number(a[0]) - Number(b[0]))

const getDefaultHourlyAdoptedPrice = (expert: ExpertLite) => {
  if (expert.defPrice == null || expert.manageCoe == null) return null
  return roundTo(toDecimal(expert.defPrice).mul(expert.manageCoe), 2)
}

const buildDefaultHourlyRows = (): HourlyRow[] =>
  getExpertEntries().map(([expertId, expert]) => ({
    id: `expert-${expertId}`,
    adoptedBudgetUnitPrice: getDefaultHourlyAdoptedPrice(expert),
    personnelCount: null,
    workdayCount: null
  }))

const mergeHourlyRows = (
  rowsFromDb: Array<Partial<HourlyRow> & Pick<HourlyRow, 'id'>> | undefined
): HourlyRow[] => {
  const dbValueMap = toRowMap(rowsFromDb)

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

    return {
      ...row,
      adoptedBudgetUnitPrice: toFiniteNumberOrNull(fromDb.adoptedBudgetUnitPrice),
      personnelCount: toFiniteNumberOrNull(fromDb.personnelCount),
      workdayCount: toFiniteNumberOrNull(fromDb.workdayCount)
    }
  })
}

const calcHourlyServiceBudget = (row: HourlyRow) => {
  if (row.adoptedBudgetUnitPrice == null || row.personnelCount == null || row.workdayCount == null) return null
  return roundTo(toDecimal(row.adoptedBudgetUnitPrice).mul(row.personnelCount).mul(row.workdayCount), 2)
}

const resolveScaleRows = (
  serviceId: string,
  pricingData: StoredDetailRowsState | null,
  htData: StoredDetailRowsState | null,
  consultCategoryFactorMap?: Map<string, number | null>,
  majorFactorMap?: Map<string, number | null>
) => {
  if (pricingData?.detailRows != null) {
    return mergeScaleRows(
      serviceId,
      pricingData.detailRows as any,
      consultCategoryFactorMap,
      majorFactorMap
    )
  }
  if (htData?.detailRows != null) {
    return mergeScaleRows(
      serviceId,
      stripGroupScaleRows(htData.detailRows as any),
      consultCategoryFactorMap,
      majorFactorMap
    )
  }
  return buildDefaultScaleRows(serviceId, consultCategoryFactorMap, majorFactorMap)
}

const normalizeScopedScaleRows = (
  serviceId: string,
  rowsFromDb: Array<Record<string, unknown>> | undefined,
  consultCategoryFactorMap?: Map<string, number | null>,
  majorFactorMap?: Map<string, number | null>
): ScaleRow[] => {
  const rows = stripGroupScaleRows(rowsFromDb) as Array<Record<string, unknown>>
  if (rows.length === 0) return []
  const defaultConsultCategoryFactor =
    consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId)

  return rows.map(row => {
    const parsedMajorId = parseScopedMajorId(row.id)
    const resolvedMajorId = majorById.has(parsedMajorId) ? parsedMajorId : (majorIdAliasMap.get(parsedMajorId) || parsedMajorId)
    const hasConsultCategoryFactor = hasOwn(row, 'consultCategoryFactor')
    const hasMajorFactor = hasOwn(row, 'majorFactor')
    const hasWorkStageFactor = hasOwn(row, 'workStageFactor')
    const hasWorkRatio = hasOwn(row, 'workRatio')
    return {
      id: resolvedMajorId,
      amount: toFiniteNumberOrNull(row.amount),
      landArea: toFiniteNumberOrNull(row.landArea),
      benchmarkBudgetBasicChecked:
        typeof row.benchmarkBudgetBasicChecked === 'boolean' ? row.benchmarkBudgetBasicChecked : true,
      benchmarkBudgetOptionalChecked:
        typeof row.benchmarkBudgetOptionalChecked === 'boolean' ? row.benchmarkBudgetOptionalChecked : true,
      consultCategoryFactor:
        toFiniteNumberOrNull(row.consultCategoryFactor) ??
        (hasConsultCategoryFactor ? null : defaultConsultCategoryFactor),
      majorFactor:
        toFiniteNumberOrNull(row.majorFactor) ??
        (hasMajorFactor ? null : (majorFactorMap?.get(resolvedMajorId) ?? getDefaultMajorFactorById(resolvedMajorId))),
      workStageFactor:
        toFiniteNumberOrNull(row.workStageFactor) ??
        (hasWorkStageFactor ? null : 1),
      workRatio:
        toFiniteNumberOrNull(row.workRatio) ??
        (hasWorkRatio ? null : 100)
    }
  })
}

// 统一生成某合同下某个咨询服务四种计费方式的存储键。
// 优先复用 Pinia store 当前约定的 key，避免与旧版 fallback key 脱节。
export const getPricingMethodDetailDbKeys = (
  contractId: string,
  serviceId: string | number
): PricingMethodDetailDbKeys => {
  const normalizedServiceId = String(serviceId)
  const store = getZxFwStoreSafely()
  if (store) {
    return {
      investScale: store.getServicePricingStorageKey(contractId, normalizedServiceId, 'investScale'),
      landScale: store.getServicePricingStorageKey(contractId, normalizedServiceId, 'landScale'),
      workload: store.getServicePricingStorageKey(contractId, normalizedServiceId, 'workload'),
      hourly: store.getServicePricingStorageKey(contractId, normalizedServiceId, 'hourly')
    }
  }
  return {
    investScale: `tzGMF-${contractId}-${normalizedServiceId}`,
    landScale: `ydGMF-${contractId}-${normalizedServiceId}`,
    workload: `gzlF-${contractId}-${normalizedServiceId}`,
    hourly: `hourlyPricing-${contractId}-${normalizedServiceId}`
  }
}

const loadPricingMethodDefaultBuildContext = async (
  contractId: string,
  options?: PricingMethodTotalsOptions
): Promise<PricingMethodDefaultBuildContext> => {
  const htDbKey = `ht-info-v3-${contractId}`
  const consultFactorDbKey = `ht-consult-category-factor-v1-${contractId}`
  const majorFactorDbKey = `ht-major-factor-v1-${contractId}`
  const baseInfoDbKey = 'xm-base-info-v1'

  const [htData, consultFactorData, majorFactorData, baseInfo] = await Promise.all([
    kvGetItem<StoredDetailRowsState>(htDbKey),
    kvGetItem<StoredFactorState>(consultFactorDbKey),
    kvGetItem<StoredFactorState>(majorFactorDbKey),
    kvGetItem<XmBaseInfoState>(baseInfoDbKey)
  ])

  return {
    htData,
    consultCategoryFactorMap: buildConsultCategoryFactorMap(consultFactorData),
    majorFactorMap: buildMajorFactorMap(majorFactorData),
    industryId: typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : '',
    excludeInvestmentCostAndAreaRows: options?.excludeInvestmentCostAndAreaRows === true
  }
}

const buildDefaultPricingMethodDetailRows = (
  serviceId: string,
  context: PricingMethodDefaultBuildContext
): PricingMethodDefaultDetailRows => {
  const onlyCostScale = isOnlyCostScaleService(serviceId)
  const scaleRows = resolveScaleRows(
    serviceId,
    null,
    context.htData,
    context.consultCategoryFactorMap,
    context.majorFactorMap
  )

  const investScale = onlyCostScale
    ? buildOnlyCostScaleDetailRows(
        serviceId,
        context.htData?.detailRows as Array<Record<string, unknown>> | undefined,
        context.consultCategoryFactorMap,
        context.majorFactorMap,
        context.industryId,
        context.htData?.totalAmount ?? null
      )
    : scaleRows.filter(row => {
        if (!isCostMajorById(row.id)) return false
        if (context.excludeInvestmentCostAndAreaRows && isDualScaleMajorById(row.id)) return false
        return true
      })
  const landScale = scaleRows.filter(row => isAreaMajorById(row.id))

  return {
    investScale,
    landScale,
    workload: buildDefaultWorkloadRows(serviceId, context.consultCategoryFactorMap),
    hourly: buildDefaultHourlyRows()
  }
}

// 强制为一组服务重建并落库默认明细行。
// 这个方法会同时写入 Pinia 内存态和底层 KV 存储，适合“重置为默认值”场景。
export const persistDefaultPricingMethodDetailRowsForServices = async (params: {
  contractId: string
  serviceIds: Array<string | number>
  options?: PricingMethodTotalsOptions
}) => {
  const uniqueServiceIds = Array.from(new Set(params.serviceIds.map(serviceId => String(serviceId))))
  if (uniqueServiceIds.length === 0) return

  const context = await loadPricingMethodDefaultBuildContext(params.contractId, params.options)
  const store = getZxFwStoreSafely()

  await Promise.all(
    uniqueServiceIds.map(async serviceId => {
      const dbKeys = getPricingMethodDetailDbKeys(params.contractId, serviceId)
      const defaultRows = buildDefaultPricingMethodDetailRows(serviceId, context)
      if (store) {
        for (const method of SERVICE_PRICING_METHODS) {
          store.setServicePricingMethodState(params.contractId, serviceId, method, {
            detailRows: defaultRows[method]
          }, { force: true })
        }
      }
      await Promise.all([
        kvSetItem(dbKeys.investScale, { detailRows: defaultRows.investScale }),
        kvSetItem(dbKeys.landScale, { detailRows: defaultRows.landScale }),
        kvSetItem(dbKeys.workload, { detailRows: defaultRows.workload }),
        kvSetItem(dbKeys.hourly, { detailRows: defaultRows.hourly })
      ])
    })
  )
}

// 汇总单个服务的四类计费方式金额。
// 数据读取顺序是：优先读当前 Pinia 中已加载的计费页数据，缺失时再回退到 KV 存储和合同段默认信息。
export const getPricingMethodTotalsForService = async (params: {
  contractId: string
  serviceId: string | number
  options?: PricingMethodTotalsOptions
}): Promise<PricingMethodTotals> => {
  const serviceId = String(params.serviceId)
  const htDbKey = `ht-info-v3-${params.contractId}`
  const consultFactorDbKey = `ht-consult-category-factor-v1-${params.contractId}`
  const majorFactorDbKey = `ht-major-factor-v1-${params.contractId}`
  const baseInfoDbKey = 'xm-base-info-v1'
  const dbKeys = getPricingMethodDetailDbKeys(params.contractId, serviceId)
  const store = getZxFwStoreSafely()

  const [storeInvestData, storeLandData, storeWorkloadData, storeHourlyData, htData, consultFactorData, majorFactorData, baseInfo] = await Promise.all([
    store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'investScale') || Promise.resolve(null),
    store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'landScale') || Promise.resolve(null),
    store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'workload') || Promise.resolve(null),
    store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'hourly') || Promise.resolve(null),
    kvGetItem<StoredDetailRowsState>(htDbKey),
    kvGetItem<StoredFactorState>(consultFactorDbKey),
    kvGetItem<StoredFactorState>(majorFactorDbKey),
    kvGetItem<XmBaseInfoState>(baseInfoDbKey)
  ])

  const [investDataFallback, landDataFallback, workloadDataFallback, hourlyDataFallback] = await Promise.all([
    storeInvestData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.investScale),
    storeLandData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.landScale),
    storeWorkloadData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.workload),
    storeHourlyData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.hourly)
  ])

  const investData = toStoredDetailRowsState(storeInvestData) || investDataFallback
  const landData = toStoredDetailRowsState(storeLandData) || landDataFallback
  const workloadData = toStoredDetailRowsState(storeWorkloadData) || workloadDataFallback
  const hourlyData = toStoredDetailRowsState(storeHourlyData) || hourlyDataFallback

  const consultCategoryFactorMap = buildConsultCategoryFactorMap(consultFactorData)
  const majorFactorMap = buildMajorFactorMap(majorFactorData)
  const onlyCostScale = isOnlyCostScaleService(serviceId)
  const industryId = typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''

  // 优先使用对应计费页的数据；不存在时回退合同段规模信息，再回退默认字典行。
  const excludeInvestmentCostAndAreaRows = params.options?.excludeInvestmentCostAndAreaRows === true
  const investScaleRowsSource = stripGroupScaleRows(investData?.detailRows as Array<Record<string, unknown>> | undefined)
  const landScaleRowsSource = stripGroupScaleRows(landData?.detailRows as Array<Record<string, unknown>> | undefined)
  const scopedInvestRows = hasScopedScaleRows(investScaleRowsSource)
    ? normalizeScopedScaleRows(serviceId, investScaleRowsSource, consultCategoryFactorMap, majorFactorMap)
    : null
  const scopedLandRows = hasScopedScaleRows(landScaleRowsSource)
    ? normalizeScopedScaleRows(serviceId, landScaleRowsSource, consultCategoryFactorMap, majorFactorMap)
    : null
  const investScale = onlyCostScale
    ? getOnlyCostScaleBudgetFee(
        serviceId,
        (investData?.detailRows as Array<Record<string, unknown>> | undefined) ||
          (htData?.detailRows as Array<Record<string, unknown>> | undefined),
        consultCategoryFactorMap,
        majorFactorMap,
        industryId,
        htData?.totalAmount ?? null
      )
      : (() => {
        const investRows = scopedInvestRows || resolveScaleRows(
          serviceId,
          investData,
          htData,
          consultCategoryFactorMap,
          majorFactorMap
        )
        return sumByNumberNullable(investRows, row => {
          if (!isCostMajorById(row.id)) return null
          if (excludeInvestmentCostAndAreaRows && isDualScaleMajorById(row.id)) return null
          return getInvestmentBudgetFee(row)
        })
      })()

  const landRows = scopedLandRows || resolveScaleRows(
    serviceId,
    landData,
    htData,
    consultCategoryFactorMap,
    majorFactorMap
  )
  const landScale = sumByNumberNullable(landRows, row => (isAreaMajorById(row.id) ? getLandBudgetFee(row) : null))

  const defaultWorkloadRows = buildDefaultWorkloadRows(serviceId, consultCategoryFactorMap)
  const workload =
    defaultWorkloadRows.length === 0
      ? null
      : sumByNumberNullable(
          workloadData?.detailRows != null
            ? mergeWorkloadRows(serviceId, workloadData.detailRows as any, consultCategoryFactorMap)
            : defaultWorkloadRows,
          row => calcWorkloadServiceFee(row)
        )

  const hourlyRows =
    hourlyData?.detailRows != null
      ? mergeHourlyRows(hourlyData.detailRows as any)
      : buildDefaultHourlyRows()
  const hourly = sumByNumberNullable(hourlyRows, row => calcHourlyServiceBudget(row))

  return {
    investScale,
    landScale,
    workload,
    hourly
  }
}

// 为一组服务补齐缺失的计费明细行，但不会覆盖已有用户数据。
// 适合在首次进入计费页或新增服务后做“按需初始化”。
export const ensurePricingMethodDetailRowsForServices = async (params: {
  contractId: string
  serviceIds: Array<string | number>
  options?: PricingMethodTotalsOptions
}) => {
  const uniqueServiceIds = Array.from(new Set(params.serviceIds.map(serviceId => String(serviceId))))
  if (uniqueServiceIds.length === 0) return

  const context = await loadPricingMethodDefaultBuildContext(params.contractId, params.options)
  const store = getZxFwStoreSafely()

  await Promise.all(
    uniqueServiceIds.map(async serviceId => {
      const dbKeys = getPricingMethodDetailDbKeys(params.contractId, serviceId)
      const [storeInvestData, storeLandData, storeWorkloadData, storeHourlyData] = await Promise.all([
        store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'investScale') || Promise.resolve(null),
        store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'landScale') || Promise.resolve(null),
        store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'workload') || Promise.resolve(null),
        store?.loadServicePricingMethodState<Record<string, unknown>>(params.contractId, serviceId, 'hourly') || Promise.resolve(null)
      ])
      const [investDataFallback, landDataFallback, workloadDataFallback, hourlyDataFallback] = await Promise.all([
        storeInvestData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.investScale),
        storeLandData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.landScale),
        storeWorkloadData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.workload),
        storeHourlyData ? Promise.resolve(null) : kvGetItem<StoredDetailRowsState>(dbKeys.hourly)
      ])
      const investData = toStoredDetailRowsState(storeInvestData) || investDataFallback
      const landData = toStoredDetailRowsState(storeLandData) || landDataFallback
      const workloadData = toStoredDetailRowsState(storeWorkloadData) || workloadDataFallback
      const hourlyData = toStoredDetailRowsState(storeHourlyData) || hourlyDataFallback

      const shouldInitInvest = !Array.isArray(investData?.detailRows) || investData!.detailRows!.length === 0
      const shouldInitLand = !Array.isArray(landData?.detailRows) || landData!.detailRows!.length === 0
      const shouldInitWorkload = !Array.isArray(workloadData?.detailRows) || workloadData!.detailRows!.length === 0
      const shouldInitHourly = !Array.isArray(hourlyData?.detailRows) || hourlyData!.detailRows!.length === 0

      const writeTasks: Promise<unknown>[] = []
      let defaultRows: PricingMethodDefaultDetailRows | null = null
      const getDefaultRows = () => {
        if (!defaultRows) {
          defaultRows = buildDefaultPricingMethodDetailRows(serviceId, context)
        }
        return defaultRows
      }

      if (shouldInitInvest) {
        if (store) {
          store.setServicePricingMethodState(params.contractId, serviceId, 'investScale', {
            detailRows: getDefaultRows().investScale
          }, { force: true })
        }
        writeTasks.push(kvSetItem(dbKeys.investScale, { detailRows: getDefaultRows().investScale }))
      }

      if (shouldInitLand) {
        if (store) {
          store.setServicePricingMethodState(params.contractId, serviceId, 'landScale', {
            detailRows: getDefaultRows().landScale
          }, { force: true })
        }
        writeTasks.push(kvSetItem(dbKeys.landScale, { detailRows: getDefaultRows().landScale }))
      }

      if (shouldInitWorkload) {
        if (store) {
          store.setServicePricingMethodState(params.contractId, serviceId, 'workload', {
            detailRows: getDefaultRows().workload
          }, { force: true })
        }
        writeTasks.push(kvSetItem(dbKeys.workload, { detailRows: getDefaultRows().workload }))
      }

      if (shouldInitHourly) {
        if (store) {
          store.setServicePricingMethodState(params.contractId, serviceId, 'hourly', {
            detailRows: getDefaultRows().hourly
          }, { force: true })
        }
        writeTasks.push(kvSetItem(dbKeys.hourly, { detailRows: getDefaultRows().hourly }))
      }

      if (writeTasks.length > 0) {
        await Promise.all(writeTasks)
      }
    })
  )
}

// 并行汇总多个服务的计费结果，返回以 serviceId 为 key 的 Map。
export const getPricingMethodTotalsForServices = async (params: {
  contractId: string
  serviceIds: Array<string | number>
  options?: PricingMethodTotalsOptions
}) => {
  const result = new Map<string, PricingMethodTotals>()
  await Promise.all(
    params.serviceIds.map(async serviceId => {
      const totals = await getPricingMethodTotalsForService({
        contractId: params.contractId,
        serviceId,
        options: params.options
      })
      result.set(String(serviceId), totals)
    })
  )
  return result
}
