import { Pos } from "@/utils/math.ts";
import { v4 as uuid } from "uuid";

/**
 * 四舍五入
 * @param num
 * @param b 保留位数
 * @returns
 */
export const round = (num: number, b: number = 2) => {
  const scale = Math.pow(10, b);
  return Math.round(num * scale) / scale;
};

/**
 * 范围取余
 * @param num
 * @param mod
 */
export const rangMod = (num: number, mod: number) => ((num % mod) + mod) % mod;

/**
 * 有偏差的indexOf
 * @param arr
 * @param warp
 * @param val
 * @returns
 */
export const warpIndexOf = (arr: number[], warp: number, val: number) =>
  arr.findIndex((num) => val >= num - warp && val <= num + warp);

/**
 * 多个函数合并成一个函数
 * @param fns
 * @returns
 */
export const mergeFuns = (...fns: (() => void)[] | (() => void)[][]) => {
  return () => {
    fns.forEach((fn) => {
      if (Array.isArray(fn)) {
        fn.forEach((f) => f());
      } else {
        fn();
      }
    });
  };
};

export const copy = <T>(data: T): T => JSON.parse(JSON.stringify(data))

/**
 * 获取数据类型
 * @param value
 * @returns
 */
export const toRawType = (value: unknown): string =>
  Object.prototype.toString.call(value).slice(8, -1);

// 是否修改
const _inRevise = (raw1: any, raw2: any, readly: Set<[any, any]>): boolean => {
  if (raw1 === raw2) return false;
  const rawType1 = toRawType(raw1);
  const rawType2 = toRawType(raw2);

  if (rawType1 !== rawType2) {
    return true;
  } else if (
    rawType1 === "String" ||
    rawType1 === "Number" ||
    rawType1 === "Boolean"
  ) {
    if (rawType1 === "Number" && isNaN(raw1) && isNaN(raw2)) {
      return false;
    } else {
      return raw1 !== raw2;
    }
  }

  const rawsArray = Array.from(readly.values());
  for (const raws of rawsArray) {
    if (raws.includes(raw1) && raws.includes(raw2)) {
      return false;
    }
  }
  readly.add([raw1, raw2]);

  if (rawType1 === "Array") {
    return (
      raw1.length !== raw2.length ||
      raw1.some((item1: any, i: number) => _inRevise(item1, raw2[i], readly))
    );
  } else if (rawType1 === "Object") {
    const rawKeys1 = Object.keys(raw1).sort();
    const rawKeys2 = Object.keys(raw2).sort();

    return (
      _inRevise(rawKeys1, rawKeys2, readly) ||
      rawKeys1.some((key) => _inRevise(raw1[key], raw2[key], readly))
    );
  } else if (rawType1 === "Map") {
    const rawKeys1 = Array.from(raw1.keys()).sort();
    const rawKeys2 = Array.from(raw2.keys()).sort();

    return (
      _inRevise(rawKeys1, rawKeys2, readly) ||
      rawKeys1.some((key) => _inRevise(raw1.get(key), raw2.get(key), readly))
    );
  } else if (rawType1 === "Set") {
    return inRevise(Array.from(raw1.values()), Array.from(raw2.values()));
  } else {
    return raw1 !== raw2;
  }
};

/**
 * 查看数据是否被修改
 * @param raw1
 * @param raw2
 * @returns
 */
export const inRevise = (raw1: any, raw2: any) =>
  _inRevise(raw1, raw2, new Set());

// 防抖
export const debounce = <T extends (...args: any) => any>(
  fn: T,
  delay: number = 160
) => {
  let timeout: any;
  return function (...args: Parameters<T>) {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      fn.apply(null, args);
    }, delay);
  };
};

/**
 * 获取数据变化
 * @param newIds
 * @param oldIds
 * @returns
 */
export const getChangePart = <T>(newIds: T[], oldIds: T[] = []) => {
  const addPort = newIds.filter((newId) => !oldIds.includes(newId));
  const delPort = oldIds.filter((oldId) => !newIds.includes(oldId));
  const holdPort = oldIds.filter((oldId) => newIds.includes(oldId));

  return { addPort, delPort, holdPort };
};

export const flatPositions = (positions: Pos[]) =>
  positions.flatMap((p) => [p.x, p.y]);

export const flatToPositions = (coords: number[]) => {
  const positions: Pos[] = [];
  for (let i = 0; i < coords.length; i += 2) {
    positions.push({
      x: coords[i],
      y: coords[i + 1],
    });
  }
  return positions;
};

export const onlyId = () => uuid();

export const startAnimation = (update: () => void, dur = -1) => {
  let isStop = false;
  const animation = () => {
    requestAnimationFrame(() => {
      if (!isStop) {
        update();
        animation();
      }
    });
  };
  animation();

  let timeout: any
  if (dur >= 0) {
    setTimeout(() => isStop = true, dur)
  }

  return () => {
    clearTimeout(timeout)
    isStop = true
  }
};

export const arrayInsert = <T>(
  array: T[],
  item: T,
  canInsert: (eItem: T, insertItem: T) => boolean
) => {
  let i = 0;
  for (i = 0; i < array.length; i++) {
    if (canInsert(array[i], item)) {
      break;
    }
  }
  array.splice(i, 0, item);
  return array;
};

