<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { AgGridVue } from 'ag-grid-vue3'
import type { ColDef, GridApi, GridReadyEvent } from 'ag-grid-community'
import { taskList } from '@/sql'
import { myTheme, gridOptions, agGridWrapClass, agGridStyle } from '@/lib/diyAgGridOptions'
import { decimalAggSum, roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
import { formatThousands, formatThousandsFlexible } from '@/lib/numberFormat'
import { parseNumberOrNull } from '@/lib/number'
import { syncPricingTotalToZxFw } from '@/lib/zxFwPricingSync'
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
import { loadConsultCategoryFactorMap } from '@/lib/xmFactorDefaults'
import MethodUnavailableNotice from '@/components/shared/MethodUnavailableNotice.vue'

import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale';

interface DetailRow {
  id: string
  taskCode: string
  taskName: string
  unit: string
  conversion: number | null
  workload: number | null
  basicFee: number | null
  budgetBase: string
  budgetReferenceUnitPrice: string
  budgetAdoptedUnitPrice: number | null
  consultCategoryFactor: number | null
  serviceFee: number | null
  remark: string
  path: string[]
}

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

const props = defineProps<{
  contractId: string,

  serviceId: string | number
}>()
const zxFwPricingStore = useZxFwPricingStore()
const DB_KEY = computed(() => `gzlF-${props.contractId}-${props.serviceId}`)
const HT_CONSULT_FACTOR_KEY = computed(() => `ht-consult-category-factor-v1-${props.contractId}`)
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())
let factorDefaultsLoaded = false
const paneInstanceCreatedAt = Date.now()
const gridApi = ref<GridApi<DetailRow> | null>(null)

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

const ensureFactorDefaultsLoaded = async () => {
  if (factorDefaultsLoaded) return
  consultCategoryFactorMap.value = await loadConsultCategoryFactorMap(HT_CONSULT_FACTOR_KEY.value)
  factorDefaultsLoaded = true
}

const shouldSkipPersist = () => {
  const storageKey = `${PRICING_CLEAR_SKIP_PREFIX}${DB_KEY.value}`
  const raw = sessionStorage.getItem(storageKey)
  if (!raw) return false
  const now = Date.now()

  if (raw.includes(':')) {
    const [issuedRaw, untilRaw] = raw.split(':')
    const issuedAt = Number(issuedRaw)
    const skipUntil = Number(untilRaw)
    if (Number.isFinite(issuedAt) && Number.isFinite(skipUntil) && now <= skipUntil) {
      return paneInstanceCreatedAt <= issuedAt
    }
    sessionStorage.removeItem(storageKey)
    return false
  }

  const skipUntil = Number(raw)
  if (Number.isFinite(skipUntil) && now <= skipUntil) return true
  sessionStorage.removeItem(storageKey)
  return false
}

const shouldForceDefaultLoad = () => {
  const storageKey = `${PRICING_FORCE_DEFAULT_PREFIX}${DB_KEY.value}`
  const raw = sessionStorage.getItem(storageKey)
  if (!raw) return false
  const forceUntil = Number(raw)
  sessionStorage.removeItem(storageKey)
  return Number.isFinite(forceUntil) && Date.now() <= forceUntil
}

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

const detailRows = computed<DetailRow[]>({
  get: () => {
    const rows = getMethodState()?.detailRows
    return Array.isArray(rows) ? rows : []
  },
  set: rows => {
    zxFwPricingStore.setServicePricingMethodState(props.contractId, props.serviceId, 'workload', {
      detailRows: rows
    })
  }
})

type taskLite = {
  serviceID: number
  code?: string
  ref?: string
  name: string
  basicParam: string
  unit: string
  conversion: number | null
  maxPrice: number | null
  minPrice: number | null
  defPrice: number | null
  desc: string | null
}

const formatTaskReferenceUnitPrice = (task: taskLite) => {
  const unit = task.unit || ''
  const hasMin = typeof task.minPrice === 'number' && Number.isFinite(task.minPrice)
  const hasMax = typeof task.maxPrice === 'number' && Number.isFinite(task.maxPrice)
  if (hasMin && hasMax) return `${task.minPrice}${unit}-${task.maxPrice}${unit}`
  if (hasMin) return `${task.minPrice}${unit}`
  if (hasMax) return `${task.maxPrice}${unit}`
  return ''
}

const getSourceTaskIds = () => {
  const currentServiceId = Number(props.serviceId)
  return Object.entries(taskList as Record<string, taskLite>)
    .filter(([, task]) => Number(task.serviceID) === currentServiceId)
    .map(([key]) => Number(key))
    .filter(Number.isFinite)
    .sort((a, b) => a - b)
}

const isWorkloadMethodApplicable = computed(() => getSourceTaskIds().length > 0)

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

  for (const [order, taskId] of sourceTaskIds.entries()) {
    const task = (taskList as Record<string, taskLite | undefined>)[String(taskId)]
    const taskCode = task?.code || task?.ref || ''
    if (!taskCode || !task?.name) continue
    const rowId = `task-${taskId}-${order}`
    rows.push({
      id: rowId,
      taskCode,
      taskName: task.name,
      unit: task.unit || '',
      conversion: typeof task.conversion === 'number' && Number.isFinite(task.conversion) ? task.conversion : null,
      workload: null,
      basicFee: null,
      budgetBase: task.basicParam || '',
      budgetReferenceUnitPrice: formatTaskReferenceUnitPrice(task),
      budgetAdoptedUnitPrice:
        typeof task.defPrice === 'number' && Number.isFinite(task.defPrice) ? task.defPrice : null,
      consultCategoryFactor: getDefaultConsultCategoryFactor(),
      serviceFee: null,
      remark: task.desc|| '',
      path: [rowId]
    })
  }

  return rows
}

const isNoTaskRow = (row: DetailRow | undefined) => row?.id?.startsWith('task-none-') ?? false

const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] => {
  const dbValueMap = new Map<string, DetailRow>()
  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 hasRemark = Object.prototype.hasOwnProperty.call(fromDb, 'remark')

    return {
      ...row,
      workload: typeof fromDb.workload === 'number' ? fromDb.workload : null,
      basicFee: typeof fromDb.basicFee === 'number' ? fromDb.basicFee : null,
      budgetAdoptedUnitPrice:
        typeof fromDb.budgetAdoptedUnitPrice === 'number' ? fromDb.budgetAdoptedUnitPrice : null,
      consultCategoryFactor:
        typeof fromDb.consultCategoryFactor === 'number' ? fromDb.consultCategoryFactor : null,
      serviceFee: typeof fromDb.serviceFee === 'number' ? fromDb.serviceFee : null,
      remark: typeof fromDb.remark === 'string' ? fromDb.remark : hasRemark ? '' : row.remark
    }
  })
}

const parseSanitizedNumberOrNull = (value: unknown) =>
  parseNumberOrNull(value, { sanitize: true, precision: 3 })

const parseSanitizedAdoptedPriceOrNull = (value: unknown) =>
  parseNumberOrNull(value, { sanitize: true, precision: 6 })

const calcBasicFee = (row: DetailRow | undefined) => {
  if (!row || isNoTaskRow(row)) return null
  const price = row.budgetAdoptedUnitPrice
  const conversion = row.conversion
  const workload = row.workload
  if (
    typeof price !== 'number' ||
    !Number.isFinite(price) ||
    typeof conversion !== 'number' ||
    !Number.isFinite(conversion) ||
    typeof workload !== 'number' ||
    !Number.isFinite(workload)
  ) {
    return null
  }
  return roundTo(toDecimal(price).mul(conversion).mul(workload), 2)
}

const calcServiceFee = (row: DetailRow | undefined) => {
  if (!row || isNoTaskRow(row)) return null
  const factor = row.consultCategoryFactor
  const basicFee = calcBasicFee(row)
  if (
    basicFee == null ||
    typeof factor !== 'number' ||
    !Number.isFinite(factor)
  ) {
    return null
  }
  return roundTo(toDecimal(basicFee).mul(factor), 2)
}

const formatEditableNumber = (params: any) => {
  if (isNoTaskRow(params.data)) return '无'
  if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
    return '点击输入'
  }
  if (params.value == null) return ''
  return formatThousandsFlexible(params.value, 3)
}

const spanRowsByTaskName = (params: any) => {
  const rowA = params?.nodeA?.data as DetailRow | undefined
  const rowB = params?.nodeB?.data as DetailRow | undefined
  if (!rowA || !rowB) return false
  if (isNoTaskRow(rowA) || isNoTaskRow(rowB)) return false

  return Boolean(rowA.taskName) && Boolean(rowA.budgetBase) && rowA.taskName === rowB.taskName && rowA.budgetBase === rowB.budgetBase
}

const columnDefs: ColDef<DetailRow>[] = [
  {
    headerName: '编码',
    field: 'taskCode',
    minWidth: 100,
    width: 120,
    pinned: 'left',
    colSpan: params => (params.node?.rowPinned ? 2 : 1),
    valueFormatter: params => (params.node?.rowPinned ? '总合计' : params.value || '')
  },
  {
    headerName: '名称',
    field: 'taskName',
    minWidth: 150,
    width: 220,
    pinned: 'left',
    wrapText: true,
    autoHeight: true,
    cellStyle: { whiteSpace: 'normal', lineHeight: '1.4' },

    spanRows: true,
    valueFormatter: params => (params.node?.rowPinned ? '' : params.value || '')
  },
  {
    headerName: '预算基数',
    field: 'budgetBase',
    minWidth: 150,
        autoHeight: true,

    width: 180,
    colSpan: params => (params.node?.rowPinned ? 3 : 1),
    spanRows: spanRowsByTaskName,
    valueFormatter: params => (params.node?.rowPinned ? '' : params.value || '')
  },
  {
    headerName: '预算参考单价',
    field: 'budgetReferenceUnitPrice',
    minWidth: 170,
    flex: 1,
    valueFormatter: params => params.value || ''
  },
  {
    headerName: '预算采用单价',
    field: 'budgetAdoptedUnitPrice',
    headerClass: 'ag-right-aligned-header',
    minWidth: 170,
    flex: 1,
    editable: params => !params.node?.group && !params.node?.rowPinned && !isNoTaskRow(params.data),
    cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'ag-right-aligned-cell editable-cell-line' : 'ag-right-aligned-cell'),
    cellClassRules: {
      'editable-cell-empty': params =>
        !params.node?.group &&
        !params.node?.rowPinned &&
        !isNoTaskRow(params.data) &&
        (params.value == null || params.value === '')
    },
    valueParser: params => parseSanitizedAdoptedPriceOrNull(params.newValue),
    valueFormatter: params => {
      if (isNoTaskRow(params.data)) return '无'
      if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
        return '点击输入'
      }
      if (params.value == null) return ''
      const unit = params.data?.unit || ''
      return `${formatThousandsFlexible(params.value, 6)}${unit}`
    }
  },
  {
    headerName: '工作量',
    field: 'workload',
    minWidth: 140,
    flex: 1,
    editable: params => !params.node?.group && !params.node?.rowPinned && !isNoTaskRow(params.data),
    cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
    cellClassRules: {
      'editable-cell-empty': params =>
        !params.node?.group &&
        !params.node?.rowPinned &&
        !isNoTaskRow(params.data) &&
        (params.value == null || params.value === '')
    },
    aggFunc: decimalAggSum,
    valueParser: params => parseSanitizedNumberOrNull(params.newValue),
    valueFormatter: formatEditableNumber
  },
  {
    headerName: '咨询分类系数',
    field: 'consultCategoryFactor',
    width: 80,
    minWidth: 70,
    maxWidth: 90,
    editable: params => !params.node?.group && !params.node?.rowPinned && !isNoTaskRow(params.data),
    cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
    cellClassRules: {
      'editable-cell-empty': params =>
        !params.node?.group &&
        !params.node?.rowPinned &&
        !isNoTaskRow(params.data) &&
        (params.value == null || params.value === '')
    },
    valueParser: params => parseSanitizedNumberOrNull(params.newValue),
    valueFormatter: formatEditableNumber
  },
  {
    headerName: '服务费用(元)',
    field: 'serviceFee',
    headerClass: 'ag-right-aligned-header',
    minWidth: 150,
    flex: 1,
    cellClass: 'ag-right-aligned-cell',
    editable: false,
    valueGetter: params => (params.node?.rowPinned ? params.data?.serviceFee ?? null : calcServiceFee(params.data)),
    aggFunc: decimalAggSum,
    valueFormatter: params => {
      if (isNoTaskRow(params.data)) return '无'
      if (params.value == null || params.value === '') return ''
      return formatThousandsFlexible(roundTo(params.value, 3), 3)
    }
  },
  {
    headerName: '说明',
    field: 'remark',
    minWidth: 180,
    flex: 1.2,
    cellEditor: 'agLargeTextCellEditor',
    wrapText: true,
    autoHeight: true,
    cellStyle: { whiteSpace: 'normal', lineHeight: '1.4' },

    editable: false,
    valueFormatter: params => {
    
      return params.value || ''
    },
    cellClass: params => (!params.node?.group && !params.node?.rowPinned ? ' remark-wrap-cell' : ''),
    cellClassRules: {
      'editable-cell-empty': params =>
        !params.node?.group &&
        !params.node?.rowPinned &&
        !isNoTaskRow(params.data) &&
        (params.value == null || params.value === '')
    }
  }
]

const totalWorkload = computed(() => sumByNumber(detailRows.value, row => row.workload))
const totalBasicFee = computed(() => sumByNumber(detailRows.value, row => calcBasicFee(row)))
const sumNullableBy = <T>(rows: T[], pick: (row: T) => unknown): number | null => {
  let hasValid = false
  const total = sumByNumber(rows, row => {
    const value = Number(pick(row))
    if (!Number.isFinite(value)) return null
    hasValid = true
    return value
  })
  return hasValid ? total : null
}

const totalServiceFee = computed(() => sumNullableBy(detailRows.value.filter(e => e.basicFee !== null && e.basicFee !== undefined), row => calcServiceFee(row)))
const pinnedTopRowData = computed(() => [
  {
    id: 'pinned-total-row',
    taskCode: '总合计',
    taskName: '',
    unit: '',
    conversion: null,
    workload: totalWorkload.value,
    basicFee: totalBasicFee.value,
    budgetBase: '',
    budgetReferenceUnitPrice: '',
    budgetAdoptedUnitPrice: null,
    consultCategoryFactor: null,
    serviceFee: totalServiceFee.value,
    remark: '',
    path: ['TOTAL']
  }
])



const buildPersistDetailRows = () =>
  detailRows.value.map(row => ({
    ...row,
    basicFee: calcBasicFee(row),
    serviceFee: calcServiceFee(row)
  }))
const saveToIndexedDB = async () => {
  if (!isWorkloadMethodApplicable.value) return
  if (shouldSkipPersist()) return
  try {
    const payload = {
      detailRows: JSON.parse(JSON.stringify(buildPersistDetailRows()))
    }
    zxFwPricingStore.setServicePricingMethodState(
      props.contractId,
      props.serviceId,
      'workload',
      payload,
      { force: true }
    )
    const synced = await syncPricingTotalToZxFw({
      contractId: props.contractId,
      serviceId: props.serviceId,
      field: 'workload',
      value: totalServiceFee.value
    })
    if (!synced) return
  } catch (error) {
    console.error('saveToIndexedDB failed:', error)
  }
}

const loadFromIndexedDB = async () => {
  try {
    if (!isWorkloadMethodApplicable.value) {
      detailRows.value = []
      return
    }

    await ensureFactorDefaultsLoaded()

    if (shouldForceDefaultLoad()) {
      detailRows.value = buildDefaultRows()
      return
    }

    const data = await zxFwPricingStore.loadServicePricingMethodState<DetailRow>(props.contractId, props.serviceId, 'workload')
    if (data) {
      detailRows.value = mergeWithDictRows(data.detailRows)
      return
    }

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

const handleCellValueChanged = () => {
  void saveToIndexedDB()
}

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

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

onBeforeUnmount(() => {
  gridApi.value?.stopEditing()
  gridApi.value = null
  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;
};

const mydiyTheme = myTheme.withParams({
  rowBorder: {
    style: "solid",
    width: 0.8,
    color: "#d8d8dd"
  },
  columnBorder: {
    style: "solid",
    width: 0.8,
    color: "#d8d8dd"
  }
})
</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 v-if="isWorkloadMethodApplicable" :class="agGridWrapClass">
        <AgGridVue :style="agGridStyle" :rowData="detailRows" :pinnedTopRowData="pinnedTopRowData"
          :columnDefs="columnDefs" :gridOptions="gridOptions" :theme="mydiyTheme" :treeData="false"
          :animateRows="true"
          :enableCellSpan="true"
          @grid-ready="handleGridReady"
          @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>
      <MethodUnavailableNotice
        v-else
        title="该服务不适用工作量法"
        message="当前服务没有关联工作量法任务，无需填写此部分内容。"
      />
    </div>
  </div>
</template>
