/* eslint-disable no-prototype-builtins */
import { createContext, useState, useEffect, use, useRef, useCallback, ReactNode } from 'react';
import get from 'lodash/get';
import Cookie from 'js-cookie';

import browserLang from './browser-lang';
import { translate } from './util';

import { getCookieOptions, resolveRootDomain } from '../Util';
import type { Locale } from '../type';

const cookieName = 'nf_lang';

// 跨应用传递多语言选择的方式是在 query string 中添加 locale 参数，LocaleSelector 要高优先级遵守这个参数
const getLocaleFromSearchParams = (
  languages: { code: string }[],
  url: string = window.location.href
): string | null => {
  const locale = new URL(url).searchParams.get('locale');
  if (languages.find((x) => x.code === locale)) {
    return locale;
  }
  return null;
};

const setLocaleParam = (locale: Locale, url: string = window.location.href) => {
  const urlObj = new URL(url);
  const hasLocaleParam = urlObj.searchParams.has('locale');
  if (hasLocaleParam) {
    urlObj.searchParams.set('locale', locale);
    window.history.replaceState({}, '', urlObj.href);
  }
};

const getLocale = (languages: { code: string }[] = []): string => {
  const langParams = {
    languages: languages.map((item) => item.code),
    // 取 languages 首个元素的 code 值, 如果不存在则取 'en'
    fallback: languages?.[0]?.code || 'en',
  };
  return getLocaleFromSearchParams(languages) || Cookie.get(cookieName) || browserLang(langParams)!;
};

const setLocale = (locale: Locale) => {
  const opts = getCookieOptions();

  // @compatibility 移除历史上使用根域名的旧 cookie
  if (!opts.domain) {
    const rootDomain = resolveRootDomain();
    if (rootDomain) {
      Cookie.remove(cookieName, { path: opts.path || '/', domain: rootDomain });
    }
  }

  Cookie.set(cookieName, locale, opts);
  setLocaleParam(locale);
};

const getLanguages = (arg?: { code: string; name: string }[]): { code: string; name: string }[] => {
  const env = get(window, 'blocklet.languages');
  if (Array.isArray(env) && env.length) {
    return env;
  }

  if (Array.isArray(arg) && arg.length) {
    return arg;
  }

  return [
    { code: 'en', name: 'English' },
    { code: 'zh', name: '简体中文' },
  ];
};

export interface LocaleProviderProps {
  children: ReactNode;
  locale?: Locale;
  fallbackLocale?: Locale;
  translations: Record<string, any>;
  onLoadingTranslation?: (locale: Locale, languages: { code: string }[]) => void;
  languages?: { code: string; name: string }[];
}

export interface LocaleContextType {
  locale: Locale;
  defaultLocale: Locale;
  changeLocale: (locale: Locale) => void;
  t: (key: string, data?: Record<string, any>) => string;
  languages: { code: string; name: string }[];
}

// FIXME: @wangshijun revert this before release
const LocaleContext = createContext<LocaleContextType>(null!);
const { Provider, Consumer } = LocaleContext;

function LocaleProvider({
  children,
  locale = undefined,
  fallbackLocale = 'en',
  translations,
  onLoadingTranslation = undefined,
  languages = [],
  ...rest
}: LocaleProviderProps) {
  const langs = getLanguages(languages);
  // eslint-disable-next-line prefer-const
  let [currentLocale, setCurrentLocale] = useState<Locale>(locale || getLocale(langs));
  const defaultLocale = langs?.[0]?.code ?? fallbackLocale;
  const lastCurrentLocale = useRef<string>(defaultLocale);
  const [, setForceUpdate] = useState(0);

  const changeLocale = (newLocale: Locale) => {
    setCurrentLocale(newLocale);
    setLocale(newLocale);
  };

  useEffect(() => {
    const tmpLocale = locale || getLocale(langs);
    if (tmpLocale !== currentLocale) {
      changeLocale(tmpLocale);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [locale]);

  // handle async loading of translations
  if (currentLocale && translations[currentLocale] && typeof translations[currentLocale] === 'function') {
    const tmpLocale = currentLocale;
    try {
      Promise.resolve(translations[tmpLocale]()).then((res) => {
        if (res && typeof res === 'object') {
          translations[tmpLocale] = res;
          setForceUpdate((x) => (x > 999 ? 0 : x + 1));
        }
      });
    } catch (err) {
      console.error(err);
    }

    onLoadingTranslation?.(tmpLocale, langs);
    currentLocale = lastCurrentLocale.current;
  } else {
    lastCurrentLocale.current = currentLocale;
  }

  const t = useCallback(
    (key: string, data?: Record<string, any>) => translate(translations, key, currentLocale, fallbackLocale, data),
    [translations, currentLocale, fallbackLocale]
  );

  return (
    <Provider value={{ locale: currentLocale, changeLocale, t, languages: langs, defaultLocale, ...rest }}>
      {children}
    </Provider>
  );
}

function useLocaleContext() {
  const context = use(LocaleContext);
  return context;
}

export { LocaleProvider, Consumer as LocaleConsumer, LocaleContext, useLocaleContext, setLocale, getLocale };
