<!--
  JsonFormDialog - JSON 多语言配置编辑弹窗

  功能：
    - 将 JSON 对象按语言 key（如 ZH、EN）拆分为 tab 页签，递归渲染为可编辑表单
    - 支持 string / number / boolean / object / array（含对象数组、字符串数组）类型自动识别与编辑
    - EN tab 下支持"一键翻译"：将 ZH 内容复制到 EN 并批量调用翻译接口（仅翻译含汉字的字段）
    - EN tab 下每个文本框支持单字段翻译按钮（当对应 ZH 值含中文时显示）
    - 翻译请求分批执行（BATCH_SIZE / BATCH_DELAY），防止接口过载

  Props：
    - show: boolean          控制弹窗显示
    - title: string          弹窗标题，默认 '配置编辑'
    - data: Record<string, any>  JSON 数据，顶层 key 作为语言 tab
    - fieldMap: Record<string, string>  字段名映射，用于显示中文 label

  Events：
    - close                  关闭弹窗
    - submit(data)           保存，返回编辑后的完整 JSON 数据
-->
<template>
    <el-dialog :model-value="show" width="900" :fullscreen="isFullscreen" :close-on-click-modal="false" @close="close"
        :show-close="false">
        <template #header>
            <div class="jfd-header">
                <span class="jfd-title">{{ title }}</span>
                <div class="jfd-header-actions">
                    <el-button link circle @click="isFullscreen = !isFullscreen"
                        :title="isFullscreen ? '退出全屏' : '全屏'">
                        <el-icon>
                            <CopyDocument v-if="isFullscreen" />
                            <FullScreen v-else />
                        </el-icon>
                    </el-button>
                    <el-button link circle @click="close" title="关闭">
                        <el-icon size="18">
                            <Close />
                        </el-icon>
                    </el-button>
                </div>
            </div>
        </template>

        <el-tabs v-if="langKeys.length" v-model="activeTab" type="border-card"
            v-loading="translating" element-loading-text="正在翻译中..."
            element-loading-background="rgba(255,255,255,0.8)">
            <el-tab-pane v-for="lang in langKeys" :key="lang" :label="lang" :name="lang">
                <el-scrollbar max-height="60vh">
                    <div class="jfd-content">
                        <NodeEditor v-for="sKey in getSectionKeys(lang)" :key="sKey" :parent="formData[lang]"
                            :field-key="sKey" :field-map="fieldMap" :depth="0" :lang="lang"
                            :zh-parent="lang === 'EN' ? formData['ZH'] : undefined" />
                    </div>
                </el-scrollbar>
            </el-tab-pane>
        </el-tabs>
        <el-empty v-else description="无数据" />

        <template #footer>
            <el-button v-if="activeTab === 'EN'" type="success" title="不覆盖翻译后的数据，从ZH补全"
                @click="handleCompleteData">补全数据</el-button>
            <el-button v-if="activeTab === 'EN'" type="warning" :loading="translating"
                @click="handleTranslateAll">一键翻译</el-button>
            <el-button @click="close">取消</el-button>
            <el-button type="primary" @click="submit">保存</el-button>
        </template>
    </el-dialog>
</template>

<script setup lang="ts">
import { ref, computed, watch, defineComponent, h, type PropType, type VNode, type Component } from 'vue'
import { Close, FullScreen, CopyDocument, Plus, Delete, Promotion, UploadFilled } from '@element-plus/icons-vue'
import {
    ElFormItem, ElInput, ElInputNumber, ElSwitch, ElButton,
    ElCollapse, ElCollapseItem, ElIcon, ElMessage
} from 'element-plus'
import { translateApi } from 'src/api/common'
import { translateRichText } from 'src/tools'
import UploadInput from './UploadInput.vue'
import WangEditor from 'lib/components/WangEditor.vue'
const props = withDefaults(defineProps<{
    show: boolean
    title?: string
    data: Record<string, any>
    fieldMap?: Record<string, string>
    uploadFile: (file: FormData) => Promise<any>
}>(), {
    show: false,
    title: '配置编辑',
    data: () => ({}),
    fieldMap: () => ({}),
    uploadFile: () => Promise.resolve({}),
})

const emit = defineEmits(['close', 'submit'])

const isFullscreen = ref(false)
const formData = ref<Record<string, any>>({})
const activeTab = ref('')
const translating = ref(false)
const translatingPath = ref('')

const langKeys = computed(() => Object.keys(formData.value))

function hasChinese(str: string): boolean {
    return /[\u4e00-\u9fa5]/.test(str)
}

function hasHtmlTag(str: string): boolean {
    return /<\/[a-zA-Z][\w:-]*\s*>/i.test(str)
}

async function translateTexts(texts: string[]): Promise<string[]> {
    const results = await Promise.allSettled(
        texts.map(text => translateApi.translate({ text, from: 'ZH', to: 'EN' }))
    )
    return results.map(r => r.status === 'fulfilled' ? parseTranslateResult(r.value) : '')
}

function parseTranslateResult(res: any): string {
    return res?.data?.text ?? res?.data ?? res?.result ?? (typeof res === 'string' ? res : '')
}

function collectChineseStrings(obj: any, result: { parent: any; key: string | number }[]) {
    if (obj === null || obj === undefined) return
    if (Array.isArray(obj)) {
        obj.forEach((item, i) => {
            if (typeof item === 'string' && hasChinese(item)) {
                result.push({ parent: obj, key: i })
            } else if (typeof item === 'object') {
                collectChineseStrings(item, result)
            }
        })
    } else if (typeof obj === 'object') {
        for (const k of Object.keys(obj)) {
            const val = obj[k]
            if (typeof val === 'string' && hasChinese(val) && !k.startsWith('untranslate_')) {
                result.push({ parent: obj, key: k })
            } else if (typeof val === 'object') {
                collectChineseStrings(val, result)
            }
        }
    }
}

function getSectionKeys(lang: string) {
    const langData = formData.value[lang]
    return langData && typeof langData === 'object' ? Object.keys(langData) : []
}

const itemTemplates: Record<string, any> = {}

function collectItemTemplates(data: any) {
    if (data === null || data === undefined) return
    if (Array.isArray(data)) {
        if (data.length > 0 && typeof data[0] === 'object' && !Array.isArray(data[0])) {
            for (const key of Object.keys(data[0])) {
                collectItemTemplates(data[0][key])
            }
        }
        data.forEach(item => collectItemTemplates(item))
    } else if (typeof data === 'object') {
        for (const key of Object.keys(data)) {
            const val = data[key]
            if (Array.isArray(val) && val.length > 0 && typeof val[0] === 'object' && !Array.isArray(val[0])) {
                if (!itemTemplates[key]) {
                    itemTemplates[key] = createEmpty(val[0])
                }
            }
            collectItemTemplates(val)
        }
    }
}

watch(() => props.show, (val) => {
    if (val && props.data) {
        formData.value = JSON.parse(JSON.stringify(props.data))
        Object.keys(itemTemplates).forEach(k => delete itemTemplates[k])
        const firstLang = langKeys.value[0]
        if (firstLang) collectItemTemplates(formData.value[firstLang])
        activeTab.value = firstLang || ''
    }
}, { immediate: true })

function close() {
    isFullscreen.value = false
    emit('close')
}

function submit() {
    emit('submit', JSON.parse(JSON.stringify(formData.value)))
}

const BATCH_SIZE = 3
const BATCH_DELAY = 300

function delay(ms: number) {
    return new Promise(resolve => setTimeout(resolve, ms))
}

function handleCompleteData() {
    if (!formData.value.ZH) return
    if (!formData.value.EN) {
        formData.value.EN = JSON.parse(JSON.stringify(formData.value.ZH))
        ElMessage.success('补全完成')
        return
    }
    completeFromSource(formData.value.EN, formData.value.ZH)
    ElMessage.success('补全完成')
}

function completeFromSource(target: any, source: any) {
    if (source === null || source === undefined) return

    if (Array.isArray(source) && Array.isArray(target)) {
        for (let i = target.length; i < source.length; i++) {
            target.push(JSON.parse(JSON.stringify(source[i])))
        }
        for (let i = 0; i < Math.min(target.length, source.length); i++) {
            if (typeof target[i] === 'object' && target[i] !== null &&
                typeof source[i] === 'object' && source[i] !== null) {
                completeFromSource(target[i], source[i])
            } else if (typeof target[i] === 'string' && target[i] === '' && source[i] !== '') {
                target[i] = JSON.parse(JSON.stringify(source[i]))
            }
        }
    } else if (typeof source === 'object' && !Array.isArray(source)) {
        for (const key of Object.keys(source)) {
            if (!(key in target) || target[key] === undefined || target[key] === null) {
                target[key] = JSON.parse(JSON.stringify(source[key]))
            } else if (typeof target[key] === 'string' && target[key] === '') {
                target[key] = JSON.parse(JSON.stringify(source[key]))
            } else if (typeof target[key] === 'object' && target[key] !== null &&
                       typeof source[key] === 'object' && source[key] !== null) {
                completeFromSource(target[key], source[key])
            }
        }
    }
}

async function handleTranslateAll() {
    if (!formData.value.ZH || translating.value) return
    formData.value.EN = JSON.parse(JSON.stringify(formData.value.ZH))
    const items: { parent: any; key: string | number }[] = []
    collectChineseStrings(formData.value.EN, items)
    if (!items.length) {
        ElMessage.info('没有需要翻译的中文内容')
        return
    }
    translating.value = true
    let failCount = 0
    try {
        for (let i = 0; i < items.length; i += BATCH_SIZE) {
            const batch = items.slice(i, i + BATCH_SIZE)
            const results = await Promise.allSettled(
                batch.map(async ({ parent, key }) => {
                    const text = String(parent[key]).trim()
                    if (hasHtmlTag(text)) {
                        const translated = await translateRichText(text, translateTexts)
                        return { parent, key, translated }
                    }
                    const res = await translateApi.translate({ text, from: 'ZH', to: 'EN' })
                    return { parent, key, translated: parseTranslateResult(res) }
                })
            )
            for (const result of results) {
                if (result.status === 'fulfilled') {
                    const { parent, key, translated } = result.value
                    if (translated) parent[key] = translated
                } else {
                    failCount++
                }
            }
            if (i + BATCH_SIZE < items.length) await delay(BATCH_DELAY)
        }
        if (failCount > 0) {
            ElMessage.warning(`${failCount} 个字段翻译失败`)
        } else {
            ElMessage.success('翻译完成')
        }
    } catch (e) {
        console.error('Translate failed:', e)
        ElMessage.error('翻译失败')
    } finally {
        translating.value = false
    }
}

async function handleTranslateField(parent: any, key: string | number, zhParent: any) {
    if (!zhParent || translatingPath.value) return
    const zhText = String(zhParent[key] ?? '').trim()
    if (!hasChinese(zhText)) return
    translatingPath.value = `${key}`
    try {
        let translated: string
        if (hasHtmlTag(zhText)) {
            translated = await translateRichText(zhText, translateTexts)
        } else {
            const res = await translateApi.translate({ text: zhText, from: 'ZH', to: 'EN' })
            translated = parseTranslateResult(res)
        }
        if (translated) parent[key] = translated
    } catch (e) {
        console.error('Translate failed:', e)
        ElMessage.error('翻译失败')
    } finally {
        translatingPath.value = ''
    }
}

function createEmpty(template: any): any {
    if (typeof template === 'string') return ''
    if (typeof template === 'number') return 0
    if (typeof template === 'boolean') return false
    if (Array.isArray(template)) return []
    if (typeof template === 'object' && template !== null) {
        const obj: any = {}
        for (const k of Object.keys(template)) obj[k] = createEmpty(template[k])
        return obj
    }
    return ''
}

const NodeEditor: Component = defineComponent({
    name: 'NodeEditor',
    props: {
        parent: { type: Object as PropType<any>, required: true },
        fieldKey: { type: [String, Number] as PropType<string | number>, required: true },
        fieldMap: { type: Object as PropType<Record<string, string>>, default: () => ({}) },
        depth: { type: Number, default: 0 },
        lang: { type: String, default: '' },
        zhParent: { type: Object as PropType<any>, default: undefined },
    },
    setup(p: any) {
        return () => {
            const value = p.parent[p.fieldKey]
            const key = p.fieldKey
            const fm = p.fieldMap
            const depth = p.depth
            const label = typeof key === 'string' ? (fm[key] || key) : `#${Number(key) + 1}`

            if (value === null || value === undefined) return null

            if (typeof value === 'boolean') {
                return h(ElFormItem, { label, class: 'jfd-form-item' }, () =>
                    h(ElSwitch, {
                        modelValue: value,
                        'onUpdate:modelValue': (v: any) => { p.parent[key] = v },
                    })
                )
            }

            if (typeof value === 'number') {
                return h(ElFormItem, { label, class: 'jfd-form-item' }, () =>
                    h(ElInputNumber, {
                        modelValue: value,
                        'onUpdate:modelValue': (v: number | undefined) => { p.parent[key] = v ?? 0 },
                        controlsPosition: 'right',
                    })
                )
            }

            if (typeof value === 'string') {
                // 含任意 HTML 闭合标签（如 </p>、</div>）时用富文本编辑器
                const hasHtmlClosingTag = /<\/[a-zA-Z][\w:-]*\s*>/i.test(value)

                // const isLong = value.length > 50
                 
                // const needUpload = value.startsWith('http')
                const isImage = value.includes('.jpg') || value.includes('.png') || value.includes('.jpeg') || value.includes('.gif') || value.includes('.bmp') || value.includes('.webp')
                const isVideo = value.includes('.mp4') || value.includes('.avi') || value.includes('.mov') || value.includes('.wmv') || value.includes('.flv') || value.includes('.mkv')
                const isFile = value.includes('.pdf') || value.includes('.doc') || value.includes('.docx') || value.includes('.xls') || value.includes('.xlsx') || value.includes('.ppt') || value.includes('.pptx')

                let uploadNode = null;
                if ((isFile || isImage || isVideo)) {
                    uploadNode = h(UploadInput, {
                        value: [{url: value}],
                        type: isImage ? 'image' : isVideo ? 'video' : 'file',
                        uploadFile: props.uploadFile,
                        hideUploadButton: true,
                        transport: `#jfd-${key}`,
                        onUpdateValue: (v: any) => { 
                            if(v.length > 0) {
                                p.parent[key] = v[0].url || ''
                            }
                         },
                    })
                }
                const inputNode = [
                h(ElInput, {
                    modelValue: value,
                    ...({ type: 'textarea', autosize: { minRows: 1, maxRows: 6 } }),
                    'onUpdate:modelValue': (v: string) => { p.parent[key] = v },
                    style: { width: '90%', marginRight: '5px' },
                }),
                p.parent[key] === '' && h(ElButton, {
                    size: 'small', type: 'primary',
                    title: '切换为图片上传框',
                    onClick: () => p.parent[key] = '1.png',
                }, () => h(ElIcon, { size: 14 }, () => h(UploadFilled))),
                ]

                const wangEditor = h(WangEditor, {
                    content: value,
                    showSaveButton: false,
                    transport: `#jfd-${key}`,
                    'onUpdate:content': (v: any) => {
                        if(v.length > 0) {
                            p.parent[key] = v || ''
                        }
                    },
                })

                const formItemNode = [
                    uploadNode || (hasHtmlClosingTag ? wangEditor : inputNode),
                ]


                const zhVal = p.zhParent?.[key]
                const showTranslateBtn = p.lang === 'EN' && typeof zhVal === 'string' && hasChinese(zhVal)
                && !key.startsWith('untranslate_')
                if (showTranslateBtn) {
                    return h(ElFormItem, { label, class: 'jfd-form-item' }, () =>
                        h('div', { class: 'jfd-field-with-translate' }, [
                            formItemNode,
                            h(ElButton, {
                                link: true, size: 'small', class: 'jfd-translate-icon',
                                loading: translatingPath.value === `${key}`,
                                onClick: () => handleTranslateField(p.parent, key, p.zhParent),
                            }, () => h(ElIcon, { color: '#409eff' }, () => h(Promotion)))
                        ])
                    )
                }
                return h(ElFormItem, { label, class: 'jfd-form-item' }, () => formItemNode)
            }

            if (Array.isArray(value)) {
                const keyStr = String(key)
                const hasLiveObj = value.length > 0 && typeof value[0] === 'object' && !Array.isArray(value[0])
                const savedTemplate = itemTemplates[keyStr]
                const isObjArr = hasLiveObj || !!savedTemplate

                if (!isObjArr) {
                    return h('div', { class: 'jfd-array-group' }, [
                        h('div', { class: 'jfd-group-header' }, [
                            h('span', { class: 'jfd-group-title' }, label),
                            h(ElButton, {
                                size: 'small', type: 'primary', link: true,
                                onClick: () => value.push(''),
                            }, () => [h(ElIcon, { size: 14 }, () => h(Plus)), ' 添加'])
                        ]),
                        ...value.map((_: any, i: number) => {
                            const zhArr = p.zhParent?.[key]
                            const zhStr = Array.isArray(zhArr) && typeof zhArr[i] === 'string' ? zhArr[i] : undefined
                            const showBtn = p.lang === 'EN' && zhStr && hasChinese(zhStr)

                            const isImage = value[i].includes('.jpg') || value[i].includes('.png') || value[i].includes('.jpeg') || value[i].includes('.gif') || value[i].includes('.bmp') || value[i].includes('.webp')
                            const isVideo = value[i].includes('.mp4') || value[i].includes('.avi') || value[i].includes('.mov') || value[i].includes('.wmv') || value[i].includes('.flv') || value[i].includes('.mkv')
                            const isFile = value[i].includes('.pdf') || value[i].includes('.doc') || value[i].includes('.docx') || value[i].includes('.xls') || value[i].includes('.xlsx') || value[i].includes('.ppt') || value[i].includes('.pptx')
                            let uploadNode = null;
                            if ((isFile || isImage || isVideo)) {
                                uploadNode = h(UploadInput, {
                                    value: [{url: value[i], name: value[i]}],
                                    type: isImage ? 'image' : isVideo ? 'video' : 'file',
                                    uploadFile: props.uploadFile,
                                    hideUploadButton: true,
                                    transport: `#jfd-${key}`,
                                    onUpdateValue: (v: any) => { 
                                        if(v.length > 0) {
                                            value[i] = v[0].url || ''
                                        }
                                    },
                                })
                            }
                            return h('div', { class: 'jfd-string-item', key: i }, [
                                uploadNode || h(ElInput, {
                                    modelValue: value[i],
                                    'onUpdate:modelValue': (v: string) => { value[i] = v },
                                }),
                                showBtn ? h(ElButton, {
                                    link: true, size: 'small', class: 'jfd-translate-icon',
                                    loading: translatingPath.value === `${key}-${i}`,
                                    onClick: async () => {
                                        translatingPath.value = `${key}-${i}`
                                        try {
                                            const res = await translateApi.translate({ text: zhStr!, from: 'ZH', to: 'EN' })
                                            const translated = parseTranslateResult(res)
                                            if (translated) value[i] = translated
                                        } catch {
                                            ElMessage.error('翻译失败')
                                        } finally {
                                            translatingPath.value = ''
                                        }
                                    },
                                }, () => h(ElIcon, { color: '#409eff' }, () => h(Promotion))) : null,
                                h(ElButton, {
                                    size: 'small', type: 'danger', link: true,
                                    onClick: () => value.splice(i, 1),
                                }, () => h(ElIcon, { size: 14 }, () => h(Delete))),
                            ])
                        })
                    ])
                }

                const makeNewItem = () => {
                    const tpl = hasLiveObj ? value[0] : savedTemplate
                    return JSON.parse(JSON.stringify(tpl ? createEmpty(tpl) : {}))
                }

                return h('div', { class: 'jfd-array-group' }, [
                    h('div', { class: 'jfd-group-header' }, [
                        h('span', { class: 'jfd-group-title' }, label),
                        h(ElButton, {
                            size: 'small', type: 'primary', link: true,
                            onClick: () => value.push(makeNewItem()),
                        }, () => [h(ElIcon, { size: 14 }, () => h(Plus)), ' 添加'])
                    ]),
                    h(ElCollapse, { class: 'jfd-obj-array' }, () =>
                        value.map((item: any, i: number) => {
                            const v = Object.values(item)
                            const tag = v[0]
                            return h(ElCollapseItem, {
                                title: `${label} #${i + 1}${tag ? ' - ' + tag : ''}`,
                                name: `${String(key)}-${i}`,
                                key: i,
                            }, () => [
                                ...Object.keys(item).map(k => {
                                    const zhArr = p.zhParent?.[key]
                                    const zhItem = Array.isArray(zhArr) ? zhArr[i] : undefined
                                    return h(NodeEditor, { parent: item, fieldKey: k, fieldMap: fm, depth: depth + 1, key: k, lang: p.lang, zhParent: zhItem })
                                }),
                                h('div', { class: 'jfd-item-actions' },
                                    h(ElButton, {
                                        size: 'small', type: 'danger',
                                        onClick: () => value.splice(i, 1),
                                    }, () => [h(ElIcon, { size: 14 }, () => h(Delete)), ' 删除此项'])
                                )
                            ])
                        })
                    )
                ])
            }

            if (typeof value === 'object') {
                const zhObj = p.zhParent?.[key]
                const children = Object.keys(value).map(k =>
                    h(NodeEditor, { parent: value, fieldKey: k, fieldMap: fm, depth: depth + 1, key: k, lang: p.lang, zhParent: zhObj })
                )

                if (depth === 0) {
                    return h('div', { class: 'jfd-section' }, [
                        h('div', { class: 'jfd-section-header' }, label),
                        h('div', { class: 'jfd-section-body' }, children)
                    ])
                }

                return h('div', { class: 'jfd-nested' }, [
                    h('div', { class: 'jfd-nested-title' }, label),
                    ...children
                ])
            }

            return null
        }
    }
})
</script>

<style lang="scss">
.jfd-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;

    .jfd-title {
        font-size: 18px;
        font-weight: 500;
    }

    .jfd-header-actions {
        display: flex;
        gap: 4px;
    }
}

.jfd-content {
    padding: 12px;
}

.jfd-form-item {
    margin-bottom: 16px;
}

.jfd-section {
    margin-bottom: 16px;
    border: 1px solid var(--el-border-color);
    border-radius: 6px;
    overflow: hidden;
}

.jfd-section-header {
    padding: 10px 16px;
    font-weight: 600;
    font-size: 15px;
    background-color: var(--el-fill-color-light);
    border-bottom: 1px solid var(--el-border-color);
}

.jfd-section-body {
    padding: 16px;
}

.jfd-array-group {
    margin-bottom: 12px;
}

.jfd-group-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;
    
}

.jfd-group-title {
    font-weight: 500;
    font-size: 14px;
    color: var(--el-text-color-primary);
}

.jfd-string-item {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-bottom: 8px;
}

.jfd-item-actions {
    text-align: right;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px dashed var(--el-border-color-lighter);
}

.jfd-nested {
    margin-left: 12px;
    padding-left: 12px;
    border-left: 2px solid var(--el-border-color);
    margin-bottom: 12px;
}

.jfd-nested-title {
    font-weight: 500;
    font-size: 13px;
    color: var(--el-text-color-regular);
    margin-bottom: 8px;
}

.jfd-field-with-translate {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    width: 100%;

    .el-input,
    .el-textarea {
        flex: 1;
    }

    .jfd-translate-icon {
        flex-shrink: 0;
        padding: 8px 0;
    }
}

.jfd-obj-array .el-collapse-item__header {
    font-size: 13px;
}

</style>
