import { tokenize } from "amis-formula"
import { flatMap } from "lodash"
import uniqWith from "lodash/uniqWith"
import { IColumn } from "../../store/table"
import { getCellValue, CROSS_SPLIT_CHAR, handleSort } from "../../store/utils/commonTableFunction"
import { cartesianProduct } from "../../utils/utils"
import { DATAKEYID } from "../../store/crud"

export const EMPTY_GROUP_VALUE = '空白'

export const INDEX_NAME = 'SF_INDEX'

export const REG_EXP = new RegExp(`\\.|:|${CROSS_SPLIT_CHAR}`, 'g')

const worker = new Worker('./public/worker/dataCross.js')

interface ICrossField {
  name: string
  titleExpr?: string
  order?: 'ASC' | 'DESC'
  orderName?: string
}

export interface ICross {
  columnFields: ICrossField[]
  rowFields: Pick<ICrossField, 'name'>[]
  valueFields: string
  /** valueField位置 0:表头 1:表体 */
  positionType: 0 | 1,
}

interface IColumnType {
  type: string
  align: string
  sortable: boolean
  map?: object
  fixed?: string
  isNumerical?: boolean
  quickEdit?: any
}

type IColumnField = ICrossField & IColumn

export interface ICrossColumn extends IColumnType {
  label: string
  field: string
  name: string
  groupName: string
  groupLabels: string[]
  isCountField: boolean
}

const uniqArr = (fields: IColumnField[], datas: any[]) => {
  const map = new Map<string, { order: 'asc' | 'desc' }>(fields.map(field => [(field.orderName ?? field.name), { order: (field.order?.toLowerCase() ?? 'asc') as 'asc' | 'desc' }]))
  const sortData = handleSort(datas.slice(), fields.map(field => field.orderName ?? field.name), map)
  return uniqWith(sortData.map(data => {
    const obj: any = {}
    for (const field of fields) {
      obj[field.name] = data[field.name]
    }
    return obj
  }), (a, b) => {
    for (const field of fields) {
      if (a[field.name] !== b[field.name]) {
        return false
      }
    }
    return true
  })
}

export const buildCrossColumn = (rowFields: IColumnField[], colFields: IColumnField[], valueFields: IColumnField[], datas: any[], countSum: boolean) => {
  const ret: ICrossColumn[] = []
  const level = colFields.length + 1
  const uniqColArr = uniqArr(colFields, datas)
  for (const rowField of rowFields) {
    const { type, label, name, align, sortable, map, fixed, pristine } = rowField
    const groupName = new Array(level - 1).fill(pristine.groupName ?? name).join(',')
    const groupLabels = new Array(level - 1).fill(pristine.groupName ?? name)
    ret.push({
      ...pristine,
      type,
      label,
      name,
      field: name,
      groupName,
      groupLabels,
      map,
      fixed,
      align,
      sortable,
      isCountField: false
    })
  }
  const product: [any, IColumnField, string[]][] = cartesianProduct(uniqColArr, valueFields).map(([item, field]: [any, IColumnField]) => {
    const cols: string[] = []
    for (let i = 0; i < colFields.length; i++) {
      const field = colFields[i]
      if (i == 0) {
        cols.push(String(item[field.name] ?? EMPTY_GROUP_VALUE).replace(REG_EXP, ''))
      } else {
        const vals = []
        for (let j = 0; j <= i; j++) {
          const preField = colFields[j]
          vals.push(String(item[preField.name] ?? EMPTY_GROUP_VALUE).replace(REG_EXP, ''))
        }
        cols.push(vals.join(CROSS_SPLIT_CHAR))
      }
    }
    return [item, field, cols.concat(`${cols[cols.length - 1]}${CROSS_SPLIT_CHAR}${field.name}`)]
  })
  for (const [uniqCol, valueField, item] of product) {
    const { type, label, name, align, sortable } = valueField
    const { quickEdit, ...rest } = valueField.pristine
    const has$Expr = colFields.some(field => field.titleExpr && !/\$\{[^\}]*\}/g.test(field.titleExpr))
    const groupName = has$Expr ? colFields.map(field => field.titleExpr).join(CROSS_SPLIT_CHAR) : level == 1 ? '' : item.slice(0, level - 1).map(val => val.replaceAll(',', '')).join(',')
    const groupLabels = colFields.map(field => {
      const val = getCellValue(uniqCol[field.name], field) || EMPTY_GROUP_VALUE
      return field.titleExpr ? field.titleExpr.replaceAll(`$\{${field.name}\}`, val) : val
    })
    ret.push({
      ...rest,
      type,
      label: /\$\{[^\}]*\}/g.test(label) ? (tokenize(label, uniqCol) || EMPTY_GROUP_VALUE) : label,
      name: item[level - 1],
      field: name,
      groupName,
      groupLabels,
      align,
      sortable,
      quickEdit: quickEdit ? { ...quickEdit, name: item[level - 1] } : undefined,
      isNumerical: true,
      isCountField: false
    })
  }
  if (countSum) {
    for (const valueField of valueFields) {
      const { type, label, name, align, sortable, pristine } = valueField
      const groupName = new Array(colFields.length).fill('SF_COUNT').join(',')
      const groupLabels = new Array(colFields.length).fill('合计')
      const rLabel = label.replace(REG_EXP, '')
      ret.push({
        ...pristine,
        type,
        label: rLabel,
        name: `${rLabel}${CROSS_SPLIT_CHAR}${groupName}`,
        field: name,
        groupName,
        groupLabels,
        align,
        sortable,
        isNumerical: false,
        isCountField: true
      })
    }
  }
  return ret
}

export const buildCrossData = (rowFields: IColumnField[], colFields: IColumnField[], crossColumns: ICrossColumn[], datas: any[]) => {
  return new Promise<any[]>((resolve, reject) => {
    worker.postMessage({ rowFields, colFields, crossColumns, datas, regExp: REG_EXP, EMPTY_GROUP_VALUE, CROSS_SPLIT_CHAR })
    worker.onmessage = (e: MessageEvent<any[]>) => {
      const map = new Map<string, { order: 'asc' | 'desc' }>(rowFields.map(field => [(field.orderName ?? field.name), { order: (field.order?.toLowerCase() ?? 'asc') as 'asc' | 'desc' }]))
      const sortData = handleSort(e.data.slice(), rowFields.map(field => field.orderName ?? field.name), map)
      resolve(sortData)
    }
    worker.onmessageerror = (e) => { reject(e.data) }
  })
}

export const buildCrossColumn1 = (rowFields: IColumnField[], colFields: IColumnField[], datas: any[]) => {
  const ret: ICrossColumn[] = []
  const level = colFields.length
  // step 1
  for (const rowField of rowFields) {
    const { type, label, name, align, sortable, map, fixed, pristine } = rowField
    ret.push({
      ...pristine,
      type,
      label,
      name,
      field: name,
      groupName: new Array(level - 1).fill(name).join(','),
      groupLabels: [],
      map,
      fixed,
      align,
      sortable,
      isCountField: false
    })
  }
  // step 2
  ret.push({
    label: '指标',
    name: INDEX_NAME,
    field: INDEX_NAME,
    groupName: new Array(level - 1).fill(INDEX_NAME).join(','),
    groupLabels: [],
    isCountField: false,
    type: 'plain',
    align: 'left',
    sortable: false,
  })
  // step 3
  const uniqColArr = uniqArr(colFields, datas)
  for (const item of uniqColArr) {
    const cols: string[] = []
    for (let i = 0; i < colFields.length; i++) {
      const field = colFields[i]
      if (i == 0) {
        cols.push(String(item[field.name] ?? EMPTY_GROUP_VALUE).replace(REG_EXP, ''))
      } else {
        const vals = []
        for (let j = 0; j <= i; j++) {
          const preField = colFields[j]
          vals.push(String(item[preField.name] ?? EMPTY_GROUP_VALUE).replace(REG_EXP, ''))
        }
        cols.push(vals.join(CROSS_SPLIT_CHAR))
      }
    }
    const groupName = level == 1 ? '' : cols.slice(0, level - 1).join(',')
    const labels = colFields.map(field => {
      const val = getCellValue(item[field.name], field) || EMPTY_GROUP_VALUE
      return field.titleExpr ? field.titleExpr.replaceAll(`$\{${field.name}\}`, val) : val
    })
    ret.push({
      label: labels[level - 1],
      name: cols[level - 1],
      field: '',
      groupName,
      groupLabels: labels.slice(0, level - 1),
      isCountField: false,
      type: 'plain',
      align: 'right',
      sortable: false
    })
  }
  return ret
}

export const buildCrossData1 = (rowFields: IColumnField[], colFields: IColumnField[], valueFields: IColumnField[], crossColumns: ICrossColumn[], datas: any[]) => {
  const uniqRowData = uniqArr(rowFields, datas)
  return new Promise<any[]>((resolve, reject) => {
    worker.postMessage({ rowFields, colFields, valueFields, crossColumns, datas, uniqRowData, regExp: REG_EXP, EMPTY_GROUP_VALUE, CROSS_SPLIT_CHAR, INDEX_NAME })
    worker.onmessage = (e: MessageEvent<any[]>) => { resolve(e.data) }
    worker.onmessageerror = (e) => { reject(e.data) }
  })
}

export const getChangeRows = (changeDatas: any[], rawDatas: any[], cross: ICross): any[] => {
  const { rowFields, columnFields, positionType } = cross
  if (positionType === 1) return []
  return flatMap(changeDatas, changeData => {
    const colFields = Object.keys(changeData).filter(key => key.includes(CROSS_SPLIT_CHAR))
    const rowTarget = rawDatas.filter(data => rowFields.every(field => data[field.name] == changeData[field.name]))
    const result = []
    for (const field of colFields) {
      const values = field.split(CROSS_SPLIT_CHAR)
      const valueField = values[values.length - 1]
      const colTarget = rowTarget.find(data => {
        return columnFields.every((field, index) => {
          const dValue = data[field.name]
          return (dValue == null ? dValue : String(dValue).replace(REG_EXP, '')) == (values[index] == EMPTY_GROUP_VALUE ? null : values[index])
        })
      })
      if (colTarget) {
        const temp = { ...colTarget }
        const index = result.findIndex(item => item[DATAKEYID] === temp[DATAKEYID])
        if (index == -1) {
          temp[valueField] = changeData[field]
          result.push(temp)
        } else {
          result[index][valueField] = changeData[field]
        }
      }
    }
    return result
  })
}

// export const buildCrossData = (rowFields: IColumnField[], colFields: IColumnField[], crossColumns: ICrossColumn[], datas: any[], countSum: boolean) => {
//   return new Promise<any[]>((resolve, reject) => {
//     const ret = datas.reduce((result, current) => {
//       const index = result.findIndex((data: any) => rowFields.every(field => data[field.name] == current[field.name]))
//       const compareFn = (field: IColumnField, index: number, name: string) => {
//         const value = name.split(CROSS_SPLIT_CHAR)[index]
//         const dValue = current[field.name]
//         return (dValue == null ? dValue : String(dValue).replace(REG_EXP, '')) == (value == EMPTY_GROUP_VALUE ? null : value)
//       }
//       if (index == -1) {
//         const target = { ...current }
//         for (let i = 0; i < crossColumns.length; i++) {
//           const { name, field, isPercentage, calculatedField, isCountField } = crossColumns[i]
//           if (i < rowFields.length) {
//             target[name] = current[field]
//           } else {
//             const isRate = isPercentage === true && (calculatedField?.length ?? 0) > 0
//             const condition = colFields.every((field, index) => compareFn(field, index, name))
//             if (isRate) {
//               target[`Z_${name}`] = condition || isCountField ? +current[field] / 100 * +current[calculatedField] : null
//               target[`M_${name}`] = condition || isCountField ? +current[calculatedField] : null
//             }
//             target[name] = condition || isCountField ? current[field] : null
//           }
//         }
//         result.push(target)
//       } else {
//         const target = { ...result[index] }
//         for (let i = rowFields.length; i < crossColumns.length; i++) {
//           const { name, field, isPercentage, calculatedField, isCountField } = crossColumns[i]
//           const isRate = isPercentage === true && (calculatedField?.length ?? 0) > 0
//           if (isCountField) {
//             if (isRate) {
//               target[`Z_${name}`] += +current[field] / 100 * +current[calculatedField]
//               target[`M_${name}`] += +current[calculatedField]
//               target[name] = (target[`Z_${name}`] == 0 || target[`M_${name}`] == 0) ? null : target[`Z_${name}`] / target[`M_${name}`] * 100
//             } else {
//               target[name] += +current[field]
//             }
//           } else {
//             const condition = colFields.every((field, index) => compareFn(field, index, name))
//             if (condition) {
//               if (isRate) {
//                 if (current[calculatedField] != null && !isNaN(+current[calculatedField])) {
//                   target[`Z_${name}`] += +current[field] / 100 * +current[calculatedField]
//                   target[`M_${name}`] += +current[calculatedField]
//                   target[name] = (target[`Z_${name}`] == 0 || target[`M_${name}`] == 0) ? null : target[`Z_${name}`] / target[`M_${name}`] * 100
//                 } else {
//                   target[name] = current[field]
//                 }
//               } else {
//                 if (current[field] != null) {
//                   if (isNaN(+current[field])) {
//                     target[name] = current[field]
//                   } else {
//                     target[name] += +current[field]
//                   }
//                 }
//               }
//             }
//           }
//         }
//         result[index] = target
//       }
//       return result
//     }, [])
//     resolve(ret)
//   })
// }

// export const buildCrossData1 = (rowFields: IColumnField[], colFields: IColumnField[], valueFields: IColumnField[], crossColumns: ICrossColumn[], datas: any[]) => {
//   const uniqRowData = uniqArr(rowFields, datas)
//   const tempColumns = crossColumns.slice(rowFields.length + 1)
//   return flatMap(uniqRowData ?? [], rowData => {
//     const ret: any[] = []
//     const rowDatas = datas.filter(data => rowFields.every(rowField => data[rowField.name] == rowData[rowField.name])) 
//     for (let i = 0; i < valueFields.length; i++) {
//       const { name, label, pristine } = valueFields[i]
//       const { isPercentage, calculatedField } = pristine
//       const isRate = isPercentage === true && (calculatedField?.length ?? 0) > 0
//       const obj = { ...rowData }
//       obj[INDEX_NAME] = label
//       for (const tempCol of tempColumns) {
//         const items: any[] = rowDatas.filter(data => {
//           return colFields.every((colField, index) => {
//             const value = tempCol.name.split(CROSS_SPLIT_CHAR)[index]
//             const dValue = data[colField.name]
//             return (dValue == null ? dValue : String(dValue).replace(REG_EXP, '')) == (value == EMPTY_GROUP_VALUE ? null : value)
//           })
//         })
//         if (items.length > 0) {
//           let value = null, zSum = 0, mSum = 0
//           for (const item of items) {
//             if (item[name] != null) {
//               if (isRate) {
//                 if (item[calculatedField] != null && !isNaN(+item[calculatedField])) {
//                   zSum += (+item[name] / 100 * + item[calculatedField])
//                   mSum += +(item[calculatedField])
//                   value = (zSum == 0 || mSum == 0) ? null : (zSum / mSum * 100)
//                 } else {
//                   value = item[name]
//                 }
//               } else {
//                 if (isNaN(+item[name])) {
//                   value = item[name]
//                 } else {
//                   value += +item[name]
//                 }
//               }
//             }
//           }
//           obj[tempCol.name] = value ? getCellValue(value, valueFields[i]) : null
//         } else {
//           obj[tempCol.name] = null
//         }
//       }
//       ret.push(obj)
//     }
//     return ret
//   })
// }