<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { AgGridVue } from 'ag-grid-vue3'
import type { ColDef } from 'ag-grid-community'
import localforage from 'localforage'
import { getBasicFeeFromScale, majorList, serviceList } from '@/sql'
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
import { addNumbers, decimalAggSum, roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'

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

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

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

interface DetailRow {
  id: string
  groupCode: string
  groupName: string
  majorCode: string
  majorName: string
  amount: number | null
  benchmarkBudget: number | null
  consultCategoryFactor: number | null
  majorFactor: number | null
  budgetFee: number | null
  remark: string
  path: string[]
}

interface XmInfoState {
  projectName: string
  detailRows: DetailRow[]
}

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 PRICING_CLEAR_SKIP_PREFIX = 'pricing-clear-skip:'
const PRICING_FORCE_DEFAULT_PREFIX = 'pricing-force-default:'
const pricingPaneReloadStore = usePricingPaneReloadStore()

const shouldSkipPersist = () => {
  const storageKey = `${PRICING_CLEAR_SKIP_PREFIX}${DB_KEY.value}`
  const raw = sessionStorage.getItem(storageKey)
  if (!raw) return false
  const skipUntil = Number(raw)
  if (Number.isFinite(skipUntil) && Date.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 serviceLite = { defCoe: number | null }
const defaultConsultCategoryFactor = computed<number | null>(() => {
  const service = (serviceList as Record<string, serviceLite | undefined>)[String(props.serviceId)]
  return typeof service?.defCoe === 'number' && Number.isFinite(service.defCoe) ? service.defCoe : null
})

type majorLite = { code: string; name: string; defCoe: number | null }
const serviceEntries = Object.entries(majorList as Record<string, majorLite>)
  .sort((a, b) => Number(a[0]) - Number(b[0]))
  .filter((entry): entry is [string, majorLite] => {
    const item = entry[1]
    return Boolean(item?.code && item?.name)
  })

const getDefaultMajorFactorById = (id: string): number | null => {
  const major = (majorList as Record<string, majorLite | undefined>)[id]
  return typeof major?.defCoe === 'number' && Number.isFinite(major.defCoe) ? major.defCoe : null
}

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

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

  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[] => {
  const rows: DetailRow[] = []
  for (const group of detailDict) {
    for (const child of group.children) {
      rows.push({
        id: child.id,
        groupCode: group.code,
        groupName: group.name,
        majorCode: child.code,
        majorName: child.name,
        amount: null,
        benchmarkBudget: null,
        consultCategoryFactor: defaultConsultCategoryFactor.value,
        majorFactor: getDefaultMajorFactorById(child.id),
        budgetFee: null,
        remark: '',
        path: [group.id, child.id]
      })
    }
  }
  return rows
}

type SourceRow = Pick<DetailRow, 'id'> & Partial<Pick<DetailRow, 'amount' | 'benchmarkBudget' | 'consultCategoryFactor' | 'majorFactor' | 'budgetFee' | 'remark'>>
const mergeWithDictRows = (rowsFromDb: SourceRow[] | undefined): DetailRow[] => {
  const dbValueMap = new Map<string, SourceRow>()
  for (const row of rowsFromDb || []) {
    dbValueMap.set(row.id, 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: typeof fromDb.amount === 'number' ? fromDb.amount : null,
      benchmarkBudget: typeof fromDb.benchmarkBudget === 'number' ? fromDb.benchmarkBudget : null,
      consultCategoryFactor:
        typeof fromDb.consultCategoryFactor === 'number'
          ? fromDb.consultCategoryFactor
          : hasConsultCategoryFactor
            ? null
            : defaultConsultCategoryFactor.value,
      majorFactor:
        typeof fromDb.majorFactor === 'number'
          ? fromDb.majorFactor
          : hasMajorFactor
            ? null
            : getDefaultMajorFactorById(row.id),
      budgetFee: typeof fromDb.budgetFee === 'number' ? fromDb.budgetFee : null,
      remark: typeof fromDb.remark === 'string' ? fromDb.remark : ''
    }
  })
}

const parseNumberOrNull = (value: unknown) => {
  if (value === '' || value == null) return null
  const v = Number(value)
  return Number.isFinite(v) ? v : null
}

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) => {
  if (params.node?.group) {
    if (defaultConsultCategoryFactor.value == null) return ''
    return Number(defaultConsultCategoryFactor.value).toFixed(2)
  }
  return formatEditableNumber(params)
}

const formatMajorFactor = (params: any) => {
  if (params.node?.group) {
    const groupId = String(params.node?.key || '')
    const v = getDefaultMajorFactorById(groupId)
    if (v == null) return ''
    return Number(v).toFixed(2)
  }
  return formatEditableNumber(params)
}

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

const getBenchmarkBudgetByAmount = (row?: Pick<DetailRow, 'amount'>) => {
  const result = getBasicFeeFromScale(row?.amount, 'cost')
  return result ? roundTo(addNumbers(result.basic, result.optional), 2) : null
}

const getBudgetFee = (row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor'>) => {
  const benchmarkBudget = getBenchmarkBudgetByAmount(row)
  if (benchmarkBudget == null || row?.majorFactor == null || row?.consultCategoryFactor == null) return null
  return roundTo(toDecimal(benchmarkBudget).mul(row.majorFactor).mul(row.consultCategoryFactor), 2)
}

const columnDefs: ColDef<DetailRow>[] = [
  {
    headerName: '造价金额(万元)',
    field: 'amount',
    minWidth: 170,
    flex: 1,

    editable: params => !params.node?.group && !params.node?.rowPinned,
    cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
    cellClassRules: {
      'editable-cell-empty': params =>
        !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
    },
    aggFunc: decimalAggSum,
    valueParser: params => parseNumberOrNull(params.newValue),
    valueFormatter: formatEditableNumber
  },
  {
    headerName: '基准预算(元)',
    field: 'benchmarkBudget',
    minWidth: 170,
    flex: 1,
    aggFunc: decimalAggSum,
    valueGetter: params => getBenchmarkBudgetByAmount(params.data),
    valueParser: params => parseNumberOrNull(params.newValue),
    valueFormatter: formatReadonlyNumber
  },
  {
    headerName: '咨询分类系数',
    field: 'consultCategoryFactor',
    minWidth: 150,
    flex: 1,
    editable: params => !params.node?.group && !params.node?.rowPinned,
    cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
    cellClassRules: {
      'editable-cell-empty': params =>
        !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
    },
    valueParser: params => parseNumberOrNull(params.newValue),
    valueFormatter: formatConsultCategoryFactor
  },
  {
    headerName: '专业系数',
    field: 'majorFactor',
    minWidth: 130,
    flex: 1,
    editable: params => !params.node?.group && !params.node?.rowPinned,
    cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
    cellClassRules: {
      'editable-cell-empty': params =>
        !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
    },
    valueParser: params => parseNumberOrNull(params.newValue),
    valueFormatter: formatMajorFactor
  },
  {
    headerName: '预算费用',
    field: 'budgetFee',
    minWidth: 150,
    flex: 1,
    aggFunc: decimalAggSum,
    valueGetter: params => (params.node?.rowPinned ? params.data?.budgetFee ?? null : getBudgetFee(params.data)),
    valueParser: params => parseNumberOrNull(params.newValue),
    valueFormatter: formatReadonlyNumber
  },
  {
    headerName: '说明',
    field: 'remark',
    minWidth: 180,
    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: 320,
  pinned: 'left',
  flex: 2,

  cellRendererParams: {
    suppressCount: true
  },
  valueFormatter: params => {
    if (params.node?.rowPinned) {
      return '总合计'
    }
    const nodeId = String(params.value || '')
    return idLabelMap.get(nodeId) || nodeId
  }
}


const totalAmount = computed(() => sumByNumber(detailRows.value, row => row.amount))

const totalBenchmarkBudget = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetByAmount(row)))

const totalBudgetFee = computed(() => sumByNumber(detailRows.value, row => getBudgetFee(row)))
const pinnedTopRowData = computed(() => [
  {
    id: 'pinned-total-row',
    groupCode: '',
    groupName: '',
    majorCode: '',
    majorName: '',
    amount: totalAmount.value,
    benchmarkBudget: totalBenchmarkBudget.value,
    consultCategoryFactor: null,
    majorFactor: null,
    budgetFee: totalBudgetFee.value,
    remark: '',
    path: ['TOTAL']
  }
])



const saveToIndexedDB = async () => {
  if (shouldSkipPersist()) return
  try {
    const payload = {
      detailRows: JSON.parse(JSON.stringify(detailRows.value))
    }
    console.log('Saving to IndexedDB:', payload)
    await localforage.setItem(DB_KEY.value, payload)
  } catch (error) {
    console.error('saveToIndexedDB failed:', error)
  }
}

const loadFromIndexedDB = async () => {
  try {
    if (shouldForceDefaultLoad()) {
      const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
      detailRows.value = htData?.detailRows ? mergeWithDictRows(htData.detailRows) : buildDefaultRows()
      return
    }

    const data = await localforage.getItem<XmInfoState>(DB_KEY.value)
    if (data) {
      detailRows.value = mergeWithDictRows(data.detailRows)
      return
    }

    const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
    if (htData?.detailRows) {
      detailRows.value = mergeWithDictRows(htData.detailRows)
      return
    }

    detailRows.value = buildDefaultRows()
  } catch (error) {
    console.error('loadFromIndexedDB failed:', error)
    detailRows.value = buildDefaultRows()
  }
}

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 handleCellValueChanged = () => {
  if (gridPersistTimer) clearTimeout(gridPersistTimer)
  gridPersistTimer = setTimeout(() => {
    void saveToIndexedDB()
  }, 1000)
}

onMounted(async () => {
  await 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="text-xs text-muted-foreground">导入导出</div>
      </div>

      <div class="ag-theme-quartz h-full min-h-0 w-full flex-1">
        <AgGridVue :style="{ height: '100%' }" :rowData="detailRows" :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>


