import {ContextType, createContext, Component, useContext} from 'react'
import {isUndefined} from 'lodash-es'
import {Json, safeInvoke} from '@befe/brick-utils'

import {ComponentLocale, ComponentLocaleDict, getI18nSingleton, I18n, LocaleDict, LocaleTextReplacement} from '../i18n'
import {ThemeConfig, ThemeConfigWithDefaults, getThemeConfigSingleton} from '../theme'

// @todo 这个应该来自 sack 中规范化的 backend-frontend API type
interface RespJson {
    status?: 'ok' | 'fail'
    message?: string
}

export interface ConfigContextValue {

    /**
     * 主题相关基础配置
     */
    theme?: ThemeConfig

    /**
     * 获取本地化文本
     */
    getLocaleText?: I18n['getLocaleText']

    /**
     * request 响应成功的判断函数
     */
    isResponseSuccess?: (respJson: Json | undefined, respText: string) => boolean

    /**
     * request 响应的消息提取器
     */
    getResponseMessage?: (respJson: Json | undefined, respText: string) => string

    /**
     * 字数计数函数
     */
    textCounter?: (text: string) => number

    /**
     * @private
     * 是否为根 context
     * - 默认值 true，经过 ConfigProvider 之后恒为 false，亦即只有第一个 ConfigProvider 的 context.isRoot 为 true
     * - 只有根 context 可以更新 themeSingleton
     */
    isRoot?: boolean

    /**
     * 包裹 wrap 的 类名，用于 "添加 scope 层次以增强特指度" 的样式隔离方案
     *
     * 相对于不提供的，如果提供了 wrapClassName  e.g. wrapClassName="wrap-name"
     * - children 将被 wrap 到一个 div.wrap-name 中
     * - mount 到 body 的 popup portal 的 children 会被到一个 div.wrap-name 中
     *
     * 需要搭配利用 `@include meta.load-css()` 将样式 wrap 到相应的 .wrap-class {} 中进行 sass 编译后的样式
     */
    wrapClassName?: string | null
}

/**
 * 多 root 场景下，需要另行传递的无法在 root 间自行取值的， config context
 *
 * 如应用样式隔离方案时， confirm(), alert(), toast(), message(), notification() 等所需要的 wrapClassName
 */
export type ConfigContextValueOffBoard = Pick<ConfigContextValue, 'wrapClassName'>

type ConfigContextValueWithDefaults = Required<ConfigContextValue> & {
    theme: ThemeConfigWithDefaults
}

function parseHTMLMessage(respText: string) {
    const domParser = new DOMParser()
    const doc = domParser.parseFromString(respText, 'text/html')
    try {
        return doc.body.innerText.trim()
    } catch {
        // textContent for test
        // - https://github.com/jsdom/jsdom/issues/1245
        // - https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
        return doc.body.textContent || ''
    }
}
const i18n = getI18nSingleton()
const theme = getThemeConfigSingleton()

export const defaultConfig: ConfigContextValueWithDefaults = {
    theme: theme.config,
    getLocaleText: (...args) => i18n.getLocaleText(...args),
    isResponseSuccess: (respJson) => {
        return !!(respJson && (respJson as RespJson).status === 'ok')
    },
    getResponseMessage: (respJson, respText) => {
        return (respJson ? (respJson as RespJson).message : parseHTMLMessage(respText)) || ''
    },
    textCounter: (text = '') => (text || '').length,
    isRoot: true,
    wrapClassName: null,
}
export const ConfigContext = createContext<ConfigContextValue>(defaultConfig)
export const ConfigConsumer = ConfigContext.Consumer

export function getValueFromContextTheme<TN extends keyof ThemeConfigWithDefaults>(
    context: ContextType<typeof ConfigContext>,
    themePropName: TN
) {
    const value = (context.theme as ThemeConfigWithDefaults)?.[themePropName]
    return isUndefined(value) ? defaultConfig.theme[themePropName] : value
}

export function getIconSetFromContextTheme(context: ContextType<typeof ConfigContext>) {
    return {
        info: getValueFromContextTheme(context, 'alertIconInfo'),
        success: getValueFromContextTheme(context, 'alertIconSuccess'),
        warning: getValueFromContextTheme(context, 'alertIconWarning'),
        error: getValueFromContextTheme(context, 'alertIconError'),
    }
}

export function getDefaultValueUsingContextTheme<
    Props,
    N extends keyof Props,
    TN extends keyof ThemeConfigWithDefaults
>(
    componentInstance: Component<Props>,
    propName: N,
    themePropName: TN
) {
    const propValue = componentInstance.props[propName]
    if (!isUndefined(propValue)) {
        return propValue as Required<Props>[N]
    }

    return getValueFromContextTheme(componentInstance.context as ContextType<typeof ConfigContext>, themePropName)
}

export function useLocale<T extends LocaleDict = ComponentLocaleDict>(localeDict: ComponentLocale<T>) {
    const configContext = useContext(ConfigContext)
    return {
        getLocaleText: (key: string, ...args: LocaleTextReplacement[]) => {
            return safeInvoke(configContext.getLocaleText, localeDict, key, ...args);
        },
    }
}
