import * as constants from '../utils/currency'

import i18next, {
  Callback,
  FallbackLng,
  InitOptions,
  Module,
  TFunction,
  TFunctionResult,
  StringMap,
  TOptions,
  i18n,
  TFunctionKeys
} from 'i18next'
import types, { isTypes } from '../plugins/types'

import ICU from '@ies/intl-icu-plugin'
import InContext from '@ies/intl-incontext-plugin'
import { formatLang } from '../plugins/process'
import { IntlModule } from './index'

const defaultFallbackLanguage = 'zh-CN'
const defaultConfig = {
  lng: defaultFallbackLanguage, // 如果使用了 Language Detector，i18next 底层 lng 的权重是大于插件的
  fallbackLng: ['en-US'],
  inContext: true
}

const defaultPlugins: IntlModule[] = [ICU as any, InContext]

// Override
declare module 'i18next' {
  interface InitOptions {
    /**
     * @deprecated https://starling.bytedance.net/open/content/9163/111691#插值相关
     */
    onParseError?: Function
  }
}

export interface IntlInitOptions extends Omit<InitOptions, 'missingInterpolationHandler'> {
  formats?: any
  postProcess?: any
  thirdParamFallback?: boolean
  ignoreWarning?: boolean
  icuConfig?: any
  inContext?: boolean
  missingInterpolationHandler?: (param: {
    key: string
    text: string
    locale: string
    interpolation: {
      key: string
      val: any
    }
  }) => string
}
export interface I18nextInstance {
  instance: i18n
  config: InitOptions & {
    lng?: string
    fallbackLng?: string[]
    [key: string]: any
  }
  plugins: Module[]
  languages: FallbackLng
  init: boolean
  // eslint-disable-next-line @typescript-eslint/no-misused-new
  constructor(config: InitOptions, plugins: Module[])
  createInstance(initCallback?: Callback): void
  _updateLanguages(): void
  getLanguages(): FallbackLng
  changeLanguage(lng: any, callback: any): void
  getDir(lng: string): 'ltr' | 'rtl'
  t: TFunction
  addResourceBundle(
    lng: string,
    ns: string,
    resources: any,
    deep: boolean,
    overwrite: boolean
  ): void
}
/**
 * I18n内核
 * 安全校验
 */
export default class I18next {
  instance: i18n
  config: InitOptions & {
    lng?: string
    fallbackLng?: string[]
    [key: string]: any
  }
  plugins: IntlModule[]
  languages: FallbackLng
  init: boolean
  userLng: string | null

  private _waitingToAddResourceBundle: [string, string, any, boolean, boolean][] = []

  _handlePlugins(plugins: IntlModule[]) {
    this.plugins = Array.isArray(plugins) ? plugins.concat(defaultPlugins) : defaultPlugins
  }

  _handleConfigs(config: InitOptions) {
    this.userLng = config?.lng || null // 用户自己设定的 lng

    this.config = Object.assign({}, defaultConfig, config || {})
  }

  constructor(config?: InitOptions, plugins?: IntlModule[]) {
    this._handlePlugins(plugins)
    this._handleConfigs(config)
    this.instance = i18next.createInstance()

    this.instance.isInitialized = false
  }
  get language() {
    return (this.instance || {}).language
  }
  createInstance(): Promise<{ err: Error; t: TFunction }> {
    return new Promise((resolve, reject) => {
      // fixed: 非浏览器环境不支持incontext
      // fixed: Cocos 有 window 但不使用 in-context
      // feat: 支持手动关闭inContext
      if (typeof window !== 'undefined' && !(window as any).CocosEngine && this.config.inContext) {
        this.config.postProcess = [].concat(['inContext'], this.config.postProcess || [])
      }

      this.plugins.map(p => {
        if (p.name === 'IES_INTL_ICU_PLUGIN') {
          ;(p as any).staticInit(this.instance)
        }
        this.instance.use(p as Module)
      })

      const config = this.config

      this.config.formats = Object.assign({}, this.config.formats, constants.defaultFormats)
      const formatLng = formatLang(
        config.lng,
        this.plugins.filter(isTypes(types.LAMGUAGE_TRANSFORMER))
      )
      this.instance.init(
        {
          ...config,
          lng: formatLng,
          i18nFormat: {
            formats: this.config.formats
          }
        },
        (err, t) => {
          // 初始化好了

          try {
            // 把等待添加的东西都加进去
            for (const item of this._waitingToAddResourceBundle) {
              this.instance.addResourceBundle(...item)
            }
            this._waitingToAddResourceBundle = []
            // eslint-disable-next-line no-empty
          } catch (err) {}

          if (!err) {
            this._updateLanguages()
            resolve({
              t,
              err
            })
          }
          this.init = true
          reject({
            t,
            err
          })
        }
      )
    })
  }
  _updateLanguages() {
    this.languages = this.instance
      ? Array.from(new Set([this.instance.language, ...this.instance.languages]))
      : null
  }
  getLanguages() {
    return this.languages
  }
  changeLanguage(lng: string, callback?: Callback) {
    this.config.lng = lng
    this.instance.changeLanguage(lng, (err, t) => {
      if (!err) {
        this._updateLanguages()
      }
      callback && callback(err, t)
    })
  }
  changeLanguageWithPromise(lng: string) {
    return new Promise((resolve, reject) => {
      this.config.lng = lng
      this.instance.changeLanguage(lng, (err, t) => {
        if (err) {
          reject({
            err,
            t
          })
        }
        this._updateLanguages()
        resolve({ err, t })
      })
    })
  }
  getDir(lng: string) {
    return this.instance.dir(lng)
  }
  t = <
    TResult extends TFunctionResult = string,
    TKeys extends TFunctionKeys = string,
    // eslint-disable-next-line @typescript-eslint/ban-types
    TInterpolationMap extends object = StringMap
  >(
    keys: TKeys | TKeys[],
    options?: TOptions<TInterpolationMap> | string,
    fallbackText?: string
  ): TResult => {
    const separatorMock = Array.isArray(keys)
      ? Array.from(keys)
          .map(() => ' ')
          .join('')
      : Array(keys.length).fill(' ')

    // fixed: 去除默认lngs，有lngs i18next就会忽略lng
    const opt: Record<string, any> = Object.assign(
      { keySeparator: separatorMock, nsSeparator: separatorMock },
      options
    )

    if ('thirdParamFallback' in this.config && this.config.thirdParamFallback) {
      opt.thirdParamFallback = true
    }
    return this.instance.t(keys, fallbackText, opt)
  }
  addResourceBundle(lng: string, ns: string, resources: any, deep?: boolean, overwrite?: boolean) {
    if (this.instance.isInitialized) {
      return this.instance.addResourceBundle(lng, ns, resources, deep, overwrite)
    }
    // 还没初始化好
    this._waitingToAddResourceBundle.push([lng, ns, resources, !!deep, !!overwrite])
    return this.instance
  }
}
