
/**
 * Parse the time to string
 * @param {(Object|string|number)} time
 * @param {string} cFormat
 * @returns {string | null}
 */
export function parseTime (time, cFormat) {
  if (arguments.length === 0 || !time) {
    return null
  }
  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
  let date
  if (typeof time === 'object') {
    date = time
  } else {
    if ((typeof time === 'string')) {
      if ((/^[0-9]+$/.test(time))) {
        time = parseInt(time)
      } else {
        time = time.replace(new RegExp(/-/gm), '/')
      }
    }

    if ((typeof time === 'number') && (time.toString().length === 10)) {
      time = time * 1000
    }
    date = new Date(time)
  }
  const formatObj = {
    y: date.getFullYear(),
    m: date.getMonth() + 1,
    d: date.getDate(),
    h: date.getHours(),
    i: date.getMinutes(),
    s: date.getSeconds(),
    a: date.getDay()
  }
  // eslint-disable-next-line camelcase
  const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
    const value = formatObj[key]
    // Note: getDay() returns 0 on Sunday
    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
    return value.toString().padStart(2, '0')
  })
  // eslint-disable-next-line camelcase
  return time_str
}

/**
 * @param {number} time
 * @param {string} option
 * @returns {string}
 */
export function formatTime (time, option) {
  if (('' + time).length === 10) {
    time = parseInt(time) * 1000
  } else {
    time = +time
  }
  const d = new Date(time)
  const now = Date.now()

  const diff = (now - d) / 1000

  if (diff < 30) {
    return '刚刚'
  } else if (diff < 3600) {
    // less 1 hour
    return Math.ceil(diff / 60) + '分钟前'
  } else if (diff < 3600 * 24) {
    return Math.ceil(diff / 3600) + '小时前'
  } else if (diff < 3600 * 24 * 2) {
    return '1天前'
  }
  if (option) {
    return parseTime(time, option)
  } else {
    return (
      d.getMonth() +
      1 +
      '月' +
      d.getDate() +
      '日' +
      d.getHours() +
      '时' +
      d.getMinutes() +
      '分'
    )
  }
}

/**
 * @param {string} url
 * @returns {Object}
 */
export function param2Obj (url) {
  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
  if (!search) {
    return {}
  }
  const obj = {}
  const searchArr = search.split('&')
  searchArr.forEach(v => {
    const index = v.indexOf('=')
    if (index !== -1) {
      const name = v.substring(0, index)
      const val = v.substring(index + 1, v.length)
      obj[name] = val
    }
  })
  return obj
}

/**
 * @param {string} path
 * @returns {Boolean}
 */
export function isExternal (path) {
  return /^(https?:|mailto:|tel:)/.test(path)
}

/**
 * @description: 统计当前被选中的ID组
 * @return: 被选中记录 的ID组
 * @param {*} selection 列表中被选中的记录
 */
export function countSelIdArr (selection) {
  const idArr = []
  selection.map((item) => idArr.push(item.id))
  return idArr
}

/**
 * @description: 封装文本转语音功能 responsiveVoice接口
 * @param {*} text 要播放的文本
 */
export function speakText (text) {
  if (!text || text.trim() === '') {
    console.error('请输入要转换的文本！')
    return
  }
  window.responsiveVoice.speak(text, 'Chinese Female')
}

/**
 * 过滤价格数值 超过小数点两位得 保留两位
 * @param {string} value
 */
export function formatPrice (value) {
  if (isNaN(value)) return '' // 如果不是数字，则返回空字符串
  // 格式化价格为保留两位小数
  let formattedPrice = parseFloat(value).toFixed(2)
  // 如果价格有两位小数以上，则保留两位小数，否则显示整数或者一位小数
  const decimalCount = (formattedPrice.split('.')[1] || []).length
  if (decimalCount > 2) {
    formattedPrice = parseFloat(formattedPrice).toFixed(2)
  }
  return formattedPrice
}

/**
 * @description: 把对象保存为 JSON 文件并触发下载
 * @param {*} data - 要保存的对象数据
 * @param {string} filename - 下载的文件名，默认为 data_时间
 */
export function saveObjectToFile (data, filename = `data_${Date.now()}.json`) {
  if (!data || typeof data !== 'object') {
    alert('无效的数据对象')
    return
  }

  // 将对象格式化为 JSON 字符串
  const content = JSON.stringify(data, null, 2)

  // 创建 Blob 对象
  const blob = new Blob([content], { type: 'application/json' })

  // 创建一个临时下载链接
  const link = document.createElement('a')
  link.href = URL.createObjectURL(blob)
  link.download = filename

  // 模拟点击以触发下载
  link.click()

  // 释放资源
  URL.revokeObjectURL(link.href)
}

/* ----------------------- 几何 线代 等 相关  ----------------------- */

/**
 * @abstract 传入一组坐标组，返回包围盒中心点（即最小矩形包围框的中心）
 * @param {Array} positions - 包含经度、纬度和高度的数组，格式：[[经度, 纬度, 高度], ...]
 * @return {Object|null} 返回包围盒中心点对象 { lon: 经度, lat: 纬度, alt: 高度 }
 *                       如果输入无效则返回 null
 */
export function getBoundingCenter (positions) {
  if (!Array.isArray(positions) || positions.length === 0) return null

  // 初始化边界极值
  let minLon = Infinity; let maxLon = -Infinity
  let minLat = Infinity; let maxLat = -Infinity
  let minAlt = Infinity; let maxAlt = -Infinity

  // 遍历所有点，计算边界最大最小值
  positions.forEach(pos => {
    const [lon, lat, alt = 0] = pos
    if (lon < minLon) minLon = lon
    if (lon > maxLon) maxLon = lon
    if (lat < minLat) minLat = lat
    if (lat > maxLat) maxLat = lat
    if (alt < minAlt) minAlt = alt
    if (alt > maxAlt) maxAlt = alt
  })

  // 计算包围盒中心点坐标，返回对象格式
  return {
    lon: (minLon + maxLon) / 2,
    lat: (minLat + maxLat) / 2,
    alt: (minAlt + maxAlt) / 2
  }
}

/**
 * @abstract 传入一组坐标组，返回几何中心点（重心）
 * @param {Array} positions - 包含经度、纬度和高度的数组，格式：[[经度, 纬度, 高度], ...]
 * @return {Object|null} 返回几何中心点对象 { lon: 经度, lat: 纬度, alt: 高度 }
 *                       如果输入无效则返回 null
 */
export function getGeometricCenter (positions) {
  if (!Array.isArray(positions) || positions.length === 0) return null

  // 累加所有点的经纬高坐标
  const sum = positions.reduce((acc, [lon, lat, alt = 0]) => {
    acc.lon += lon
    acc.lat += lat
    acc.alt += alt
    return acc
  }, { lon: 0, lat: 0, alt: 0 })

  const count = positions.length

  // 计算平均值作为几何中心，返回对象格式
  return {
    lon: sum.lon / count,
    lat: sum.lat / count,
    alt: sum.alt / count
  }
}

/**
 * @description:等待 Mapbox 地图的画布容器（Canvas Container）准备就绪。因为 Mapbox 初始化是异步的，某些操作需要等待画布容器加载完成才能执行。
 * @param {Object} map - Mapbox GL JS 地图实例对象
 * @param {number} maxRetry - 最大重试次数，防止无限等待，默认5次
 * @returns {Promise<HTMLElement>} 返回一个 Promise，成功时返回画布容器元素，失败时抛出错误
 */
export function waitForMapCanvasReady (map, maxRetry = 5) {
  return new Promise((resolve, reject) => {
    /**
     * 递归检查函数，使用 requestAnimationFrame 尽可能快地检测画布容器是否可用
     * @param {number} retry 剩余重试次数
     */
    const check = (retry) => {
      // 尝试获取画布容器元素
      const container = map?.getCanvasContainer?.()

      // 如果获取成功，表示地图画布已准备好
      if (container) {
        resolve(container) // Promise 成功返回画布容器
      } else if (retry > 0) { // 如果还没准备好且重试次数未用尽，继续异步下一帧检测
        // requestAnimationFrame 在浏览器每一帧时执行回调，比 setTimeout 更高效和精准
        requestAnimationFrame(() => check(retry - 1))
      } else { // 超过最大重试次数，拒绝 Promise，避免无限等待
        reject(new Error('Map canvas container is not ready after multiple retries'))
      }
    }

    // 启动首次检测，带上最大重试次数参数
    check(maxRetry)
  })
}
