/**
   下载方法 通过blob和文件名
*/
export const downloadFile = (blob: Blob, fileName: string) => {
   const link = document.createElement("a")
   link.style.display = "none"
   link.href = window.URL.createObjectURL(blob)
   link.download = fileName
   // 触发点击
   document.body.appendChild(link)
   link.click()
   // 然后移除
   document.body.removeChild(link)
   window.URL.revokeObjectURL(link.href)
}

/**
   下载方法 通过文件路径
 */
export const downloadFileUrl = (url: string) => {
   const link = document.createElement("a")
   link.style.display = "none"
   link.href = url
   // 触发点击
   document.body.appendChild(link)
   link.click()
   // 然后移除
   document.body.removeChild(link)
   window.URL.revokeObjectURL(link.href)
}

/**
   批量下载方法 通过文件路径 尽量打成压缩包下载 这个方法很多浏览器禁用
  */
export const batchDownloadFileUrl = (url: string) => {
   const iframe = document.createElement("iframe")
   iframe.style.display = "none"
   iframe.src = url
   document.body.appendChild(iframe)
   iframe.onload = () => {
      iframe.remove()
   }
}

/**
 * 去掉字符串中的html标签
 */
export const removeHtmlTag = (str: string) => {
   return new DOMParser().parseFromString(str, "text/html").body.textContent || ""
}

/**
   复制文本到剪贴板
*/
export const copyText = (text: string) => {
   return navigator.clipboard.writeText(text)
}

/**
 * 时间转换
 * @param fmt yyyy-mm-dd hh:ii:ss
 */
export const format = (dateData?: Date | number | string, fmt?: string) => {
   //兼容ie  replace(/-/g, "/")
   const date = dateData ? new Date(typeof dateData === "string" ? dateData.replace(/-/g, "/") : dateData) : new Date()
   fmt = fmt || "yyyy-mm-dd"
   const opt = {
      "y+": date.getFullYear().toString(), // 年
      "m+": (date.getMonth() + 1).toString(), // 月
      "d+": date.getDate().toString(), // 日
      "h+": date.getHours().toString(), // 时
      "i+": date.getMinutes().toString(), // 分
      "s+": date.getSeconds().toString() // 秒
   }
   let k: keyof typeof opt
   for (k in opt) {
      const ret = new RegExp(`(${k})`).exec(fmt)
      if (ret) {
         fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0"))
      }
   }
   return fmt
}

/**
   获取两日期之间日期列表函数 返回数组
*/
export const getdiffdate = (stime: string, etime: string) => {
   //初始化日期列表，数组
   const diffdate: string[] = []
   let i = 0
   //开始日期小于等于结束日期,并循环
   while (stime <= etime) {
      diffdate[i] = stime
      //获取开始日期时间戳
      const stime_ts = new Date(stime.replace(/-/g, "/")).getTime()
      //增加一天时间戳后的日期
      const next_date = stime_ts + 24 * 60 * 60 * 1000
      //拼接年月日，这里的月份会返回（0-11），所以要+1
      const next_dates_y = new Date(next_date).getFullYear() + "-"
      const next_dates_m =
         new Date(next_date).getMonth() + 1 < 10 ? "0" + (new Date(next_date).getMonth() + 1) + "-" : new Date(next_date).getMonth() + 1 + "-"
      const next_dates_d = new Date(next_date).getDate() < 10 ? "0" + new Date(next_date).getDate() : new Date(next_date).getDate()
      stime = next_dates_y + next_dates_m + next_dates_d
      //增加数组key
      i++
   }
   return diffdate
}

/** 生成 uuid */
export const UUID = () => {
   let d = new Date().getTime()
   if (window.performance && typeof window.performance.now === "function") {
      d += performance.now() //use high-precision timer if available
   }
   const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
      const r = (d + Math.random() * 16) % 16 | 0
      d = Math.floor(d / 16)
      return (c == "x" ? r : (r & 0x3) | 0x8).toString(16)
   })
   return uuid
}

/** 深拷贝 */
export const deepCopy = <T extends Record<string, any>>(source: T): T => {
   const target = (Array.isArray(source) ? [] : {}) as T
   for (const k in source) {
      if (Object.hasOwnProperty.call(source, k)) {
         // __proto__上面的属性不拷贝
         const sourceVal = source[k]
         const typeSource = Object.prototype.toString.call(sourceVal).slice(8, -1)
         if (typeSource === "Object" || typeSource === "Array") {
            target[k] = deepCopy(sourceVal)
         } else {
            target[k] = sourceVal
         }
      }
   }
   return target
}

/** 判断是否是ie浏览器 */
export const isIE = () => {
   return !!window.ActiveXObject || "ActiveXObject" in window ? true : false
}

/**
 * 判断是不是空
 *
 * 能判断 "",[],null,undefined
 */
export const isEmpty = (val: string | Array<any> | number | null | undefined) => {
   if (typeof val === "number") {
      return val ? false : true
   } else {
      return val === null || val === undefined || val.length === 0 ? true : false
   }
}

/** 根据身份证号获取出生年月日 */
export const getBirthByIdCard = (idCard: string) => {
   let birthday = ""
   if (idCard.length == 15) {
      birthday = "19" + idCard.slice(6, 12)
   } else if (idCard.length == 18) {
      birthday = idCard.slice(6, 14)
   }
   birthday = birthday.replace(/(.{4})(.{2})/, "$1-$2-")
   //通过正则表达式来指定输出格式为:1990-01-01
   return birthday
}

/** 根据省份证获取性别 */
export const getSexByIdCard = (idCard: string) => {
   return parseInt(idCard.slice(-2, -1)) % 2 == 1 ? "1" : "0"
}

interface ThrottleFunc<T extends (...args: any[]) => any> {
   (...args: Parameters<T>): ReturnType<T>
   cancel(): void
}
/**
   创建并返回一个像节流阀一样的函数，当重复调用函数的时候，至少每隔 wait毫秒调用一次该函数。对于想控制一些触发频率较高的事件有帮助。
   默认情况下，throttle将在你调用的第一时间尽快执行这个function，并且，如果你在wait周期内调用任意次数的函数，都将尽快的被覆盖。
   如果你想禁用第一次首先执行的话，传递{leading: false}，还有如果你想禁用最后一次执行的话，传递{trailing: false}。
   如果需要取消预定的 throttle ，可以在 throttle 函数上调用 .cancel()。
*/
export const throttle = <T extends (...args: any[]) => any>(
   func: T,
   wait = 600,
   options: {
      trailing?: boolean
      leading?: boolean
   } = {}
): ThrottleFunc<T> => {
   let timeout: number | null, context: any, args: any[], result: ReturnType<T>
   let previous = 0
   const later = function () {
      previous = options.leading === false ? 0 : new Date().getTime()
      timeout = null
      result = func.apply(context, args)
      if (!timeout) (args = []), (context = null)
   }
   const throttled: ThrottleFunc<T> = function (this: any, ..._args) {
      const _now = new Date().getTime()
      if (!previous && options.leading === false) previous = _now
      const remaining = wait - (_now - previous)
      context = this
      args = _args
      if (remaining <= 0 || remaining > wait) {
         if (timeout) {
            clearTimeout(timeout)
            timeout = null
         }
         previous = _now
         result = func.apply(context, args)
         if (!timeout) (args = []), (context = null)
      } else if (!timeout && options.trailing !== false) {
         timeout = window.setTimeout(later, remaining)
      }
      return result
   }
   throttled.cancel = function () {
      timeout && clearTimeout(timeout)
      previous = 0
      args = []
      timeout = context = null
   }
   return throttled
}

interface DebouncedFunc<T extends (...args: any[]) => any> {
   (...args: Parameters<T>): ReturnType<T>
   cancel(): void
}
/**
   返回 function 函数的防反跳版本, 将延迟函数的执行(真正的执行)在函数最后一次调用时刻的 wait 毫秒之后. 对于必须在一些输入（多是一些用户操作）停止到达之后执行的行为有帮助。
   在 wait 间隔结束时，将使用最近传递给 debounced（去抖动）函数的参数调用该函数。
   传参 immediate 为 true， debounce会在 wait 时间间隔的开始调用这个函数 。（愚人码头注：并且在 waite 的时间之内，不会再次调用。）在类似不小心点了提交按钮两下而提交了两次的情况下很有用。
   如果需要取消预定的 debounce ，可以在 debounce 函数上调用 .cancel()。
*/
export const debounce = <T extends (...args: any[]) => any>(func: T, wait = 600, immediate = false): DebouncedFunc<T> => {
   let timeout: number | null, previous: number, args: any[], result: ReturnType<T>, context: any
   const later = function () {
      const passed = new Date().getTime() - previous
      if (wait > passed) {
         timeout = window.setTimeout(later, wait - passed)
      } else {
         timeout = null
         if (!immediate) result = func.apply(context, args)
         // This check is needed because `func` can recursively invoke `debounced`.
         if (!timeout) (args = []), (context = null)
      }
   }
   const debounced: DebouncedFunc<T> = function (this: any, ..._args) {
      context = this
      args = _args
      previous = new Date().getTime()
      if (!timeout) {
         timeout = window.setTimeout(later, wait)
         if (immediate) result = func.apply(context, args)
      }
      return result
   }
   debounced.cancel = function () {
      timeout && clearTimeout(timeout)
      args = []
      timeout = context = null
   }
   return debounced
}

/**
 * html字符串关键词高亮
 * @param html html格式的字符串
 * @param keyword 需要查找的关键字
 */
export function htmlMarkHitClass(html: string, keyword: string) {
   const repKey = "♽⚁" //如果查询的关键词与这个替换的冲突，查询就会出bug 排除以下情况
   if (["♽⚁", "♽", "⚁"].includes(keyword)) {
      return {
         html,
         indexLen: 0
      }
   }
   if (!keyword) {
      return {
         html,
         indexLen: 0
      }
   }
   let indexLen = 0
   const regTag = /<.*?>/gi
   const htmlTags = html.match(regTag) || []
   html = html.replace(regTag, repKey)
   html = html.replace(new RegExp(keyword, "g"), () => {
      indexLen++
      return `<span id="cus_${indexLen}" class="hitClass" >${keyword}</span>`
   })
   let regTagIndex = -1
   html = html.replace(new RegExp(repKey, "g"), () => {
      regTagIndex++
      return htmlTags[regTagIndex]
   })
   return {
      html,
      indexLen
   }
}

const classNameToArray = (cls = "") => cls.split(" ").filter(item => !!item.trim())
/** Boolean 判断有没有这个class */
export const hasClass = (el: HTMLElement | null | undefined, cls: string) => {
   if (!el) return false
   if (cls.includes(" ")) throw new Error("className should not contain space.")
   return el.classList.contains(cls)
}
/**
   添加class  cls:"a b c" or "a" or "a b"
*/
export const addClass = (el: HTMLElement | null | undefined, cls: string) => {
   if (!el) return
   el.classList.add(...classNameToArray(cls))
}

/** 删除class cls:"a b c" or "a" or "a b" */
export const removeClass = (el: HTMLElement | null | undefined, cls: string) => {
   if (!el) return
   el.classList.remove(...classNameToArray(cls))
}
/**
 * 获取el的style值
 *
 *  例：getStyle(document.querySelector("body"),"position") relative
 */
export const getStyle = (el: HTMLElement | null | undefined, styleName: keyof CSSStyleDeclaration): string => {
   if (!el) return ""
   if (styleName === "float") {
      styleName = "cssFloat"
   }
   try {
      const style = el.style[styleName]
      if (style) return style as string
      const computed = document.defaultView?.getComputedStyle(el, "")
      return computed ? (computed[styleName] as string) : ""
   } catch {
      return el.style[styleName] as string
   }
}
/**
 * 给el修改 style
 *
   setStyle(document.querySelector("body"),{left:0,position:"relative"})
   setStyle(document.querySelector("body"),"left","0"})
*/
export const setStyle = (el: HTMLElement | null | undefined, styleName: CSSStyleDeclaration | keyof CSSStyleDeclaration, value?: string) => {
   if (!el) return
   if (typeof styleName === "object") {
      let prop: keyof CSSStyleDeclaration
      for (prop in styleName) {
         setStyle(el, prop, styleName[prop])
      }
   } else {
      typeof value === "string" && (el.style[styleName as any] = value)
   }
}

/** 删除style
 *removeStyle(document.querySelector("body"),{left:"",position:""})
 * removeStyle(document.querySelector("body"),"left")
 */
export const removeStyle = (el: HTMLElement | null | undefined, style: CSSStyleDeclaration | keyof CSSStyleDeclaration) => {
   if (!el) return
   if (typeof style === "object") {
      let prop: keyof CSSStyleDeclaration
      for (prop in style) {
         setStyle(el, prop, "")
      }
   } else {
      setStyle(el, style, "")
   }
}
