import { nextTick } from 'vue';
import { isUndefined, isNull, isString, isFinite } from 'lodash-es';

const noop = () => {};
const noopInNoop = () => noop;
const defaultContainer = () => document.body;
async function sleep(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}
function asyncExpect(fn, timeout) {
  return new Promise(resolve => {
    if (typeof timeout === 'number') {
      setTimeout(() => {
        fn();
        resolve();
      }, timeout);
    } else {
      nextTick(() => {
        fn();
        resolve();
      });
    }
  });
}

// in order to test transitions, we need to use
// await rAF() after firing transition events.
const rAF = async () => {
  return new Promise(resolve => {
    requestAnimationFrame(() => {
      requestAnimationFrame(async () => {
        resolve(null);
        await nextTick();
      });
    });
  });
};
const hasOwn = (val, key) => Object.hasOwnProperty.call(val, key);
const requestAnimationFrame = (() => {
  const hackRAF = function (func) {
    return setTimeout(() => {
      func === null || func === void 0 || func();
    }, 10);
  };
  if (typeof window !== 'undefined') {
    return window.requestAnimationFrame || hackRAF;
  }
  return hackRAF;
})();
const isFirefox = () => !!window.navigator.userAgent.match(/firefox/i);
const extractPropsDefaultValue = props => {
  const defaultValue = {};
  Object.keys(props).forEach(key => {
    if (props[key].default) {
      defaultValue[key] = props[key].default;
    }
  });
  return defaultValue;
};

// 10px => 10
const depx = value => {
  if (isUndefined(value) || isNull(value)) {
    return undefined;
  }
  if (isString(value) && value.endsWith('px')) {
    const formatValue = value.slice(0, value.length - 2);
    if (isFinite(Number(formatValue))) {
      return Number(formatValue);
    }
  }
  if (isFinite(Number(value))) {
    return Number(value);
  }
  console.warn('[depx] 转换失败，原始值为：', value);
  return value;
};

// 10 => 10px
const pxfy = value => {
  if (isUndefined(value) || isNull(value)) {
    return undefined;
  }
  if (isFinite(value)) {
    return `${value}px`;
  }
  if (isFinite(Number(value))) {
    return `${Number(value)}px`;
  }
  return value;
};

// 90 => 90deg
const degfy = value => {
  // 如果输入值是字符串，且已经包含了"deg"后缀，则直接返回
  if (isString(value) && value.endsWith('deg')) {
    return value;
  }
  if (isFinite(Number(value))) {
    return `${Number(value)}deg`;
  }

  // 如果输入值既不是数字也不是字符串，或者是一个不能转换为数字的字符串，返回一个错误信息
  throw new Error(`Invalid deg: ${value}`);
};
function getParentNode(node) {
  // document type
  if (node.nodeType === 9) {
    return null;
  }
  return node.parentNode;
}
function getScrollParent(node) {
  if (node == null) {
    return null;
  }
  const parentNode = getParentNode(node);
  if (parentNode === null) {
    return null;
  }

  // Document
  if (parentNode.nodeType === 9) {
    return document;
  }

  // Element
  if (parentNode.nodeType === 1) {
    // Firefox want us to check `-x` and `-y` variations as well
    const {
      overflow,
      overflowX,
      overflowY
    } = getComputedStyle(parentNode);
    if (/(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)) {
      return parentNode;
    }
  }
  return getScrollParent(parentNode);
}

// 比Array.concat快
function concat(arr, arr2) {
  const arrLength = arr.length;
  const arr2Length = arr2.length;
  arr.length = arrLength + arr2Length;
  for (let i = 0; i < arr2Length; i++) {
    arr[arrLength + i] = arr2[i];
  }
  return arr;
}

// JSON.stringify
function stringify(value, onError) {
  let fallbackValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
  try {
    const str = JSON.stringify(value);
    return str;
  } catch (err) {
    onError === null || onError === void 0 || onError(err);
    return fallbackValue;
  }
}

export { asyncExpect, concat, defaultContainer, degfy, depx, extractPropsDefaultValue, getParentNode, getScrollParent, hasOwn, isFirefox, noop, noopInNoop, pxfy, rAF, requestAnimationFrame, sleep, stringify };
