/* eslint-disable no-bitwise */
import { lazy } from 'react';
import padStart from 'lodash/padStart';
import { getDIDMotifInfo, colors } from '@arcblock/did-motif';
import { mergeAllThemeOptions } from '@blocklet/theme';
import isNil from 'lodash/isNil';
import omitBy from 'lodash/omitBy';
import pRetry from 'p-retry';
import Cookies from 'js-cookie';
import colorConvert from 'color-convert';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import relativeTime from 'dayjs/plugin/relativeTime';
import updateLocale from 'dayjs/plugin/updateLocale';
import localizedFormat from 'dayjs/plugin/localizedFormat';

import { DID_PREFIX, BLOCKLET_SERVICE_PATH_PREFIX } from './constant';
import type { $TSFixMe, Locale } from '../type';
import { getFederatedEnabled } from './federated';

let dateTool: $TSFixMe | null = null;
const IP_V4_REGEX = /^(\d{1,3}\.){3}\d{1,3}(:\d+)?$/;
// 常见顶级域名
const commonSLDs = new Set(['co', 'com', 'net', 'org', 'gov', 'edu', 'ac']);
// 常见国家域名
const commonCcTLDs = new Set(['uk', 'au', 'cn', 'nz', 'za', 'in', 'br', 'mx', 'fr', 'it', 'ca']);

/** @deprecated for compatibility, please use `import { mergeAllThemeOptions } from '@blocklet/theme'` instead */
export { mergeAllThemeOptions as deepmergeAll };

/** 是否常见二段式顶级域名 */
export function isTwoSegmentTLD(domain: string) {
  if (!domain) {
    return false;
  }

  const parts = domain.split('.');

  if (parts.length < 2) return false;

  const sld = parts[parts.length - 2];
  const tld = parts[parts.length - 1];

  return commonSLDs.has(sld) && commonCcTLDs.has(tld);
}

/** 获取根域名 */
export function resolveRootDomain() {
  const { host } = window.location;

  if (!host) {
    return '';
  }

  if (IP_V4_REGEX.test(host)) {
    return '';
  }

  // 移除可能存在的端口号
  const hostWithoutPort = host.split(':')[0];
  const parts = hostWithoutPort.split('.');

  if (parts.length === 1) {
    return '';
  }

  // 二段式顶级域名
  if (parts.length > 2) {
    const tld = parts.slice(-2).join('.');

    if (isTwoSegmentTLD(tld)) {
      return `.${parts.slice(-3).join('.')}`;
    }
  }

  return `.${parts.slice(-2).join('.')}`;
}

export function parseQuery(str: string) {
  return str
    .replace(/^\?/, '')
    .split('&')
    .map((x) => x.split('='))
    .filter(([key]) => !!key)
    .reduce<Record<string, string | boolean>>((memo, x) => {
      const key = x[0];
      const value = decodeURIComponent(x[1]) || true;
      memo[key] = value;
      return memo;
    }, {});
}

export function stringifyQuery(params = {}) {
  return new URLSearchParams(params).toString();
}

export function getCookieOptions(
  expireInDays:
    | number
    | {
        expireInDays: number;
        path?: string;
        domain?: string;
        returnDomain?: boolean;
        sameSite?: Cookies.CookieAttributes['sameSite'];
        secure?: boolean;
      } = 1
) {
  let opts = expireInDays;

  if (typeof opts === 'number') {
    opts = { expireInDays: opts };
  }

  if (opts.path === undefined) {
    opts.path = '/';
  }

  if (!opts.expireInDays) {
    opts.expireInDays = 1;
  }

  const options: Cookies.CookieAttributes = {
    expires: opts.expireInDays,
    path: opts.path,
    domain: opts.domain || '',
    sameSite: opts.sameSite || 'Lax',
    // 允许自定义设置为 false，默认是 true
    secure: opts.secure !== false,
  };
  if (typeof window === 'undefined' || opts.domain || opts.returnDomain === false) {
    if (opts.returnDomain === false) {
      delete options.domain;
    }
    return options;
  }

  return options;
}

export const getColor = (props: $TSFixMe) => {
  if (props.color) {
    return props.color;
  }

  if (props.dark) {
    return props.theme.palette.common.white;
  }

  return props.theme.palette.text.primary;
};

export const getBackground = (props: $TSFixMe) => {
  if (props.background) {
    return props.background;
  }

  if (props.dark) {
    return props.theme.palette.common.black;
  }

  return props.theme.palette.common.white;
};

/**
 * 1. 将 props 中以 ::prop:: 开头的字符串替换为 component.defaultProps 中的值
 * 2. 将 props 中由 jsonAttrs 指定的 json 字符串转换为对象
 * 3. 最后将 props 中的 ::prop:: 字符串替换为 ''
 */
export function mergeProps<P extends object>(
  props: Record<string, any>,
  component: React.ComponentType<P>,
  jsonAttrs: string[] = []
) {
  const newProps = Object.assign({}, props);
  const defaultProps = (component as any).defaultProps || {};
  Object.keys(defaultProps).forEach((x) => {
    if (typeof newProps[x] === 'string' && newProps[x].indexOf('::prop::') === 0) {
      newProps[x] = defaultProps[x];
    }
  });

  if (Array.isArray(jsonAttrs)) {
    jsonAttrs.forEach((x) => {
      if (typeof newProps[x] === 'string') {
        try {
          newProps[x] = JSON.parse(newProps[x]);
        } catch (err) {
          // Do nothing
        }
      }
    });
  }

  Object.keys(newProps).forEach((x) => {
    if (typeof newProps[x] === 'string' && newProps[x].indexOf('::prop::') === 0) {
      newProps[x] = '';
    }
  });

  return newProps;
}

export function getCopyright(startYear = 2017) {
  const currentYear = new Date().getFullYear();
  return `${currentYear}` === `${startYear}`
    ? `© ArcBlock ${currentYear}`
    : `© ArcBlock ${startYear} - ${currentYear}`;
}

export const getTimezone = () => {
  if (
    typeof Intl === 'object' &&
    typeof Intl.DateTimeFormat === 'function' &&
    typeof Intl.DateTimeFormat().resolvedOptions === 'function'
  ) {
    return Intl.DateTimeFormat().resolvedOptions().timeZone;
  }

  return '';
};

export const str2color = (str: string) => {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    hash = str.charCodeAt(i) + ((hash << 5) - hash);
  }
  let colour = '#';
  for (let j = 0; j < 3; j++) {
    const value = (hash >> (j * 8)) & 0xff;
    colour += `00${value.toString(16)}`.substr(-2);
  }
  return colour;
};

export const formatUptime = (uptime: number) => {
  const total = Math.round(uptime / 1000);
  const hours = Math.floor(total / 3600);
  const minutes = Math.floor((total - hours * 3600) / 60);
  const seconds = total % 60;
  // @ts-expect-error
  return `${padStart(hours, 2, '0')}:${padStart(minutes, 2, '0')}:${padStart(seconds, 2, '0')}`;
};

/**
 * Set the date tool library which support for format() and locale() functions,
 *  moment and dayjs are recommended.
 */
export function setDateTool(tool: typeof dateTool) {
  dateTool = tool;
}

export function getDateTool() {
  return dateTool;
}

const createDateFormatter =
  (format: string) =>
  (date: string | number | Date, { locale, tz, isUtc }: { locale?: Locale; tz?: string; isUtc?: boolean } = {}) => {
    if (dateTool === null) {
      // 作为默认的 dateTool 使用
      dayjs.extend(localizedFormat);
      dayjs.extend(utc);
      dayjs.extend(timezone);
      dayjs.extend(updateLocale);
      dayjs.extend(relativeTime);
      dayjs.updateLocale('zh-cn', {
        // copy with https://github.com/iamkun/dayjs/blob/dev/src/locale/zh-cn.js
        relativeTime: {
          future: '%s后',
          past: '%s前',
          s: '几秒',
          m: '1 分钟',
          mm: '%d 分钟',
          h: '1 小时',
          hh: '%d 小时',
          d: '1 天',
          dd: '%d 天',
          M: '1 个月',
          MM: '%d 个月',
          y: '1 年',
          yy: '%d 年',
        },
      });
      setDateTool(dayjs);
      // throw new Error('call setDateTool to set the date tool library, such as: setDateTool(dayjs)');
    }

    if (isNil(date) || date === '') {
      return '-';
    }

    let instance = dateTool(date);

    if (tz) {
      instance = instance.tz(tz);
    }

    if (isUtc) {
      instance = instance.utc();
    }

    if (typeof locale !== 'undefined') {
      instance = instance.locale(locale);
    }

    return instance.format(format);
  };

/**
 * Format the time string as `ll` format: MMM D, YYYY
 * Ensure that the setDateTool() function is called first to set the time tool library.
 * @returns formatted date string
 */
export function formatToDate(
  date: string | number | Date,
  { locale = 'en', tz }: { locale?: Locale; tz?: string } = {}
) {
  return createDateFormatter('ll')(date, { locale, tz });
}

/**
 * Format the time string as `lll` format: MMM D, YYYY h:mm A
 * Ensure that the setDateTool() function is called first to set the time tool library.
 * @returns formatted date string
 */
export function formatToDatetime(
  date: string | number | Date,
  {
    locale = 'en',
    tz,
    isUtc = false,
    format = 'lll',
  }: { locale?: Locale; tz?: string; isUtc?: boolean; format?: string } = {}
) {
  return createDateFormatter(format)(date, { locale, tz, isUtc });
}

export function detectWalletExtension() {
  // @ts-expect-error Property 'ABT_DEV' does not exist on type 'Window & typeof globalThis'.
  const extension = window?.ABT_DEV || window.ABT;
  if (extension && typeof extension.open === 'function') {
    return extension;
  }

  return null;
}

export function openWebWallet({
  webWalletUrl,
  action = 'login',
  locale = 'en',
  url,
  windowFeatures,
  appInfo,
  memberAppInfo,
}: {
  webWalletUrl: string;
  action?: string;
  locale?: Locale;
  url: string;
  windowFeatures: $TSFixMe;
  appInfo: $TSFixMe;
  memberAppInfo: $TSFixMe;
}) {
  // web wallet extension
  const extension = detectWalletExtension();
  if (extension) {
    extension.open({
      action,
      locale,
      url: encodeURIComponent(url),
      appInfo: {
        ...appInfo,
      },
      memberAppInfo: {
        ...memberAppInfo,
      },
    });

    return { type: 'extension' };
  }

  const defaultWindowFeatures = {
    toolbar: 'no',
    location: 'no',
    status: 'no',
    menubar: 'no',
    scrollbars: 'yes',
    resizable: 'yes',
    // iphone 8plus size
    width: 414,
    height: 736,
  };
  const windowUrl = `${webWalletUrl}?action=${action}&locale=${locale}&url=${encodeURIComponent(url)}`;
  const mergedWindowFeatures = Object.assign({}, defaultWindowFeatures, windowFeatures);
  const getWindowWidth = (win: Window) => {
    return win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth;
  };
  // 默认在浏览器右上角弹出窗口
  if (!('left' in mergedWindowFeatures)) {
    const winWidth = getWindowWidth(window.top || window);
    const winLeft = window.screenLeft || window.screenX;
    mergedWindowFeatures.left = winWidth + winLeft - mergedWindowFeatures.width;
  }
  if (!('top' in mergedWindowFeatures)) {
    const winTop = window.screenTop || window.screenY;
    mergedWindowFeatures.top = winTop;
  }
  const strWindowFeatures = Object.keys(mergedWindowFeatures)
    .map((key) => `${key}=${mergedWindowFeatures[key]}`)
    .join(',');
  // 这里打开的是钱包的 URL，是安全的
  window.open(windowUrl, 'targetWindow', strWindowFeatures);
  return { type: 'web' };
}

export const getFontSize = (size: string | number) => {
  // 12px 及以上的 size 有效, 否则返回 inherit
  if (size && Number(size) >= 12) {
    return `${Number(size)}px`;
  }
  return 'inherit';
};

// 参考: asset-chain @arcblock/did
export const isEthereumDid = (did: string) => {
  const address = did.replace(DID_PREFIX, '');
  // check if it has the basic requirements of an address
  if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) {
    return false;
  }
  return true;
};

export const appendParams = (
  url: string,
  params: {
    imageFilter: 'crop' | 'resize';
    w?: number;
    h?: number;
    r?: 0 | 90 | 180 | 270;
  }
) => {
  if (!params) {
    return url;
  }
  try {
    // HACK: 如果 url 中带有域名，则 urlInstance.origin 为 url 中的域名；否则，借用当前页面的 location.origin 作为域名来拼接一个 url
    const urlInstance = new URL(url, window.location.origin);
    Object.keys(params).forEach((key) => {
      urlInstance.searchParams.set(key, String(params[key as keyof typeof params]));
    });
    // HACK: 如果前后域名一致，代表前面已经借用了 location.origin，这个时候只需要返回 urlInstance.pathname + urlInstance.search 即可
    if (urlInstance.origin === window.location.origin) {
      return urlInstance.pathname + urlInstance.search;
    }
    return urlInstance.href;
  } catch {
    return url;
  }
};

export const getUserAvatar = (avatar: string, size = 48) => {
  if (!avatar) {
    return avatar;
  }

  if (avatar.indexOf(BLOCKLET_SERVICE_PATH_PREFIX) >= 0) {
    return appendParams(avatar, {
      imageFilter: 'resize',
      w: size,
      h: size,
    });
  }

  return avatar;
};

export const sleep = (time = 0) => {
  return new Promise<void>((resolve) => {
    setTimeout(() => {
      resolve();
    }, time);
  });
};

export const isUrl = (str: string) => {
  return /^https?:\/\//.test(str);
};

const visitorIdKey = 'vid';
const visitorIdKeyLegacy = '__visitor_id';

export const getVisitorId = () => {
  // FIXME: @zhanghan 短期内做一个兼容，确保在 migrate 前的请求能够携带正确的 vid
  return Cookies.get(visitorIdKey) || localStorage.getItem(visitorIdKeyLegacy);
};

export const setVisitorId = (value: string | null) => {
  if (value === null) {
    Cookies.remove(visitorIdKey, {
      sameSite: 'None',
      secure: true,
    });
  } else {
    Cookies.set(visitorIdKey, value, {
      sameSite: 'None',
      secure: true,
      expires: 365,
    });
  }
};

export const ensureVisitorId = () => {
  let visitorId = localStorage.getItem(visitorIdKeyLegacy);
  if (visitorId) {
    localStorage.removeItem(visitorIdKeyLegacy);
    setVisitorId(visitorId);
  }
  if (getVisitorId()) {
    return;
  }

  if (!getFederatedEnabled()) {
    try {
      // 在支持 crypto.randomUUID 的环境中使用
      if (window.crypto && typeof window.crypto.randomUUID === 'function') {
        visitorId = window.crypto.randomUUID();
      } else {
        // 在不支持 crypto.randomUUID 的环境中生成随机 ID
        const randomValues = new Uint8Array(16);
        if (window.crypto && typeof window.crypto.getRandomValues === 'function') {
          window.crypto.getRandomValues(randomValues);
        } else {
          // 降级方案：使用 Math.random 生成
          for (let i = 0; i < 16; i++) {
            randomValues[i] = Math.floor(Math.random() * 256);
          }
        }

        // 转换为 UUID 格式
        const hexArray = Array.from(randomValues).map((b) => b.toString(16).padStart(2, '0'));
        visitorId = [
          hexArray.slice(0, 4).join(''),
          hexArray.slice(4, 6).join(''),
          hexArray.slice(6, 8).join(''),
          hexArray.slice(8, 10).join(''),
          hexArray.slice(10, 16).join(''),
        ].join('-');
      }
    } catch (error) {
      // 如果上述方法都失败，使用时间戳和随机数生成
      visitorId = `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
    }
  }

  if (visitorId) {
    setVisitorId(visitorId);
  }
};

export const getDIDColor = (did: string) => {
  if (isEthereumDid(did)) {
    const index =
      Uint8Array.from(
        did
          .slice(2)
          .match(/.{1,2}/g)!
          .map((pair) => parseInt(pair, 16))
      )
        .slice(0, 8)
        .reduce((acc, val) => acc + val, 0) % colors.length;

    return colors[index];
  }
  try {
    const didMotifInfo = getDIDMotifInfo(did);
    return didMotifInfo?.color;
  } catch {
    return null;
  }
};

type NestedTranslation = {
  [key: string]: string | NestedTranslation;
};

type TranslationsObject = {
  [locale: string]: NestedTranslation;
};

/**
 * Retrieves the appropriate translation based on the locale, with fallback options.
 * @return {string} The translated string based on the locale, with fallback options if necessary.
 */
export const getTranslation = (
  translations: TranslationsObject,
  locale: Locale,
  options: { fallbackLocale?: Locale; defaultValue?: string } = {}
) => {
  const { fallbackLocale = 'en', defaultValue = 'unknown' } = options;

  if (typeof translations === 'string') {
    return translations;
  }

  let translation;
  if (locale) {
    translation = translations[locale];
  }
  if (!translation || typeof translation !== 'string') {
    translation = translations[fallbackLocale];
  }
  if (!translation || typeof translation !== 'string') {
    translation = defaultValue;
  }

  return translation;
};

export const lazyRetry = (fn: () => Promise<any>) =>
  lazy(() =>
    pRetry(
      async () => {
        try {
          return await fn();
        } catch (error: unknown) {
          // HACK: pRetry 不会对 TypeError 的错误进行重试，手动放行了部分 network error 的错误，但此处的错误是经过 react-router 包装过的，不适用于上述的判断，所以这里需要特殊逻辑来进行处理
          if (error instanceof TypeError && !error.message.includes('Failed to fetch')) {
            throw error;
          }
          if (error instanceof Error) {
            throw new Error(error.message);
          }
          throw new Error('Unknown error');
        }
      },
      // 只需要重试两次，加上原本的一次，总共三次
      { retries: 2 }
    )
  );

export const cleanedObj = (obj: object) => {
  return omitBy(obj, isNil);
};

/**
 * 将十六进制颜色转换为 RGBA
 * @param hex 十六进制颜色字符串 (例如: "#FF0000" 或 "FF0000")
 * @param alpha 透明度值 (0-1 之间，默认为 1)
 * @returns RGBA 颜色字符串 (例如: "rgba(255, 0, 0, 1)")
 */
export function hexToRgba(hex: string, alpha = 1) {
  const [r, g, b] = colorConvert.hex.rgb(hex);
  // 返回 RGBA 格式
  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
