import { EInfinityString, ETypeStrings, LeFE_Prefix, Segment } from './defines'
import {
  isArray,
  isBigInt,
  isDate,
  isFunction,
  isMap,
  isNaN,
  isNegativeInfinity,
  isObject,
  isPositiveInfinity,
  isSet,
  isSymbol,
  isUndefined,
  TAnyFunction
} from './type_detectors'
import { blockSelfToJSON, shouldBlockSelfToJSON } from './utils'

const assemble = (type: string, data?: string) =>
  `${LeFE_Prefix}${Segment}${type}${data ? Segment + data : ''}`

const isTypesNeededTraverse = (
  val: unknown
): val is unknown[] | Record<string, unknown> =>
  isArray(val) || (isObject(val) && !isDate(val) && !isSet(val) && !isMap(val))

const isArrowFunction = (str: string) => {
  // TODO: 这块判断有点不太好，后期考虑一下，搞个完善的正则什么的
  const arr = str.split('=>')
  if (arr.length === 1) {
    // 只有一项说明字符串里没有 =>, 肯定不是箭头函数
    return false
  }
  const part1 = arr[0].trim() // 取 => 前面的部分
  if (part1.startsWith('(') && part1.endsWith(')')) {
    // 如果这一部分是 (...) 的话，认为是箭头函数，排除这种： function () { return () => {} }
    return true
  }
  // 如果不是 (....), 那只能是单独是形参变量名，不可能包含 {, 排除这种: function () { return a => {} }, 保住这种 a => {}
  return part1.indexOf('{') === -1
}

const functionStringify = (func: TAnyFunction) => {
  const str = func.toString()
  if (
    str.startsWith('function') ||
    str.startsWith('async function') ||
    isArrowFunction(str)
  ) {
    return str
  }
  // 这个是为了处理方法，如 { test () {} }
  return str.startsWith('async')
    ? `async function ${str.slice(6)}`
    : `function ${str}`
}

const needNotTraverseHandler = (val: unknown) => {
  switch (true) {
    case isUndefined(val):
      return assemble(ETypeStrings.undefined)
    case isNegativeInfinity(val):
      return assemble(ETypeStrings.Infinity, EInfinityString.negative)
    case isPositiveInfinity(val):
      return assemble(ETypeStrings.Infinity, EInfinityString.positive)
    case isNaN(val):
      return assemble(ETypeStrings.NaN)
    case isBigInt(val):
      return assemble(ETypeStrings.BigInt, (val as bigint).toString())
    case isSymbol(val):
      // TODO: 辣鸡 TS 有 Bug, 目前不支持 symbol.prototype.description 属性。
      // 请关注官方 Release, 支持了以后升级改掉这个 any 类型声明。
      return assemble(ETypeStrings.Symbol, (val as any).description)
    case isFunction(val):
      return assemble(
        ETypeStrings.Function,
        functionStringify(val as TAnyFunction)
      )
    case isDate(val):
      return assemble(ETypeStrings.Date, `${(val as Date).getTime()}`)
    case isMap(val):
      return assemble(
        ETypeStrings.Map,
        stringify(Array.from((val as Map<unknown, unknown>).entries()))
      )
    case isSet(val):
      return assemble(ETypeStrings.Set, stringify([...(val as Set<unknown>)]))
    default:
      return val
  }
}

export const stringify = (data: Record<string, unknown> | unknown[]): string =>
  JSON.stringify(data, (key: string, val: unknown) => {
    if (!isTypesNeededTraverse(val)) {
      return needNotTraverseHandler(val)
    } else {
      return isObject(val)
        ? Object.entries(val).reduce(
            (prev, [k, v]) =>
              Object.assign(prev, {
                [k]: shouldBlockSelfToJSON(v) ? blockSelfToJSON(v) : v
              }),
            {}
          )
        : val.map(item =>
            shouldBlockSelfToJSON(item) ? blockSelfToJSON(item) : item
          )
    }
  })
