/**
 * 魔方魔派系统工具包
 */
import MofangMopaiCalculator from "./mofang-mopai-calculator"
import MofangCalculatorV2 from "./mofang-calculator-v2"

/**
 * 深拷贝
 * @param data { any }
 * @returns any
 */
function deepCopy<T>(data: T): T {
    let d: any
    if (typeof data === 'object') {
        if (Array.isArray(data)) {
            d = []
            for (let i = 0, len = data.length; i < len; i++) d[i] = deepCopy(data[i])
        } else if (data === null) {
            d = null
        } else if (data.constructor === RegExp) {
            d = data
        } else if (Object.keys(data).length) {
            d = {}
            for (const key in data) {
                const k = key
                d[k] = deepCopy(data[k])
            }
        } else {
            d = data
        }
    } else {
        d = data
    }
    return d
}

/**
 * 防抖
 * @param fn {Function}
 * @param delay {number}
 * @returns {Function}
 */
function debounce<T = Function>(fn: T, delay: number = 200): T {
    let timer: ReturnType<typeof setTimeout>
    if (typeof fn !== 'function') {
        throw new TypeError('类型错误，传入函数不是一个方法')
    }
    return function (this: unknown) {
        const args = arguments
        clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(this, args)
        }, delay)
    } as any
}

/**
 * 节流
 * @param fn - The function to be throttled.
 * @param delay - The number of milliseconds to delay.
 * @returns A new throttled function.
 */
function throttle<T = Function>(fn: T, delay: number = 200): T {
    let isRunning: boolean = false
    if (typeof fn !== 'function') {
        throw new TypeError('类型错误，传入函数不是一个方法')
    }
    return function (this: unknown) {
        const args = arguments
        if (isRunning) return
        isRunning = true
        fn.apply(this, args)
        setTimeout(() => {
            isRunning = false
        }, delay)
    } as any
}

/**
 * 去重
 * Returns a new array without duplicate elements.
 * @param arr - The array to remove duplicates from.
 * @returns A new array without duplicate elements.
 */
function unique<T>(arr: []): T {
    if (!Array.isArray(arr)) {
        throw new TypeError('类型错误，传入参数不是一个Array')
    }
    let list: any[] = []
    for (const value of arr) {
        if (typeof value === "object" && value !== null) {
            if (list.every((v) => JSON.stringify(v) !== JSON.stringify(value))) {
                list.push(value);
            }
        } else if (value !== undefined && list.indexOf(value) === -1) {
            list.push(value);
        }
    }
    return list as T
}

/**
 * Flattens an array of arrays into a single level array.
 *
 * @param arr - the array to flatten
 * @returns a new array with all the elements of the original array, regardless of their nesting level
 */
function flat<T>(arr: T): T {
    if (!Array.isArray(arr)) {
        throw new TypeError('类型错误，传入参数不是一个Array')
    }
    return arr.reduce((prev, cur: any) => {
        return prev.concat(Array.isArray(cur) ? flat(cur) : cur)
    }, [])
}

/**
 * 过滤掉object中的无效键值
 * @param data object
 * @returns object
 */
function getValidObject<T = Record<string, any>>(data: T): T {
    if (typeof data !== 'object' && Object.prototype.toString.call(data) !== '[object Object]') {
        throw new TypeError('类型错误，传入参数不是一个Object')
    }
    let obj = {} as T
    for (const key in data) {
        const value: any = data[key]
        let isEnable = false
        if (typeof value === 'object' && value !== null) {
            isEnable = isEmpty(value)
        } else {
            isEnable = value || value === 0
        }
        isEnable && (obj[key] = value)
    }
    return obj
}

/**
 * 基于前端的计算器
 * @param formula { string } 计算公式
 * @returns number 计算结果
 * 
 * @example
 * calc('1.2+0.3*0.02+4')   //  return 5.206
 */
function calc(formula: string): number{
    const ca = new MofangMopaiCalculator(formula)
    return ca.get()
}

function calcV2(number1: number, number2: number, method: string): number {
    return MofangCalculatorV2(number1, number2, method)
}

function getUUID(len: number = 32): string {
    const chars = Array.from('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
    const uuid: string[] = []
    const radix = len
    for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
    return uuid.join('')
}

/**
 * 判断Pbject或Array是否为空
 * @param data { object | array }
 * @returns boolean
 */
function isEmpty(data: Object): boolean {
    if (Array.isArray(data)) {
        return data.length === 0
    } else {
        return Object.keys(data).length === 0
    }
}
function isNotEmpty(data: Object): boolean {
    return !isEmpty(data)
}

/**
 * 格式化数组分组
 * @param arr 
 * @returns array
 */
function zip<T>(...args: any): T {
    if (!args || !Array.isArray(args) || args.some(item => !Array.isArray(item))) {
        throw new TypeError('类型错误，传入参数错误')
    }
    const resultList = [] as any[]
    args.forEach((list: [], listIdx: number) => {
        list.forEach((item: any, itemIdx: number) => {
            if (!resultList[itemIdx]) resultList[itemIdx] = []
            resultList[itemIdx][listIdx] = item
        })
    })
    return resultList as T
}

function unzip<T>(...args: any): T {
    if (!args || !Array.isArray(args) || args.some(item => !Array.isArray(item))) {
        throw new TypeError('类型错误，传入参数错误')
    }
    let len = 0
    const resultList = [] as any[]
    args.forEach((list: [], listIdx: number) => {
        if (len === 0) {
            len = list.length
        }
        if (len !== list.length) {
            throw new TypeError('they are not same length Array in list')
        }
        list.forEach((item: any, itemIdx: number) => {
            if (!resultList[itemIdx]) resultList[itemIdx] = []
            resultList[itemIdx][listIdx] = item
        })
    })
    return resultList as T
}

/**
 * 判断object|array中是否存在指定参数
 * @param keyname { string }
 * @param data { object | array }
 * @returns boolean
 */
function isExist(keyname: string, data: Object): boolean {
    if (Array.isArray(data)) {
        return data.indexOf(keyname) > -1
    } else {
        const keys = Object.keys(data)
        return keys.indexOf(keyname) > -1
    }
}

/**
 * 判断数据类型
 * @param data { any }
 * @returns string 数据的类型，小写
 */
function whichTypeOf(data: any): string {
    if (typeof data === 'object') {
        return Array.isArray(data) ? 'array' : Object.prototype.toString.call(data) === '[object Object]' ? 'object' : 'unknownType'
    } else {
        return typeof data
    }
}

/**
 * 动态置入script标签，引用js文件
 * @param src { string } js文件链接
 * @param cb { function } 状态回调
 * @returns promise 状态回调
 */
function loadJs(src: string, cb?: Function): Promise<string> {
    const script = document.createElement('script')
    script.type = 'text/javascript'
    script.src = src
    document.getElementsByTagName('head')[0].appendChild(script)
    return new Promise((resolve, rejects) => {
        script.onload = () => {
            cb?.('ok')
            resolve('ok')
        }
        script.onerror = () => {
            cb?.('error')
            rejects('error')
        }
    })
}

const toolkit = {
    deepCopy,
    debounce,
    throttle,
    unique,
    getValidObject,
    calc,
    calcV2,
    getUUID,
    isEmpty,
    isNotEmpty,
    zip,
    unzip,
    isExist,
    whichTypeOf,
    loadJs,
    flat,
}

declare const window: Window
interface Window {
    [key: string]: any
}
/**
 * 设置window全局utils
 * @param keyname { string } 键名 [default: $utils]
 */
function setGlobalUtils(keyname: string) {
    let key = keyname || '$utils'
    if (window as Window) {
        !window[key] && (window[key] = toolkit)
    }
}

export {
    deepCopy,
    debounce,
    throttle,
    unique,
    getValidObject,
    calc,
    calcV2,
    getUUID,
    isEmpty,
    isNotEmpty,
    zip,
    unzip,
    isExist,
    whichTypeOf,
    loadJs,
    setGlobalUtils,
    flat,
    toolkit as default
}