export function copy(obj) {
	return JSON.parse(JSON.stringify(obj))
}

export function splitArray2Parts(arr, size) {
  const result = []
  for (let i = 0; i < arr.length; i += size) {
    result.push(arr.slice(i, i + size))
  }
  return result
}

export function camelCase(name) {
	return name.substr(0, 1).toLowerCase() + name.substr(1)
}

export class UrlBuilder {
	constructor(url) {
		this.url = url
		this.paramList = []
	}

	append(name, value) {
		if (isNotEmpty(value)) {
			this.paramList.push({ name: name, value: value })
		}
		return this
	}

	build() {
		this.paramList.forEach((e, i, a) => {
			if (i === 0) {
				this.url += "?"
			} else {
				this.url += "&"
			}
			this.url += e.name + "=" + e.value
		})
		return this.url
	}
}

export function isNotEmpty(val) {
	return val !== undefined && val !== null && val !== ""
}

// export function isEmpty(val) {
// 	return val === undefined || val === null || val === ""
// }

export function prepareUrl(url) {
	return new UrlBuilder(url)
}

// export function debounce(fn, delay = 500) {
// 	let timer
// 	return e => {
// 		e.persist()
// 		clearTimeout(timer)
// 		timer = setTimeout(() => {
// 			fn(e)
// 		}, delay)
// 	}
// }

export function throttle(fn, delay, atleast) {
	let timeout = null,
		startTime = new Date()
	return function () {
		let curTime = new Date()
		clearTimeout(timeout)
		if (curTime - startTime >= atleast) {
			fn()
			startTime = curTime
		} else {
			timeout = setTimeout(fn, delay)
		}
	}
}

export function clickUrl(url) {
	const httpLink = document.createElement("a")
	httpLink.href = url
	httpLink.target = "_blank"
	httpLink.click()
}

export function escape2Html(str) {
	var temp = document.createElement("div")
	temp.innerHTML = str
	var output = temp.innerText || temp.textContent
	temp = null
	return output
}

/**
 * ! 不支持计算 Set 或 Map
 * @param {*} val
 * @example
 * true if: 0, [], {}, null, '', undefined
 * false if: 'false', 'undefined'
 */
export function isEmpty(val) {
  // return val === undefined || val === null || val === "";
  return [Object, Array].includes((val || {}).constructor) && !Object.entries(val || {}).length;
}
/**
 * 数组排序
 */
export const sortBy = (key) => {
  return (a, b) => (a[key] > b[key] ? 1 : b[key] > a[key] ? -1 : 0);
};

/**
 * Object排序keys
 */
export const sortKeys = (obj) =>
  Object.keys(obj)
    .sort()
    .reduce((a, k2) => ({ ...a, [k2]: obj[k2] }), {});

/**
 * 数组排序, 给定排序数组
 * @param {array} items 需要排序的数组
 * @param {array} keyName 排序的key
 * @param {array} keyOrder 给定排序
 * @returns
 */
export const sortArrayByOrder = (items, keyName, keyOrder) => {
  return items.sort((a, b) => {
    return keyOrder.indexOf(a[keyName]) - keyOrder.indexOf(b[keyName]);
  });
};
/**
 * 合并Object, 递归地
 */
export function merge(...objects) {
  const isDeep = objects.some((obj) => obj !== null && typeof obj === 'object');

  const result = objects[0] || (isDeep ? {} : objects[0]);

  for (let i = 1; i < objects.length; i++) {
    const obj = objects[i];

    if (!obj) continue;

    Object.keys(obj).forEach((key) => {
      const val = obj[key];

      if (isDeep) {
        if (Array.isArray(val)) {
          result[key] = [].concat(Array.isArray(result[key]) ? result[key] : [result[key]], val);
        } else if (typeof val === 'object') {
          result[key] = merge(result[key], val);
        } else {
          result[key] = val;
        }
      } else {
        result[key] = typeof val === 'boolean' ? val : result[key];
      }
    });
  }

  return result;
}

/**
 * 数组分组
 * - 相当于 lodash 的 _.groupBy
 * @see https://www.lodashjs.com/docs/lodash.groupBy#_groupbycollection-iteratee_identity
 */
export function groupBy(array = [], callback) {
  return array.reduce((groups, item) => {
    const key = typeof callback === 'function' ? callback(item) : item[callback];

    if (!groups[key]) {
      groups[key] = [];
    }

    groups[key].push(item);
    return groups;
  }, {});
}

/**
 * 创建一个从 object 中选中的属性的对象。
 * @param {*} object
 * @param {array} keys
 */
export function pick(object, keys) {
  return keys.reduce((obj, key) => {
    if (object && Object.prototype.hasOwnProperty.call(object, key)) {
      obj[key] = object[key];
    }
    return obj;
  }, {});
}

/**
 * 返回对象的副本，经过筛选以省略指定的键。
 * @param {*} object
 * @param {string[]} keysToOmit
 * @returns
 */
export function omit(object, keysToOmit) {
  return Object.fromEntries(Object.entries(object).filter(([key]) => !keysToOmit.includes(key)));
}

/**
 * 深拷贝
 */
export function cloneDeep(value) {
  // return structuredClone(value);
  if (typeof value !== 'object' || value === null) {
    return value;
  }

  const result = Array.isArray(value) ? [] : {};

  for (const key in value) {
    if (Object.prototype.hasOwnProperty.call(value, key)) {
      result[key] = cloneDeep(value[key]);
    }
  }

  return result;
}

/**
 * 向零四舍五入, 固定精度设置
 */
function curriedFix(precision = 0) {
  return function (number) {
    // Shift number by precision places
    const shift = Math.pow(10, precision);
    const shiftedNumber = number * shift;

    // Round to nearest integer
    const roundedNumber = Math.round(shiftedNumber);

    // Shift back decimal place
    return roundedNumber / shift;
  };
}
/**
 * 向零四舍五入, 保留2位小数
 */
export const fixTo2Decimals = curriedFix(2);
/**
 * 向零四舍五入, 保留4位小数
 */
export const fixTo4Decimals = curriedFix(4);

export const fixTo1Decimals = curriedFix(1);
export const fixToInt = curriedFix(0);

/**
 * 映射
 * @example
 * const keyMap = {
    a: [{key: 'a1'}, {key: 'a2', transform: v => v * 2}],
    b: {key: 'b1'}
  };
  const result = objectMapper({a: 1, b: 3}, keyMap);
  // result = {a1: 1, a2: 2, b1: 3}
 *
 */
export function objectMapper(input, keyMap) {
  // Loop through array mapping
  if (Array.isArray(input)) {
    return input.map((obj) => objectMapper(obj, keyMap));
  }

  if (typeof input === 'object') {
    const mappedObj = {};

    Object.keys(input).forEach((key) => {
      // Keep original keys not in keyMap
      if (!keyMap[key]) {
        mappedObj[key] = input[key];
      }
      // Handle array of maps
      if (Array.isArray(keyMap[key])) {
        keyMap[key].forEach((map) => {
          let value = input[key];
          if (map.transform) value = map.transform(value);
          mappedObj[map.key] = value;
        });

        // Handle single map
      } else {
        const map = keyMap[key];
        if (map) {
          let value = input[key];
          if (map.transform) value = map.transform(value);
          mappedObj[map.key || key] = value;
        }
      }
    });

    return mappedObj;
  }

  return input;
}

/**
 * 创建一个对应于对象路径的值数组
 */
export function at(obj, path) {
  let result;
  if (Array.isArray(obj)) {
    // array case
    const indexes = path.split('.').map((i) => parseInt(i));
    result = [];
    for (let i = 0; i < indexes.length; i++) {
      result.push(obj[indexes[i]]);
    }
  } else {
    // object case
    const indexes = path.split('.').map((i) => i);
    result = [obj];
    for (let i = 0; i < indexes.length; i++) {
      result = [result[0][indexes[i]]];
    }
  }
  return result;
}
/**
 * 删除 null/undefined
 */
export function flush(collection) {
  let result, len, i;
  if (!collection) {
    return undefined;
  }
  if (Array.isArray(collection)) {
    result = [];
    len = collection.length;
    for (i = 0; i < len; i++) {
      const elem = collection[i];
      if (elem != null) {
        result.push(elem);
      }
    }
    return result;
  }
  if (typeof collection === 'object') {
    result = {};
    const keys = Object.keys(collection);
    len = keys.length;
    for (i = 0; i < len; i++) {
      const key = keys[i];
      const value = collection[key];
      if (value != null) {
        result[key] = value;
      }
    }
    return result;
  }
  return undefined;
}

/**
 * 千分位 格式化数字
 */
export const numberFormatter = (number) => {
  return new Intl.NumberFormat().format(number);
};

/**
 * @example
 *   const obj = { a: { b: 'c' } };
 *   const keyArr = ['a', 'b'];
 *   getNestedValue(obj, keyArr);  // Returns: 'c'
 */
export const getNestedValue = (obj, keyArr) => {
  return keyArr.reduce((acc, curr) => {
    return acc && Object.prototype.hasOwnProperty.call(acc, curr) ? acc[curr] : undefined;
    // return acc && acc[curr];
  }, obj);
};

/**
 * 计算笛卡尔积
 */
export const cartesianProductArray = (arr, sep = '_', index = 0, prefix = '') => {
  let result = [];
  if (index === arr.length) {
    return [prefix];
  }
  arr[index].forEach((item) => {
    result = result.concat(cartesianProductArray(arr, sep, index + 1, prefix ? `${prefix}${sep}${item}` : `${item}`));
  });
  return result;
};

export const stringToColour = (str='', withFlag = true) => {
  var hash = 0
  if (str.length === 0) return hash;
  for (let i = 0; i < str.length; i++) {
    hash = str.charCodeAt(i) + ((hash << 5) - hash)
  }
  var colour = withFlag ? '#' : ''
  for (let i = 0; i < 3; i++) {
    var value = (hash >> (i * 8)) & 0xff
    value = (value % 150) + 50
    colour += ('00' + value.toString(16)).substr(-2)
  }
  return colour
}

export const debounce = (func, wait, immediate) => {
  var timeout;
  return function () {
    var context = this,
      args = arguments;
    clearTimeout(timeout);
    if (immediate && !timeout) func.apply(context, args);
    timeout = setTimeout(function () {
      timeout = null;
      if (!immediate) func.apply(context, args);
    }, wait);
  };
}

export const removeFormattingChars = (str) => {
  const regex = /[\r\n\t\v\f]/g;
  str = str.replace(regex, ' ');
  // Replace more than four consecutive spaces with a single space
  str = str.replace(/\s{4,}/g, ' ');
  return str;
}

export const olog = (text, ...args) => {
  console.log(
    `%c ${text} `,
    'background:#fb923c ; padding: 1px; border-radius: 3px;  color: #fff',...args
  );
};

export const sanitizeFilename = (str) => {
  // Remove whitespace and replace with hyphens
  str = str.replace(/\s+/g, '-');
  // Remove invalid characters and replace with hyphens
  str = str.replace(/[^a-zA-Z0-9.-]/g, '-');
  // Replace consecutive hyphens with a single hyphen
  str = str.replace(/-+/g, '-');
  // Trim leading and trailing hyphens
  str = str.replace(/^-+|-+$/g, '');
  return str;
}

export const formatBytes = (bytes, decimals = 2) => {
  if (bytes === 0) return '';

  const k = 1024;
  const dm = decimals < 0 ? 0 : decimals;
  const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];

  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
};
export const calcCacheSizes = async () => {
  try {
    let swCacheSize = 0;
    let diskCacheSize = 0;
    let indexedDBSize = 0;

    // 1. Get the service worker cache size
    if ('caches' in window) {
      const cacheNames = await caches.keys();
      for (const name of cacheNames) {
        const cache = await caches.open(name);
        const requests = await cache.keys();
        for (const request of requests) {
          const response = await cache.match(request);
          swCacheSize += Number(response.headers.get('Content-Length')) || 0;
        }
      }
    }

    // 2. Get the disk cache size
    // const diskCacheName = 'disk-cache';
    // const diskCache = await caches.open(diskCacheName);
    // const diskCacheKeys = await diskCache.keys();
    // for (const request of diskCacheKeys) {
    //   const response = await diskCache.match(request);
    //   diskCacheSize += Number(response.headers.get('Content-Length')) || 0;
    // }

    // 3. Get the IndexedDB cache size
    // const indexedDBNames = await window.indexedDB.databases();
    // for (const dbName of indexedDBNames) {
    //   const db = await window.indexedDB.open(dbName.name);
    //   const objectStoreNames = db.objectStoreNames;

    //   if (objectStoreNames !== undefined) {
    //     const objectStores = Array.from(objectStoreNames).map((storeName) => db.transaction([storeName], 'readonly').objectStore(storeName));

    //     for (const objectStore of objectStores) {
    //       const request = objectStore.count();
    //       request.onsuccess = () => {
    //         indexedDBSize += request.result;
    //       };
    //     }
    //   }
    // }

    return { swCacheSize, diskCacheSize, indexedDBSize, totalSize: Number(swCacheSize) + Number(diskCacheSize) + indexedDBSize };
  } catch (error) {
    console.error('Error getting cache sizes:', error);
  }
};

export const clearAllCaches = async (cb) => {
  try {
    // 1. Clear the service worker cache
    if ('caches' in window) {
    // if (navigator.serviceWorker) {
      const cacheNames = await caches.keys();
      await Promise.all(cacheNames.map((name) => caches.delete(name)));
    }

    // 2. Clear the disk cache (HTTP cache)
    // const diskCacheName = 'disk-cache';
    // await window.caches.delete(diskCacheName);
    // const diskCache = await window.caches.open(diskCacheName);
    // const diskCacheKeys = await diskCache.keys();
    // await Promise.all(diskCacheKeys.map((request) => diskCache.delete(request)));

    // 3. Clear the IndexedDB cache
    const indexedDBNames = await window.indexedDB.databases();
    await Promise.all(indexedDBNames.map((dbName) => window.indexedDB.deleteDatabase(dbName.name)));

    // Unregister the service worker
    const registration = await navigator.serviceWorker.getRegistration();
    if (registration) {
      await registration.unregister();
      console.log('Service worker unregistered');
    } else {
      console.log('No service worker registered');
    }
    if (typeof cb === 'function' ) {
      cb();
    }

  } catch (error) {
    console.error('Error clearing caches or unregistering service worker:', error);
  }
};

export const loadScript = (src) => {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.type = 'text/javascript';
    script.onload = resolve;
    script.onerror = reject;
    script.crossOrigin = 'anonymous';
    script.src = src;
    if (document.head.append) {
      document.head.append(script);
    } else {
      document.getElementsByTagName('head')[0].appendChild(script);
    }
  });
};

