/*
 * @Author: 赵伟
 * @Date: 2024-03-25 13:52:54
 * @Description: 工具类
 */

import { PageEnum } from '@/enums/pagesEnums';
import G6 from '@antv/g6';

/**
 * 生成 8 位随机数
 * @returns 8 位随机数
 */
export function s8() {
  return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1);
}

export function getNameByCode(list: any[], code: any) {
  let name = '';
  list.forEach((item) => {
    if (item.dictValue === code) name = item.dictLabel;
  });
  return name;
}

/**
 * 完全解析 json 字符串，不会抛异常
 *
 * @param text - the string to be parsed
 * @returns the parsed JSON object if the string is a valid JSON text, null otherwise
 */
export function parseJsonText(text?: string | null): any | null {
  if (text === undefined || text === null || text === '') {
    return null;
  }
  try {
    return JSON.parse(text);
  } catch (error) {
    return null;
  }
}

/**
 * Checks if a given value is a plain object.
 *
 * A plain object is an object that is created using the Object constructor.
 * It does not have any special properties or methods that are not part of the
 * standard Object prototype.
 *
 * @param {any} value - The value to be checked.
 * @returns {boolean} true if the value is a plain object, false otherwise.
 */
function isPlainObject(value: any): boolean {
  if (value === null || typeof value !== 'object') return false;
  let proto = Object.getPrototypeOf(value);
  while (proto !== null) {
    if (proto.constructor && proto.constructor !== Object) return false;
    proto = Object.getPrototypeOf(proto);
  }
  return true;
}

/**
 * Converts all property names in an object from underscore notation to camelCase.
 *
 * If the object is not a plain object, it is returned as is.
 *
 * @param obj - The object whose property names need to be converted.
 * @returns The object with all property names converted to camelCase.
 */
export function underscoreToCamelCase(obj: Record<string, any>) {
  if (!isPlainObject(obj)) {
    return obj;
  }
  const newObj: Record<string, any> = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      const newKey = key.replace(/([-_][a-z])/gi, function ($1) {
        return $1.toUpperCase().replace('[-_]', '').replace('_', '');
      });
      let value = obj[key];
      if (Array.isArray(value)) {
        value = value.map((item) => underscoreToCamelCase(item));
      } else if (isPlainObject(value)) {
        value = underscoreToCamelCase(value);
      }
      newObj[newKey] = value;
    }
  }
  return newObj;
}

/**
 * Converts all property names in an object from camelCase to underscore notation.
 *
 * If the object is not a plain object, it is returned as is.
 *
 * @param obj - The object whose property names need to be converted.
 * @returns The object with all property names converted to underscore notation.
 */
export function camelCaseToUnderscore(obj: Record<string, any>) {
  if (!isPlainObject(obj)) {
    return obj;
  }
  const newObj: Record<string, any> = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      const newKey = key.replace(/([A-Z])/g, '_$1').toLowerCase();
      let value = obj[key];
      if (Array.isArray(value)) {
        value = value.map((item) => camelCaseToUnderscore(item));
      } else if (isPlainObject(value)) {
        value = camelCaseToUnderscore(value);
      }
      newObj[newKey] = value;
    }
  }
  return newObj;
}

/**
 * Recursively converts all null values in an object to undefined.
 *
 * If the given value is not an object, it is returned as is.
 *
 * @param obj - The object whose null values need to be converted.
 * @returns The object with all null values converted to undefined.
 */
export function nullToUndefined(obj: Record<string, any> | null) {
  if (obj === null) {
    return undefined;
  }
  if (!isPlainObject(obj)) {
    return obj;
  }
  const newObj: Record<string, any> = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      const value = obj[key];
      if (value === null) {
        newObj[key] = undefined;
      } else if (Array.isArray(value)) {
        newObj[key] = value.map((item) => nullToUndefined(item));
      } else if (isPlainObject(value)) {
        newObj[key] = nullToUndefined(value);
      } else {
        newObj[key] = value;
      }
    }
  }
  return newObj;
}

/**
 * Recursively converts all undefined values in an object to null.
 *
 * If the given value is not an object, it is returned as is.
 *
 * @param obj - The object whose undefined values need to be converted.
 * @returns The object with all undefined values converted to null.
 */

export function undefinedToNull(obj?: Record<string, any>) {
  if (obj === undefined) {
    return null;
  }
  if (!isPlainObject(obj)) {
    return obj;
  }
  const newObj: Record<string, any> = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      const value = obj[key];
      if (value === undefined) {
        newObj[key] = null;
      } else if (Array.isArray(value)) {
        newObj[key] = value.map((item) => undefinedToNull(item));
      } else if (isPlainObject(value)) {
        newObj[key] = undefinedToNull(value);
      } else {
        newObj[key] = value;
      }
    }
  }
  return newObj;
}

/**
 * Changes the property names of an object based on a mapping provided.
 *
 * @param obj - The object whose property names need to be changed.
 * @param mapping - The mapping of old property names to new property names.
 * @return The object with the changed property names.
 */
export function changePropertyName(obj: Record<string, any>, mapping: Record<string, string>) {
  if (!isPlainObject(obj)) {
    return obj;
  }
  const newObj: Record<string, any> = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      let value = obj[key];
      const newKey = mapping.hasOwnProperty(key) ? mapping[key] : key;
      if (Array.isArray(value)) {
        value = value.map((item) => changePropertyName(item, mapping));
      } else if (isPlainObject(value)) {
        value = changePropertyName(value, mapping);
      }
      newObj[newKey] = value;
    }
  }
  return newObj;
}

/**
 * 计算能显示的字符串
 * @param {string} str 要裁剪的字符串
 * @param {number} maxWidth 最大宽度
 * @param {number} fontSize 字体大小
 * @return {string} 处理后的字符串
 */
export const fittingString = (str: string, maxWidth: number, fontSize: number): string => {
  if (!str) {
    return '';
  }
  const ellipsis = '...';
  const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
  let currentWidth = 0;
  let res = str;
  const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
  str.split('').forEach((letter, i) => {
    if (currentWidth > maxWidth - ellipsisLength) return;
    if (pattern.test(letter)) {
      // Chinese charactors
      currentWidth += fontSize;
    } else {
      // get the width of single letter according to the fontSize
      currentWidth += G6.Util.getLetterWidth(letter, fontSize);
    }
    if (currentWidth > maxWidth - ellipsisLength) {
      res = `${str.substring(0, i)}${ellipsis}`;
    }
  });
  return res;
};

/**
 * Checks if a given string is empty, undefined, or null.
 *
 * @param {any} str - the string to be checked
 * @return {boolean} true if the string is empty, undefined, or null, false otherwise
 */
export const isEmpty = (str?: any | null): boolean => {
  return str === '' || str === undefined || str === null;
};

/**
 * Checks if a given value is undefined or null.
 *
 * @param {any} value - the value to be checked
 * @return {boolean} true if the value is undefined or null, false otherwise
 */
export const hasNoValue = (value?: any | null): boolean => {
  return value === undefined || value === null;
};

/**
 * 获取 git 仓库的 url
 *
 * @param {string} [url] - the url of the git repository
 * @param {string} [branch] - the branch of the repository
 * @return {string} the url of the repository
 *
 * If `branch` is given, the url will be in the format of 'http://gitlab.com/user/repo/tree/branch'.
 * Otherwise, the url will be in the format of 'http://gitlab.com/user/repo'.
 */
export const getGitUrl = (url?: string, branch?: string): string => {
  if (!url) {
    return '';
  }
  let gitUrlStr = url.replace(/\.git$/, '');
  const gitUrl = new URL(gitUrlStr);
  if (gitUrl.port === '30202') {
    gitUrl.port = '30203'; // 30202 该为 30203
  }
  gitUrlStr = gitUrl.href;

  return branch ? `${gitUrlStr.toString()}/tree/${branch}` : gitUrlStr;
};

/**
 * 判断是否需要登录
 *
 * @param {string} pathname - the pathname to be checked
 * @return {boolean} true if the pathname needs to be authorized, false otherwise
 */
export const needAuth = (pathname: string): boolean => {
  return pathname !== PageEnum.LOGIN && pathname !== PageEnum.Authorize;
};

/**
 * 表格排序
 *
 * @param {any} a - the first value to be compared
 * @param {any} b - the second value to be compared
 * @returns {number} -1 if a is less than b, 1 if a is greater than b, 0 if a is equal to b
 *
 * The sorter function compares two values and returns a number indicating their relative order.
 * The function first checks if either value is null or undefined. If so, it returns -1 if b is null or undefined,
 * and 1 if a is null or undefined.
 * If both values are numbers, it returns the difference between the two numbers.
 * If both values are strings, it returns the result of the localeCompare() method.
 * Otherwise, it returns 0.
 */
export const tableSorter = (a: any, b: any) => {
  if (b === null || b === undefined) {
    return -1;
  }
  if (a === null || a === undefined) {
    return 1;
  }
  if (typeof a === 'number' && typeof b === 'number') {
    return a - b;
  }
  if (typeof a === 'string' && typeof b === 'string') {
    return a.localeCompare(b);
  }
  return 0;
};

/**
 * Trim the given character from both ends of the given string.
 *
 * @param {string} ch - the character to trim
 * @param {string} str - the string to trim
 * @return {string} the trimmed string
 */
export const trimCharacter = (str: string, ch: string): string => {
  if (str === null || str === undefined) {
    return str;
  }
  const reg = new RegExp(`^${ch}|${ch}$`, 'g');
  return str.trim().replace(reg, '');
};

/**
 * Converts an empty string to undefined.
 *
 * @param {string} [value] - The string to convert.
 * @return {string | undefined} The converted string or undefined.
 */
export const convertEmptyStringToUndefined = (value?: string): string | undefined => {
  return value === '' ? undefined : value;
};
