<template>
  <view class="spread-designer-generic">
    <NavBar>
      <template #title>
        <text class="nav-title">{{ navTitle }}</text>
      </template>
      <template #right>
        <button
          v-for="(button, index) in navButtons"
          :key="index"
          :class="['nav-btn', button.className]"
          @click="handleNavButtonClick(button)"
        >
          {{ button.text }}
        </button>
      </template>
    </NavBar>

    <view class="main-container">
      <Designer
        ref="designerRef"
        :config="designerConfig"
        :style="styleInfo"
        @designerInitialized="handleWorkbookInitialized"
      />
    </view>

    <view
      v-if="floatingInputVisible"
      class="floating-input-container"
      :class="{ 'keyboard-visible': keyboardHeight > 0 }"
      :style="{ '--keyboard-height': `${keyboardHeight - 32}px` }"
    >
      <view class="floating-toolbar">
        <view class="floating-field-name-display">{{ currentFieldName }}</view>
        <view class="button-group">
          <button
            id="prevCellBtn"
            class="prev-cell-btn"
            :disabled="!currentCell"
            @click="findNextEmptyBoundCell('prev')"
          >
            <text class="btn-text">上一项</text>
          </button>
          <button
            id="nextCellBtn"
            class="next-cell-btn"
            :disabled="!currentCell"
            @click="findNextEmptyBoundCell('next')"
          >
            <text class="btn-text">下一项</text>
          </button>
        </view>
      </view>
      <view class="floating-input-wrapper">
        <textarea
          id="floatingInput"
          v-model="floatingInputValue"
          placeholder="请选择单元格"
          @input="backfillValueToCell"
        />
        <button id="floatingClearBtn" class="clear-btn" @click="clearInput">×</button>
      </view>
    </view>

    <view v-if="dialogVisible" class="dialog-overlay" @click="dialogVisible = false">
      <view class="dialog-content" @click.stop>
        <view class="modal-message">{{ dialogMessage }}</view>
      </view>
    </view>
  </view>
</template>

<script setup>
import { ref, computed, watch, nextTick } from 'vue'
import GC from './tools/gc'
import dayjs from 'dayjs'
import '@grapecity-software/spread-sheets/styles/gc.spread.sheets.excel2013white.css'
import '@grapecity-software/spread-sheets-designer/styles/gc.spread.sheets.designer.min.css'
import '@grapecity-software/spread-sheets-print'
import '@grapecity-software/spread-sheets-shapes'
import '@grapecity-software/spread-sheets-charts'
import '@grapecity-software/spread-sheets-slicers'
import '@grapecity-software/spread-sheets-pivot-addon'
import '@grapecity-software/spread-sheets-tablesheet'
import '@grapecity-software/spread-sheets-ganttsheet'
import '@grapecity-software/spread-sheets-reportsheet-addon'
import '@grapecity-software/spread-sheets-formula-panel'
import '@grapecity-software/spread-sheets-io'
import '@grapecity-software/spread-sheets-designer-resources-cn'
import Designer from '@grapecity-software/spread-sheets-designer-vue'
import NavBar from '@/components/NavBar/NavBar.vue'

const props = defineProps({
  businessConfig: {
    type: Object,
    default: null,
  },
  templateData: {
    type: Object,
    default: null,
  },
  templateBlob: {
    type: String,
    default: '',
  },
})

const emit = defineEmits(['save', 'cancel', 'customAction', 'close', 'openCamera', 'calcCompleted'])

const designerKey = import.meta.env.VITE_SPREADJS_DESIGNER_KEY
const licenseKey = import.meta.env.VITE_SPREADJS_LICENSE_KEY
GC.Spread.Sheets.Designer.LicenseKey = designerKey
GC.Spread.Sheets.LicenseKey = licenseKey

const designer = ref(null)
const navTitle = ref('通用编辑器')
const navButtons = ref([])
const floatingInputVisible = ref(false)
const floatingInputValue = ref('')
const currentCell = ref(null)
const currentCellInfo = ref(null)
const keyboardHeight = ref(0)
const selectedPicture = ref(null)
const currentFieldName = ref('-')
const fieldNameMapping = ref([])
const dialogVisible = ref(false)
const dialogMessage = ref('')

const designerConfig = computed(() => {
  const config = GC.Spread.Sheets.Designer.DefaultConfig
  config.ribbon = []
  const commandsToDelete = ['formulaBarPanel']
  config.sidePanels = config.sidePanels.filter((item) => !commandsToDelete.includes(item.command))
  return config
})

const styleInfo = computed(() => {
  return {
    width: '100%',
    height: '100%',
  }
})

function is(val, type) {
  return Object.prototype.toString.call(val) === `[object ${type}]`
}

function changeStringToObject(json) {
  if (is(json, 'String') && json?.length) {
    return changeStringToObject(JSON.parse(json))
  } else if (is(json, 'Object')) {
    return json
  } else {
    console.error('数据类型不正确!')
    return null
  }
}

function base64ToBlob(base64Data, contentType = '') {
  const byteCharacters = atob(base64Data)
  const byteNumbers = new Array(byteCharacters.length)
  for (let i = 0; i < byteCharacters.length; i++) {
    byteNumbers[i] = byteCharacters.charCodeAt(i)
  }
  const byteArray = new Uint8Array(byteNumbers)
  return new Blob([byteArray], { type: contentType })
}

function blobToBase64(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.onload = () => {
      const base64 = reader.result.split(',')[1]
      resolve(base64)
    }
    reader.onerror = reject
    reader.readAsDataURL(blob)
  })
}

function formatterOADateNumber(input) {
  const regex = /OADate\(([\d.]+)\)/
  const match = input.match(regex)
  let OADate = null
  if (match && match[1]) {
    OADate = Number(match[1])
    OADate = isNaN(OADate) ? null : parseInt(OADate)
  }
  return OADate !== null ? new Date(Math.round(OADate - 25569) * 86400000) : null
}

function getSheetBindingPathData(sheet, dataSource) {
  const boundFields = new Set()
  const tableMap = new Map()

  // 扫描模板文件中的数据填充槽标识（键）
  for (let row = 0; row < sheet.getRowCount(); row++) {
    for (let col = 0; col < sheet.getColumnCount(); col++) {
      const bindingPath = sheet.getBindingPath(row, col)
      if (bindingPath) {
        boundFields.add(bindingPath)
      }
    }
  }

  // 扫描模板文件中所有子表及其对应的数据填充槽标识
  const tables = sheet.tables.all() || []
  if (tables.length > 0) {
    tables.forEach(function (table) {
      const tableName = table.bindingPath()
      boundFields.add(tableName)
      const tableBoundFields = new Set()
      const columnCount = table.range().colCount
      for (let i = 0; i < columnCount; i++) {
        const field = table.getColumnDataField(i)
        if (field) {
          tableBoundFields.add(field)
        }
      }
      tableMap.set(tableName, [...tableBoundFields])
    })
  }

  const boundFieldsArray = [...boundFields].filter(Boolean)
  if (boundFieldsArray.length === 0) return {}

  // 根据模板文件数据值槽的需要，过滤数据值容器的数据（而且是按整个数据结构层次的过滤）
  function recombineDataSource(dataSourceObj, prekey, boundFieldsArray) {
    const filterResult = {}
    for (const key in dataSourceObj) {
      // 如果key对应属性值是对象，那么按需向下层处理
      if (is(dataSourceObj[key], 'Object')) {
        const nextKey = !prekey ? key : `${prekey}.${key}`
        if (boundFieldsArray.findIndex((field) => field.indexOf(nextKey) >= 0) >= 0) {
          filterResult[key] = recombineDataSource(dataSourceObj[key], nextKey, boundFieldsArray)
        }
        continue
      }

      // 如果key对应属性值是对象数组，那么对sheet中的子表数据填充槽进行处理
      // 也就是说，数组处理上，基本数据值类型一般就是一个单元格的填充，但是对象类型说明是一张子表填充
      if (is(dataSourceObj[key], 'Array') && dataSourceObj[key].every((x) => is(x, 'Object'))) {
        if (boundFieldsArray.includes(key)) {
          filterResult[key] = Object.entries(dataSourceObj[key]).map(([i, item]) => {
            if (is(item, 'String')) return item
            const tableBoundFieldArrays = tableMap.get(key)
            return recombineDataSource(item, '', tableBoundFieldArrays)
          })
        }
        continue
      }

      if (boundFieldsArray.includes(!prekey ? key : `${prekey}.${key}`)) {
        filterResult[key] = dataSourceObj[key]
      }
    }
    return filterResult
  }

  const newDataSource = recombineDataSource(dataSource, '', boundFieldsArray)
  boundFields.clear()
  tableMap.clear()
  return newDataSource
}

// 生成属性键，并填充对应默认值
function generateDefaultData(schema) {
  const result = {}
  for (const key in schema.properties) {
    result[key] = generatePropertyDefaultValue(schema.properties[key])
  }
  return result
}

// 为属性填充默认值
function generatePropertyDefaultValue(property) {
  if (property.type === 'array' && property.items) {
    if (property.items.type !== 'object' || !property.items.properties) {
      return ['']
    }
    const itemValue = {}
    for (const itemKey in property.items.properties) {
      itemValue[itemKey] = generatePropertyDefaultValue(property.items.properties[itemKey])
    }
    return [itemValue]
  }

  if (property.properties) {
    const objValue = {}
    for (const objKey in property.properties) {
      objValue[objKey] = generatePropertyDefaultValue(property.properties[objKey])
    }
    return objValue
  }

  return ''
}

/**
 * 为属性填充非默认数据值
 * @param target 属性值为默认值的键值数据容器
 * @param source 填充数据到键值容器的数据源头
 */
function deepMergeSchemaValue(target, source) {
  const result = { ...target }

  for (const key in source) {
    if (source.hasOwnProperty(key)) {
      const targetValue = target[key]
      const sourceValue = source[key]

      if (targetValue === undefined) {
        continue
      }

      if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
        result[key] = sourceValue.length > 0 ? sourceValue : targetValue
      } else if (
        typeof targetValue === 'object' &&
        targetValue !== null &&
        typeof sourceValue === 'object' &&
        sourceValue !== null
      ) {
        result[key] =
          Object.keys(sourceValue).length === 0
            ? targetValue
            : deepMergeSchemaValue(targetValue, sourceValue)
      } else if (sourceValue !== undefined && sourceValue !== '') {
        result[key] = sourceValue
      }
    }
  }

  return result
}

function generateAndReturnDataSourceOADate(dataSource) {
  if (!is(dataSource, 'Object') && !is(dataSource, 'Array')) return {}

  if (is(dataSource, 'Object')) {
    for (const key in dataSource) {
      if (is(dataSource[key], 'String') && dataSource[key].indexOf('OADate') >= 0) {
        dataSource[key] = dayjs(formatterOADateNumber(dataSource[key])).format('YYYY年MM月DD日')
        continue
      } else if (is(dataSource[key], 'Date')) {
        dataSource[key] = dayjs(dataSource[key]).format('YYYY年MM月DD日')
        continue
      }
      if (is(dataSource[key], 'Object') || is(dataSource[key], 'Array')) {
        dataSource[key] = generateAndReturnDataSourceOADate(dataSource[key])
        continue
      }
    }
  }

  if (is(dataSource, 'Array')) {
    return dataSource.map((keyItem) => {
      if (is(keyItem, 'String') && keyItem.indexOf('OADate') >= 0) {
        return dayjs(formatterOADateNumber(keyItem)).format('YYYY年MM月DD日')
      }
      if (is(keyItem, 'Object') || is(keyItem, 'Array')) {
        return generateAndReturnDataSourceOADate(keyItem)
      }
      return keyItem
    })
  }
  return dataSource
}

// 监听值变化（用户通过UI操作变化值会被监听，代码去变化值不会被监听），进行dataSource的同步（发生事件以外的sheet）
// 因为每个sheet用了同一个dataSource（这里不是同一个实例，但是是数据结构和值相同），为了保持这个dataSource的一致，所以要更新其他sheet的datasource
function registerCellValuesChangeEventHandlerForEverySheet(sheets) {
  sheets.forEach((activedSheet) => {
    activedSheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (sender, args) {
      const bindingPathName = activedSheet.getBindingPath(args.row, args.col)
      if (bindingPathName) {
        try {
          // 处理其他sheet的dataSource
          for (const sheet of sheets) {
            const dataSource = sheet.getDataSource()?.getSource() || {}
            if (
              !dataSource.hasOwnProperty(bindingPathName) ||
              (activedSheet.name() === sheet.name() && activedSheet.__ID__ === sheet.__ID__)
            )
              continue

            const tables = sheet.tables?.all() || []
            for (let i = 0; i < tables.length; i++) {
              const table = sheet.tables.all()[i]
              table.expandBoundRows(true)
            }
            const newDataSource = new GC.Spread.Sheets.Bindings.CellBindingSource({
              ...dataSource,
              [bindingPathName]: args.newValue,
            })
            sheet.setDataSource(newDataSource)
          }
        } catch (err) {
          console.error('监听单元格出错啦', err)
        }
      }
    })
  })
}

function registerZoomEventHandler(designerInstance) {
  if (!designerInstance) return
  const spreadInstance = designerInstance?.getWorkbook()
  if (Object.prototype.toString.call(spreadInstance) !== '[object Object]') return
  // 缩放事件
  spreadInstance.bind(GC.Spread.Sheets.Events.ViewZooming, function (sender, args) {
    const activedSheet = spreadInstance.getActiveSheet()
    const minZoom = calcSheetZoom(activedSheet)
    // 如果缩小，限制其最小是sheet铺满屏幕，否则不再缩小
    if (args.newZoomFactor < minZoom) args.cancel = true
  })
}

// 计算sheet铺满屏幕的缩放比例
function calcSheetZoom(sheet) {
  const screenWidth = window.screen.width
  const usedRange = sheet.getUsedRange(GC.Spread.Sheets.UsedRangeType.style)
  sheet.showCell(usedRange.row, usedRange.col)

  const scrollbarWidth = 40
  let totalWidth = 0
  for (let col = usedRange.col; col <= usedRange.colCount - 1; col++) {
    totalWidth += sheet.getColumnWidth(col)
  }
  return 1 + (screenWidth - scrollbarWidth - totalWidth) / totalWidth
}

function initDesignerSheetConfig(sheet) {
  sheet?.zoom(1)
  sheet?.zoom(calcSheetZoom(sheet))
  sheet.options.rowHeaderVisible = false
  sheet.options.colHeaderVisible = false
  sheet.setActiveCell(null)
}

async function setDefaultSchema(designerInstance, bindingPathSchema) {
  const initBindingPathSchema = !bindingPathSchema ? {} : changeStringToObject(bindingPathSchema)
  await designerInstance.setData('treeNodeFromJson', JSON.stringify(initBindingPathSchema))
}

function getDefaultSchema(designerInstance) {
  return (
    designerInstance.getData('updatedTreeNode') ||
    designerInstance.getData('treeNodeFromJson') ||
    designerInstance.getData('oldTreeNodeFromJson')
  )
}

// 设置数据到模板文件，渲染对应数据
function setDataSource(designerInstance, schemaData, dataSourceValues) {
  dataSourceValues =
    !is(dataSourceValues, 'Object') && is(dataSourceValues, 'String')
      ? JSON.parse(dataSourceValues)
      : dataSourceValues || {}
  schemaData =
    !is(schemaData, 'Object') && is(schemaData, 'String')
      ? JSON.parse(schemaData)
      : schemaData || {}

  const formatterSource = generateDefaultData(schemaData)
  const resultDataSource = deepMergeSchemaValue(formatterSource, dataSourceValues)

  const spreadInstance = designerInstance.getWorkbook()
  registerCellValuesChangeEventHandlerForEverySheet(spreadInstance.sheets)
  for (const sheet of spreadInstance.sheets) {
    const tables = sheet.tables?.all() || []
    for (let i = 0; i < tables.length; i++) {
      const table = tables[i]
      table.expandBoundRows(true)
    }
    const filterResultDataSource = getSheetBindingPathData(sheet, resultDataSource)
    const dataSource = new GC.Spread.Sheets.Bindings.CellBindingSource(filterResultDataSource)
    sheet.setDataSource(dataSource)
  }
}

function getDataSource(designerInstance) {
  const spreadInstance = designerInstance.getWorkbook()

  let dataSource = {}
  for (const sheet of spreadInstance.sheets) {
    const source = sheet.getDataSource()?.getSource()
    if (source) {
      for (const key in source) {
        if (is(source[key], 'Object') && is(dataSource[key], 'Object')) {
          source[key] = Object.assign(dataSource[key], source[key])
        }
      }
      dataSource = {
        ...dataSource,
        ...source,
      }
    }
  }
  dataSource = generateAndReturnDataSourceOADate(dataSource)
  return dataSource
}

function handleSheetTableCopyTo(designerInstance, isRowMerage) {
  if (!designerInstance) return
  const spreadInstance = designerInstance.getWorkbook()
  for (const sheet of spreadInstance.sheets) {
    const tables = sheet.tables?.all() || []

    for (let tableIndex = 0; tableIndex < tables.length; tableIndex++) {
      const range = tables[tableIndex].range()
      const { row, rowCount, col, colCount } = range

      sheet.getRange(row, col, rowCount, colCount).wordWrap(true)

      if (isRowMerage) {
        const firstRowSpans = []
        for (let c = col; c < col + colCount; c++) {
          const span = sheet.getSpan(row, c)
          if (span && span.row === row) {
            firstRowSpans.push({
              startCol: span.col,
              colCount: span.colCount,
            })
            c += span.colCount - 1
          }
        }

        if (firstRowSpans.length === 0) {
          const firstRowValues = []
          for (let c = col; c < col + colCount; c++) {
            firstRowValues.push(sheet.getValue(row, c))
          }

          let startMergeCol = 0
          for (let c = 1; c <= colCount; c++) {
            if (c === colCount || firstRowValues[c] !== firstRowValues[c - 1]) {
              if (c - startMergeCol > 1) {
                firstRowSpans.push({
                  startCol: col + startMergeCol,
                  colCount: c - startMergeCol,
                })
                sheet.addSpan(row, col + startMergeCol, 1, c - startMergeCol)
              }
              startMergeCol = c
            }
          }
        }

        for (let r = 1; r < rowCount; r++) {
          const currentRow = row + r
          firstRowSpans.forEach((mergeInfo) => {
            try {
              const firstCellValue = sheet.getValue(currentRow, mergeInfo.startCol)
              let shouldMerge = true

              for (let c = 1; c < mergeInfo.colCount; c++) {
                const currentCellValue = sheet.getValue(currentRow, mergeInfo.startCol + c)
                if (currentCellValue !== firstCellValue) {
                  shouldMerge = false
                  break
                }
              }

              if (shouldMerge) {
                sheet.addSpan(currentRow, mergeInfo.startCol, 1, mergeInfo.colCount)
              }
            } catch (error) {
              console.warn(
                `合并单元格失败 at (${currentRow}, ${mergeInfo.startCol}):`,
                error.message,
              )
            }
          })
        }
      }

      for (let rowIndex = 1; rowIndex < rowCount; rowIndex++) {
        const currentRow = row + rowIndex
        sheet?.copyTo(
          row,
          col,
          currentRow,
          col,
          1,
          colCount,
          GC.Spread.Sheets.CopyToOptions.style | GC.Spread.Sheets.CopyToOptions.formula,
        )
      }

      setTimeout(() => {
        for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
          const currentRow = row + rowIndex
          sheet?.autoFitRow(currentRow)
          const currentHeight = sheet.getRowHeight(currentRow)
          const newHeight = Math.max(currentHeight * 1.2, 25)
          sheet.setRowHeight(currentRow, newHeight)
        }
      }, 100)
    }

    const currentTheme = sheet.currentTheme()
    currentTheme.bodyFont('仿宋_GB2312')
  }
}

function removeSheetCellFocus(designerInstance) {
  const spreadInstance = designerInstance.getWorkbook()
  if (spreadInstance) {
    spreadInstance.focus(false)
    for (const sheet of spreadInstance?.sheets) {
      sheet.clearSelection()
    }
  }
}

function updateFieldNameDisplay(fieldName) {
  let displayName = '-'
  if (fieldNameMapping.value && Array.isArray(fieldNameMapping.value)) {
    const fieldMapping = fieldNameMapping.value.find((item) => item.field === fieldName)
    if (fieldMapping && fieldMapping.displayName) {
      displayName = fieldMapping.displayName
    }
  }
  currentFieldName.value = displayName
}

function showFloatingInput() {
  floatingInputValue.value = currentCellInfo.value.value
  floatingInputVisible.value = true
}

function backfillValueToCell() {
  if (!currentCell.value || !currentCellInfo.value) return
  const { sheet, row, col } = currentCell.value
  sheet.setValue(row, col, floatingInputValue.value)
}

function clearInput() {
  floatingInputValue.value = ''
}

function findNextEmptyBoundCell(direction = 'next') {
  if (!currentCell.value || !currentCell.value.sheet) {
    console.warn('没有当前活动单元格')
    return
  }

  try {
    const sheet = currentCell.value.sheet
    const usedRange = sheet.getUsedRange(GC.Spread.Sheets.UsedRangeType.data)

    if (!usedRange) {
      console.warn('没有找到使用范围')
      return
    }

    const { rowCount, colCount } = usedRange
    const startRow = currentCell.value.row
    const startCol = currentCell.value.col

    const targetCell = findNextEmptyCellFromPosition(
      sheet,
      startRow,
      startCol,
      rowCount,
      colCount,
      direction,
    )

    if (targetCell) {
      currentCell.value = { sheet, row: targetCell.row, col: targetCell.col }
      const cell = sheet.getCell(targetCell.row, targetCell.col)
      const bindingPath = sheet.getBindingPath(targetCell.row, targetCell.col) || '-'

      currentCellInfo.value = {
        value: cell.value() || '',
        isLocked: false,
        formatter: cell.formatter(),
        fieldName: bindingPath,
      }

      updateFieldNameDisplay(bindingPath)
      showFloatingInput()
      sheet.setSelection(targetCell.row, targetCell.col, 1, 1)

      const directionText = direction === 'next' ? '下一个' : '上一个'
      console.log(
        `跳转到${directionText}未输入单元格: 行${targetCell.row + 1}, 列${targetCell.col + 1}`,
      )
    } else {
      console.log(`没有找到更多${direction === 'next' ? '下一个' : '上一个'}未输入的绑定字段单元格`)
    }
  } catch (error) {
    console.error('查找过程中出错:', error)
  }
}

function findNextEmptyCellFromPosition(
  sheet,
  startRow,
  startCol,
  maxRow,
  maxCol,
  direction = 'next',
) {
  const currentCellRange = getMergedCellRange(sheet, startRow, startCol)
  const targetCell = searchInRange(
    sheet,
    currentCellRange,
    startRow,
    startCol,
    maxRow,
    maxCol,
    direction,
    true,
  )
  if (targetCell) return targetCell
  return searchInRange(
    sheet,
    currentCellRange,
    startRow,
    startCol,
    maxRow,
    maxCol,
    direction,
    false,
  )
}

function searchInRange(
  sheet,
  currentCellRange,
  startRow,
  startCol,
  maxRow,
  maxCol,
  direction,
  fromCurrentPosition,
) {
  const rowStart = fromCurrentPosition ? startRow : direction === 'next' ? 0 : maxRow - 1
  const rowEnd = fromCurrentPosition
    ? direction === 'next'
      ? maxRow
      : -1
    : direction === 'next'
      ? startRow
      : startRow
  const rowStep = direction === 'next' ? 1 : -1

  for (let row = rowStart; direction === 'next' ? row < rowEnd : row >= rowEnd; row += rowStep) {
    let colStart, colEnd, colStep

    if (fromCurrentPosition) {
      colStart =
        row === startRow
          ? direction === 'next'
            ? startCol + 1
            : startCol - 1
          : direction === 'next'
            ? 0
            : maxCol - 1
      colEnd = direction === 'next' ? maxCol : -1
      colStep = direction === 'next' ? 1 : -1
    } else {
      colStart = direction === 'next' ? 0 : maxCol - 1
      colEnd = row === startRow ? startCol : direction === 'next' ? maxCol : -1
      colStep = direction === 'next' ? 1 : -1
    }

    for (let col = colStart; direction === 'next' ? col < colEnd : col >= colEnd; col += colStep) {
      if (!fromCurrentPosition && row === startRow && col === startCol) {
        continue
      }

      if (isInMergedRange(currentCellRange, row, col)) {
        continue
      }

      const bindingPath = sheet.getBindingPath(row, col)
      const cellValue = sheet.getValue(row, col)

      if (bindingPath && (cellValue === null || cellValue === undefined || cellValue === '')) {
        return { row, col }
      }
    }
  }

  return null
}

function getMergedCellRange(sheet, row, col) {
  try {
    const span = sheet.getSpan(row, col)
    if (span) {
      return {
        row: span.row,
        col: span.col,
        rowCount: span.rowCount,
        colCount: span.colCount,
      }
    }
  } catch (error) {
    console.log('获取合并范围失败:', error)
  }
  return null
}

function isInMergedRange(mergedRange, row, col) {
  if (!mergedRange) {
    return false
  }

  const { row: rangeRow, col: rangeCol, rowCount, colCount } = mergedRange
  return (
    row >= rangeRow && row < rangeRow + rowCount && col >= rangeCol && col < rangeCol + colCount
  )
}

function handleWorkbookInitialized(spreadInstance) {
  designer.value = spreadInstance

  registerZoomEventHandler(designer.value)
  designer.value.setData('isRibbonCollapse', true)

  const templateData = props.templateData
  const businessConfig = props.businessConfig
  if (businessConfig && templateData) {
    initGenericEditorUI(businessConfig.ui)
    loadTemplateData(props.templateBlob, templateData)
  } else {
    console.error('没有获取到业务配置和模板数据')
  }

  if (templateData && templateData.pathNameMapping) {
    fieldNameMapping.value = templateData.pathNameMapping
    console.log('字段名映射已初始化:', fieldNameMapping.value)
  } else {
    fieldNameMapping.value = []
    console.log('未找到字段名映射，使用空数组')
  }
}

function initGenericEditorUI(uiConfig) {
  navTitle.value = uiConfig.title

  navButtons.value = []
  const cancelBtn = {
    text: uiConfig.cancelButtonText || '取消',
    className: '',
    action: 'cancel',
  }
  navButtons.value.push(cancelBtn)

  if (!uiConfig.hideSaveButton) {
    const saveBtn = {
      text: uiConfig.saveButtonText || '保存',
      className: 'primary',
      action: 'save',
    }
    navButtons.value.push(saveBtn)
  }

  if (uiConfig.customButtons && uiConfig.customButtons.length > 0) {
    uiConfig.customButtons.forEach((button) => {
      const customBtn = {
        text: button.text,
        className: button.className || '',
        action: button.action,
      }
      navButtons.value.push(customBtn)
    })
  }
}

function handleNavButtonClick(button) {
  if (button.action === 'cancel') {
    cancelEdit()
  } else if (button.action === 'save') {
    saveRecord()
  } else {
    handleCustomAction(button.action)
  }
}

/**
 * 加载模板文件和数据，
 * @param templateBlob 模板文件，是模板数据的基础容器
 * @param templateData 模板数据，填充模板文件中对应的数据格
 */
function loadTemplateData(templateBlob, templateData) {
  console.log('触发模板渲染加载。。。')
  if (!designer.value || !templateData) return

  const spreadInstance = designer.value.getWorkbook()
  spreadInstance.touchToolStrip.clear()

  if (templateBlob) {
    console.log('开始加载模板文件')

    try {
      const blob = base64ToBlob(templateBlob, 'application/ssjson')

      spreadInstance.open(
        blob,
        () => {
          console.log('模板文件加载成功！')
          initDesignerSheetConfig(spreadInstance.getActiveSheet())

          if (templateData.schema) {
            initDataSource(designer.value, templateData)
          }
          handleSheetTableCopyTo(designer.value, true)
          initSpreadInputEvents(spreadInstance)
        },
        (error) => {
          console.error('模板文件加载失败:', error)
        },
      )
    } catch (error) {
      console.error('处理模板文件时发生错误:', error)
    }
  } else if (templateData.schema) {
    console.log(
      '开始加载schema模板:',
      JSON.stringify(templateData.schema).substring(0, 200) + '...',
    )
    console.log('开始加载数据:', JSON.stringify(templateData.data).substring(0, 200) + '...')

    if (spreadInstance.getSheetCount() === 0) {
      const sheet = spreadInstance.addSheet(0)
      sheet.name('Sheet1')
      initDesignerSheetConfig(sheet)
      console.log('创建了新工作表')
    }

    initDataSource(designer.value, templateData)

    spreadInstance.repaint()
    handleSheetTableCopyTo(designer.value, true)
    initSpreadInputEvents(spreadInstance)
  }

  console.log('模板数据加载完成。。。')
}

function initDataSource(designer, templateData) {
  setDefaultSchema(designer, templateData.schema)
  setDataSource(designer, templateData.schema, templateData.data)
}

function initSpreadInputEvents(spreadInstance) {
  spreadInstance.bind(GC.Spread.Sheets.Events.CellClick, function (sender, args) {
    const sheet = args.sheet
    const row = args.row
    const col = args.col

    selectedPicture.value = null
    currentCell.value = { sheet, row, col }
    const cell = sheet.getCell(row, col)
    const bindingPath = sheet.getBindingPath(row, col) || '-'

    currentCellInfo.value = {
      value: cell.value() || '',
      isLocked: false,
      formatter: cell.formatter(),
      fieldName: bindingPath,
    }

    updateFieldNameDisplay(bindingPath)
    showFloatingInput()
  })

  console.log('悬浮输入框事件已绑定')
}

function handleCustomAction(action) {
  if (!designer.value) return

  const spreadInstance = designer.value.getWorkbook()
  spreadInstance.save(async (blob) => {
    removeSheetCellFocus(designer.value)
    const base64 = await blobToBase64(blob)
    const dataJSON = getDataSource(designer.value)
    const schemaJSON = getDefaultSchema(designer.value)

    emit('customAction', {
      action,
      data: {
        blob: base64,
        dataJSON,
        schemaJSON,
        businessType: props.businessConfig.businessType,
        templateId: props.templateData.data?.templateId || props.templateData.template,
        reportUrl: props.templateData.data?.templateUrl || props.templateData.templateUrl || '',
      },
    })
  })
}

function saveRecord() {
  if (!designer.value || !props.businessConfig || !props.templateData) return

  const spreadInstance = designer.value.getWorkbook()
  spreadInstance.save(async (blob) => {
    removeSheetCellFocus(designer.value)
    const base64 = await blobToBase64(blob)
    const dataJSON = getDataSource(designer.value)
    const schemaJSON = getDefaultSchema(designer.value)

    emit('save', {
      blob: base64,
      dataJSON,
      schemaJSON,
      businessType: props.businessConfig.businessType,
      templateId: props.templateData.data?.templateId || props.templateData.template,
      reportUrl: props.templateData.data?.templateUrl || props.templateData.templateUrl || '',
    })
  })
}

function cancelEdit() {
  emit('cancel')
}

function goBack() {
  uni.navigateBack({
    delta: 1,
    success: () => {
      // 返回成功后的回调函数
    },
    fail: () => {
      // 返回失败后的回调函数
    },
  })
}

function showDialog(message, timer = 3000) {
  dialogMessage.value = message
  dialogVisible.value = true
  setTimeout(() => {
    dialogVisible.value = false
  }, timer)
}

function updateDataSource(dataSource) {
  const templateData = props.templateData
  if (!designer.value || !templateData || !templateData.schema) return
  setDataSource(designer.value, templateData.schema, dataSource)
  showDialog('数据更新完成')
}

function onWebViewResize(newWidth) {
  console.log('收到屏幕尺寸变化通知，新宽度:', newWidth)
  if (designer.value) {
    const spreadInstance = designer.value.getWorkbook()
    spreadInstance.touchToolStrip.clear()
    const activedSheet = spreadInstance.getActiveSheet()
    initDesignerSheetConfig(activedSheet)
  }
}

function setKeyboardHeight(height) {
  keyboardHeight.value = height
}

function handleCameraResult(result) {
  if (result.success && result.imageData) {
    console.log('收到相机拍摄结果:', result)
  } else {
    console.log('相机拍摄失败或用户取消')
  }
}

watch(
  () => props.templateBlob,
  (newBlob, oldBlob) => {
    if (newBlob && newBlob !== oldBlob && designer.value && props.templateData) {
      console.log('templateBlob 发生变化，重新加载模板，数据：', props.templateData)
      loadTemplateData(props.templateBlob, props.templateData)
    }
  },
)

defineExpose({
  onWebViewResize,
  updateDataSource,
  handleCameraResult,
  setKeyboardHeight,
})
</script>

<style scoped>
* {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

.spread-designer-generic {
  position: relative;
  height: 100vh;
  overflow: hidden;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.main-container {
  width: 100%;
  height: calc(100vh - clamp(44px, 8vw, 56px) - 95px);
}

.navigator-bar {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: clamp(44px, 8vw, 56px);
  min-height: 44px;
  padding: 0 16px;
  background-color: #f8f9fa;
  border-bottom: 1px solid #e9ecef;
}

.nav-left {
  display: flex;
  flex: 1;
  align-items: center;
}

.back-btn {
  padding: 0 8px;
  font-size: 18px;
  cursor: pointer;
  background: none;
  border: 0;
  border-radius: 4px;
  transition: background-color 0.2s;
}

.back-btn:hover {
  background-color: #e9ecef;
}

.nav-title {
  flex: 1;
  font-size: 17px;
  font-weight: 600;
  color: #333;
  text-align: center;
}

.nav-right {
  display: flex;
  flex: 1;
  gap: 8px;
  justify-content: flex-end;
}

.nav-btn {
  padding: 0 12px;
  font-size: 12px;
  color: #495057;
  white-space: nowrap;
  cursor: pointer;
  background-color: #fff;
  border: 1px solid #dee2e6;
  border-radius: 4px;
  transition: all 0.2s;
}

.nav-btn:hover {
  background-color: #f8f9fa;
}

.nav-btn.primary {
  color: white;
  background-color: #007bff;
  border-color: #007bff;
}

.nav-btn.primary:hover {
  background-color: #0056b3;
}

.nav-btn.warning {
  color: white;
  background-color: #e6a23c;
  border-color: #e6a23c;
}

.floating-input-wrapper {
  position: relative;
  width: 100%;
}

.floating-input-wrapper textarea {
  width: 100%;
  height: 40px;
  padding: 8px 32px 8px 12px;
  font-size: 14px;
  resize: none;
  border: 1px solid #d9d9d9;
  border-radius: 4px;
  outline: none;
}

.floating-input-wrapper textarea:focus {
  border-color: #007aff;
}

.clear-btn {
  position: absolute;
  top: 50%;
  right: 8px;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  font-size: 16px;
  font-weight: bold;
  color: #666;
  cursor: pointer;
  background: #f0f0f0;
  border: none;
  border-radius: 12px;
  transform: translateY(-50%);
}

.clear-btn:hover {
  color: #333;
  background: #e0e0e0;
}

.floating-field-name-display {
  display: flex;
  flex: 1;
  align-items: center;
  height: 35px;
  padding: 6px 12px;
  margin-right: 8px;
  overflow: hidden;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 12px;
  line-height: 1.2;
  color: #666;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: #f5f5f5;
  border-radius: 4px;
}

.floating-input-container {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  width: 100%;
  min-height: 85px;
  padding: 8px 12px 12px 12px;
  background: white;
  border-top: 1px solid #e0e0e0;
  transition: all 0.3s ease;
}

.floating-input-container.keyboard-visible {
  position: fixed !important;
  bottom: var(--keyboard-height, 0);
  z-index: 10000;
  min-height: 105px;
  max-height: 345px;
}

.floating-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 35px;
  padding: 0 2px;
  margin-top: 4px;
  margin-bottom: 10px;
}

.button-group {
  display: flex;
  gap: 8px;
  align-items: center;
}

.prev-cell-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 60px;
  height: 35px;
  padding: 6px 12px;
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  color: white;
  cursor: pointer;
  background: #007aff;
  border: none;
  border-radius: 4px;
  transition: all 0.2s ease;
}

.prev-cell-btn:hover:not(:disabled) {
  background: #0056b3;
  transform: translateY(-1px);
}

.prev-cell-btn:active:not(:disabled) {
  transform: translateY(0);
}

.prev-cell-btn:focus {
  outline: none;
  box-shadow: 0 0 0 1px rgba(0, 122, 255, 0.3);
}

.prev-cell-btn:disabled {
  color: #999999;
  cursor: not-allowed;
  background: #cccccc;
  opacity: 0.7;
}

.next-cell-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 60px;
  height: 35px;
  padding: 6px 12px;
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  color: white;
  cursor: pointer;
  background: #007aff;
  border: none;
  border-radius: 4px;
  transition: all 0.2s ease;
}

.next-cell-btn:hover:not(:disabled) {
  background: #0056b3;
  transform: translateY(-1px);
}

.next-cell-btn:active:not(:disabled) {
  transform: translateY(0);
}

.next-cell-btn:focus {
  outline: none;
  box-shadow: 0 0 0 1px rgba(0, 122, 255, 0.3);
}

.next-cell-btn:disabled {
  color: #999999;
  cursor: not-allowed;
  background: #cccccc;
  opacity: 0.7;
}

.dialog-overlay {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 10001;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.5);
}

.dialog-content {
  max-width: 80%;
  padding: 20px;
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.modal-message {
  font-size: 16px;
  line-height: 1.5;
  color: #333;
  text-align: center;
}
</style>
