<template>
    <section class="mapping-panel">
        <div class="panel-header">
            <div>
                <h2 class="panel-title">映射结果</h2>
                <p class="panel-description">展示和导出JSON与XML的映射结果，以及JSON的映射序列配置。</p>
            </div>
            <div class="panel-actions">
                <el-button
                    type="primary"
                    :icon="Connection"
                    :loading="isGeneratingXml"
                    :disabled="!canGenerateXmlMapping"
                    @click="emit('generate-xml-mapping')"
                >
                    生成XML映射
                </el-button>
                <div class="export-actions">
                    <el-button
                        type="primary"
                        plain
                        :icon="Download"
                        :disabled="!canExportActiveMapping"
                        @click="emit('export-mapping', activeExportType)"
                    >
                        {{ exportButtonText }}
                    </el-button>
                    <el-dropdown trigger="click" :disabled="!canExportAnyMapping" @command="handleExportCommand">
                        <el-button
                            type="primary"
                            plain
                            class="export-menu-button"
                            :icon="ArrowDown"
                            :disabled="!canExportAnyMapping"
                        />
                        <template #dropdown>
                            <el-dropdown-menu>
                                <el-dropdown-item command="json" :disabled="!canExportJsonMapping">
                                    导出JSON映射
                                </el-dropdown-item>
                                <el-dropdown-item command="xml" :disabled="!canExportXmlMapping">
                                    导出XML映射
                                </el-dropdown-item>
                            </el-dropdown-menu>
                        </template>
                    </el-dropdown>
                </div>
                <el-tag :type="responseStatusTagType" effect="light">{{ responseStatusText }}</el-tag>
            </div>
        </div>

        <div class="panel-content panel-content--fixed">
            <div class="panel-section result-card grow-card preview-tab-section">
                <el-tabs v-model="activeTabProxy" class="preview-tabs">
                    <el-tab-pane label="JSON映射" name="json">
                        <div class="mapping-json-scroll">
                            <JsonMappingTree
                                v-if="mappingJsonPreview"
                                :source="mappingJsonPreview"
                                :meta-text="mappingMetaText"
                            >
                                <template #actions>
                                    <el-button
                                        type="primary"
                                        plain
                                        size="small"
                                        :icon="Setting"
                                        :disabled="!mappingJsonPreview"
                                        @click="openSequenceDialog"
                                    >
                                        序列配置
                                    </el-button>
                                    <el-button
                                        type="primary"
                                        plain
                                        size="small"
                                        :icon="Warning"
                                        @click="problemDialogVisible = true"
                                    >
                                        {{ problemButtonText }}
                                    </el-button>
                                </template>
                            </JsonMappingTree>
                            <el-empty v-else description="当前返回未包含 mappingJson" />
                        </div>
                    </el-tab-pane>
                    <el-tab-pane v-if="showXmlMappingTab" label="XML映射" name="xml">
                        <div class="mapping-json-scroll">
                            <div class="match-result-actions">
                                <el-button
                                    type="primary"
                                    plain
                                    :icon="Document"
                                    :disabled="!methodDescribe"
                                    @click="matchResultDialogVisible = true"
                                >
                                    匹配结果展示
                                </el-button>
                            </div>
                            <div v-if="xmlMappingPreview" class="xml-file-viewer">
                                <div class="xml-file-header">
                                    <span class="xml-file-name">XML 文件</span>
                                    <span class="xml-file-meta">{{ xmlMetaText }}</span>
                                </div>
                                <pre class="xml-file-content">{{ xmlMappingPreview }}</pre>
                            </div>
                            <el-empty v-else :description="xmlEmptyText" />
                        </div>
                    </el-tab-pane>
                </el-tabs>
            </div>
        </div>

        <el-dialog
            v-model="problemDialogVisible"
            title="问题列表"
            width="720px"
            destroy-on-close
            top="8vh"
            class="mapping-problem-dialog"
        >
            <div v-if="problemList.length" class="problem-dialog-list">
                <div v-for="(problem, index) in problemList" :key="`${index}-${problem}`" class="problem-item">
                    <span class="problem-index">{{ index + 1 }}</span>
                    <span class="problem-text">{{ problem }}</span>
                </div>
            </div>
            <el-empty v-else :description="problemEmptyText" />
        </el-dialog>

        <el-dialog
            v-model="matchResultDialogVisible"
            title="匹配结果展示"
            width="640px"
            destroy-on-close
            top="12vh"
        >
            <div class="match-result-detail">
                {{ methodDescribe || '当前接口返回未包含 methodDescribe' }}
            </div>
        </el-dialog>

        <el-dialog
            v-model="sequenceDialogVisible"
            title="序列配置"
            width="920px"
            destroy-on-close
            top="8vh"
            class="sequence-config-dialog"
        >
            <template v-if="sequenceConfigRows.length">
                <div class="dialog-search-bar">
                    <el-input
                        v-model="sequenceSearchKeyword"
                        :prefix-icon="Search"
                        clearable
                        placeholder="按顶层、类型、上层描述、name 或路径检索"
                    />
                    <span class="dialog-search-count">
                        {{ filteredSequenceRows.length }} / {{ sequenceConfigRows.length }}
                    </span>
                </div>

                <div v-if="sequenceConfigGroups.length" class="sequence-config-list">
                    <section v-for="group in sequenceConfigGroups" :key="group.topKey" class="sequence-top-group">
                        <div class="sequence-top-header">
                            <div>
                                <h3 class="sequence-top-title">{{ group.topDesc || group.topKey }}</h3>
                                <p class="sequence-top-key">{{ group.topKey }}</p>
                            </div>
                            <el-tag type="info" effect="light">{{ group.rowCount }} 项</el-tag>
                        </div>

                        <div class="sequence-type-list">
                            <article
                                v-for="typeGroup in group.typeGroups"
                                :key="typeGroup.typeName"
                                class="sequence-type-card"
                            >
                                <div class="sequence-type-header">
                                    <div class="sequence-type-title">{{ typeGroup.typeName }}</div>
                                    <el-tag type="primary" effect="plain" size="small">{{ typeGroup.rows.length }} 项</el-tag>
                                </div>

                                <div v-for="row in typeGroup.rows" :key="row.id" class="sequence-config-item">
                                    <div class="sequence-config-info">
                                        <div class="sequence-config-title">{{ row.parentDesc }}</div>
                                        <div class="sequence-config-subtitle">
                                            {{ row.name }}
                                            <span class="sequence-config-path">{{ row.pathText }}</span>
                                        </div>
                                    </div>
                                    <el-form label-position="top" size="small" class="sequence-config-form">
                                        <el-form-item label="start">
                                            <el-input v-model="row.start" />
                                        </el-form-item>
                                        <el-form-item label="end">
                                            <el-input v-model="row.end" />
                                        </el-form-item>
                                    </el-form>
                                </div>
                            </article>
                        </div>
                    </section>
                </div>
                <el-empty v-else description="当前检索条件下没有匹配的序列。" />
            </template>
            <el-empty v-else description="当前 JSON 映射中未分析到包含 start 和 end 的序列。" />
            <template #footer>
                <el-button @click="sequenceDialogVisible = false">取消</el-button>
                <el-button type="primary" :disabled="!sequenceConfigRows.length" @click="confirmSequenceConfig">
                    确定
                </el-button>
            </template>
        </el-dialog>
    </section>
</template>

<script setup lang="ts">
import { ArrowDown, Connection, Document, Download, Search, Setting, Warning } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { computed, ref } from 'vue'
import JsonMappingTree from './JsonMappingTree.vue'

defineOptions({
    name: 'MappingResultPanel'
})

type TagType = 'success' | 'warning' | 'info' | 'primary' | 'danger'
type ResultTab = 'json' | 'xml' | 'problem'
type ExportMappingType = 'json' | 'xml'
type JsonObject = Record<string, unknown>
type JsonPath = Array<string | number>

interface SequenceConfigRow {
    id: string
    path: JsonPath
    pathText: string
    topKey: string
    topDesc: string
    parentDesc: string
    desc: string
    name: string
    start: string
    end: string
    startValueType: string
    endValueType: string
}

interface SequenceConfigTypeGroup {
    typeName: string
    rows: SequenceConfigRow[]
}

interface SequenceConfigGroup {
    topKey: string
    topDesc: string
    rowCount: number
    typeGroups: SequenceConfigTypeGroup[]
}

const props = defineProps<{
    responseStatusText: string
    responseStatusTagType: TagType
    activeResultTab: ResultTab
    mappingMetaText: string
    mappingJsonPreview: string
    xmlMetaText: string
    xmlMappingPreview: string
    xmlEmptyText: string
    problemTabLabel: string
    problemList: string[]
    problemEmptyText: string
    methodDescribe: string
    canExportJsonMapping: boolean
    canExportXmlMapping: boolean
    canGenerateXmlMapping: boolean
    isGeneratingXml: boolean
    showXmlMappingTab: boolean
}>()

const emit = defineEmits<{
    (event: 'update:activeResultTab', value: ResultTab): void
    (event: 'export-mapping', value: ExportMappingType): void
    (event: 'generate-xml-mapping'): void
    (event: 'update-mapping-json', value: string): void
}>()

const activeTabProxy = computed({
    get: () => (props.activeResultTab === 'problem' ? 'json' : props.activeResultTab),
    set: value => emit('update:activeResultTab', value)
})

const canExportAnyMapping = computed(() => props.canExportJsonMapping || props.canExportXmlMapping)
const activeExportType = computed<ExportMappingType>(() => {
    if (props.activeResultTab === 'xml') return 'xml'
    if (props.canExportJsonMapping) return 'json'
    if (props.canExportXmlMapping) return 'xml'
    return 'json'
})
const canExportActiveMapping = computed(() =>
    activeExportType.value === 'json' ? props.canExportJsonMapping : props.canExportXmlMapping
)
const exportButtonText = computed(() => (activeExportType.value === 'xml' ? '导出XML映射' : '导出JSON映射'))
const problemButtonText = computed(() =>
    props.problemList.length ? `问题列表（${props.problemList.length}）` : '问题列表'
)
const problemDialogVisible = ref(false)
const matchResultDialogVisible = ref(false)
const sequenceDialogVisible = ref(false)
const sequenceConfigRows = ref<SequenceConfigRow[]>([])
const sequenceSearchKeyword = ref('')
const normalizedSequenceSearchKeyword = computed(() => sequenceSearchKeyword.value.trim().toLowerCase())
const filteredSequenceRows = computed(() => {
    const keyword = normalizedSequenceSearchKeyword.value

    if (!keyword) return sequenceConfigRows.value

    return sequenceConfigRows.value.filter(row =>
        [row.topKey, row.topDesc, row.desc, row.parentDesc, row.name, row.pathText, row.start, row.end].some(value =>
            value.toLowerCase().includes(keyword)
        )
    )
})
const sequenceConfigGroups = computed<SequenceConfigGroup[]>(() => {
    const groupMap = new Map<string, SequenceConfigRow[]>()

    filteredSequenceRows.value.forEach(row => {
        const groupRows = groupMap.get(row.topKey) || []

        groupRows.push(row)
        groupMap.set(row.topKey, groupRows)
    })

    return Array.from(groupMap.entries()).map(([topKey, rows]) => {
        const typeMap = new Map<string, SequenceConfigRow[]>()

        rows.forEach(row => {
            const typeRows = typeMap.get(row.desc) || []

            typeRows.push(row)
            typeMap.set(row.desc, typeRows)
        })

        return {
            topKey,
            topDesc: rows[0]?.topDesc || '',
            rowCount: rows.length,
            typeGroups: Array.from(typeMap.entries()).map(([typeName, typeRows]) => ({
                typeName,
                rows: typeRows
            }))
        }
    })
})

const handleExportCommand = (command: string | number | object) => {
    if (command === 'json' || command === 'xml') {
        emit('export-mapping', command)
    }
}

const isRecord = (value: unknown): value is JsonObject => Boolean(value && typeof value === 'object' && !Array.isArray(value))

const toDisplayText = (value: unknown, fallback: string) => {
    if (typeof value === 'string' && value.trim()) return value.trim()
    if (typeof value === 'number' || typeof value === 'boolean') return String(value)
    return fallback
}

const isZeroValue = (value: unknown) => {
    if (typeof value === 'number') return value === 0
    if (typeof value === 'string') return Number(value.trim()) === 0
    return false
}

const getPathText = (path: JsonPath) => (path.length ? path.map(item => String(item)).join(' / ') : '$')

const getTopKey = (path: JsonPath) => (path.length ? String(path[0]) : '$')

const resolveTopDesc = (source: unknown, path: JsonPath) => {
    if (!path.length || !isRecord(source)) return ''

    const topValue = source[String(path[0])]

    return isRecord(topValue) ? toDisplayText(topValue.desc, '') : ''
}

const collectSequenceRows = (source: unknown, path: JsonPath = [], parentDesc = '', rootSource = source): SequenceConfigRow[] => {
    if (Array.isArray(source)) {
        return source.flatMap((item, index) => collectSequenceRows(item, [...path, index], parentDesc, rootSource))
    }

    if (!isRecord(source)) return []

    const currentDesc = toDisplayText(source.desc, '')
    const nextParentDesc = currentDesc || parentDesc
    const childrenRows = Object.entries(source).flatMap(([key, value]) =>
        collectSequenceRows(value, [...path, key], nextParentDesc, rootSource)
    )
    const hasStart = Object.prototype.hasOwnProperty.call(source, 'start')
    const hasEnd = Object.prototype.hasOwnProperty.call(source, 'end')

    if (!hasStart || !hasEnd) return childrenRows
    if (isZeroValue(source.start) && isZeroValue(source.end)) return childrenRows

    return [
        {
            id: path.length ? path.join('/') : '$',
            path,
            pathText: getPathText(path),
            topKey: getTopKey(path),
            topDesc: resolveTopDesc(rootSource, path),
            parentDesc: parentDesc || '未配置上层描述',
            desc: toDisplayText(source.desc, '未命名序列'),
            name: toDisplayText(source.name, '未配置 name'),
            start: source.start === undefined || source.start === null ? '' : String(source.start),
            end: source.end === undefined || source.end === null ? '' : String(source.end),
            startValueType: typeof source.start,
            endValueType: typeof source.end
        },
        ...childrenRows
    ]
}

const getObjectByPath = (source: unknown, path: JsonPath) => {
    return path.reduce<unknown>((target, key) => {
        if (!target || typeof target !== 'object') return undefined
        return (target as Record<string | number, unknown>)[key]
    }, source)
}

const normalizeSequenceValue = (value: string, valueType: string) => {
    if (valueType !== 'number') return value

    const numericValue = Number(value)
    if (!Number.isFinite(numericValue)) {
        throw new Error('start 和 end 需要填写有效数字')
    }

    return numericValue
}

const openSequenceDialog = () => {
    try {
        const parsed = JSON.parse(props.mappingJsonPreview) as unknown

        sequenceConfigRows.value = collectSequenceRows(parsed)
        sequenceSearchKeyword.value = ''
        sequenceDialogVisible.value = true
    } catch {
        ElMessage.warning('当前 JSON 映射内容无法解析，不能配置序列')
    }
}

const confirmSequenceConfig = () => {
    try {
        const nextJson = JSON.parse(props.mappingJsonPreview) as unknown

        sequenceConfigRows.value.forEach(row => {
            const target = getObjectByPath(nextJson, row.path)

            if (!isRecord(target)) return

            // 关键业务节点：序列配置只回写 start/end，避免弹窗编辑影响映射 JSON 的其他字段结构。
            target.start = normalizeSequenceValue(row.start, row.startValueType)
            target.end = normalizeSequenceValue(row.end, row.endValueType)
        })

        emit('update-mapping-json', JSON.stringify(nextJson, null, 4))
        sequenceDialogVisible.value = false
        ElMessage.success('序列配置已同步到 JSON 映射')
    } catch (error) {
        ElMessage.warning(error instanceof Error ? error.message : '序列配置同步失败')
    }
}
</script>

<style scoped lang="scss">
.mapping-panel {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-height: 0;
    padding: 24px;
    border: 1px solid #e5e7eb;
    border-radius: 12px;
    background: #ffffff;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
    overflow: hidden;
}

.panel-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
}

.panel-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 12px;
}

.export-actions {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 8px;
    white-space: nowrap;
}

.export-menu-button {
    width: 32px;
    padding: 8px;
}

.panel-title {
    margin: 0;
    font-size: 22px;
    font-weight: 600;
    line-height: 1.4;
    color: #1f2937;
}

.panel-description {
    margin: 8px 0 0;
    font-size: 14px;
    line-height: 1.7;
    color: #4b5563;
}

.panel-content {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 16px;
    min-height: 0;
    overflow: auto;
}

.panel-content--fixed {
    overflow: hidden;
}

.panel-section {
    border: 1px solid #e5e7eb;
    border-radius: 12px;
    background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
}

.result-card {
    padding: 16px;
}

.grow-card {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-height: 0;
}

.preview-tab-section {
    overflow: hidden;
}

.preview-tabs {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-height: 0;
}

.preview-tabs :deep(.el-tabs__header) {
    margin-bottom: 16px;
}

.preview-tabs :deep(.el-tabs__nav-wrap::after) {
    background-color: #e5e7eb;
}

.preview-tabs :deep(.el-tabs__item) {
    height: 36px;
    font-size: 15px;
    font-weight: 600;
    color: #4b5563;
}

.preview-tabs :deep(.el-tabs__item.is-active) {
    color: #2563eb;
}

.preview-tabs :deep(.el-tabs__content) {
    flex: 1;
    min-height: 0;
}

.preview-tabs :deep(.el-tab-pane) {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

.mapping-json-scroll {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-height: 0;
    overflow: auto;
}

.mapping-json-text {
    margin: 0;
    padding: 16px;
    border: 1px solid #dbe3f0;
    border-radius: 10px;
    background: #ffffff;
    font-family: Consolas, 'Courier New', monospace;
    font-size: 13px;
    line-height: 1.7;
    color: #172033;
    white-space: pre-wrap;
    word-break: break-word;
}

.xml-file-viewer {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-height: 0;
    border: 1px solid #dbe3f0;
    border-radius: 10px;
    background: #ffffff;
    overflow: hidden;
}

.xml-file-header {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    min-height: 40px;
    padding: 0 14px;
    border-bottom: 1px solid #e5e7eb;
    background: #f8fafc;
    white-space: nowrap;
}

.xml-file-name {
    font-size: 14px;
    font-weight: 600;
    color: #1f2937;
}

.xml-file-meta {
    min-width: 0;
    overflow: hidden;
    font-size: 13px;
    color: #64748b;
    text-overflow: ellipsis;
}

.xml-file-content {
    flex: 1;
    min-height: 0;
    margin: 0;
    padding: 16px;
    overflow: auto;
    font-family: Consolas, 'Courier New', monospace;
    font-size: 13px;
    line-height: 1.7;
    color: #172033;
    white-space: pre;
}

.problem-dialog-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
    max-height: 62vh;
    padding-right: 4px;
    overflow: auto;
}

.problem-item {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 14px 16px;
    border: 1px solid #f3d19e;
    border-radius: 10px;
    background: #fff7ed;
}

.problem-index {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 24px;
    width: 24px;
    height: 24px;
    border-radius: 999px;
    background: #f97316;
    font-size: 12px;
    font-weight: 600;
    color: #ffffff;
}

.problem-text {
    flex: 1;
    font-size: 14px;
    line-height: 1.7;
    color: #7c2d12;
    word-break: break-word;
}

.match-result-actions {
    display: flex;
    flex: 0 0 auto;
    justify-content: flex-end;
    margin-bottom: 12px;
}

.match-result-detail {
    max-height: 58vh;
    padding: 14px 16px;
    overflow: auto;
    border: 1px solid #dbe3f0;
    border-radius: 8px;
    background: #f8fafc;
    font-size: 14px;
    line-height: 1.7;
    color: #334155;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.dialog-search-bar {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 12px;
}

.dialog-search-count {
    flex: 0 0 auto;
    font-size: 13px;
    line-height: 1.6;
    color: #64748b;
    white-space: nowrap;
}

.sequence-config-list {
    display: flex;
    flex-direction: column;
    gap: 16px;
    max-height: 62vh;
    padding-right: 4px;
    overflow: auto;
}

.sequence-top-group {
    padding: 16px;
    border: 1px solid #dbe3f0;
    border-radius: 10px;
    background: #f8fafc;
}

.sequence-top-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 12px;
}

.sequence-top-title {
    margin: 0;
    font-size: 16px;
    font-weight: 600;
    line-height: 1.5;
    color: #1f2937;
}

.sequence-top-key {
    margin: 4px 0 0;
    font-size: 12px;
    line-height: 1.6;
    color: #64748b;
    word-break: break-all;
}

.sequence-type-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.sequence-type-card {
    padding: 14px;
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    background: #ffffff;
}

.sequence-type-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}

.sequence-type-title {
    min-width: 0;
    overflow: hidden;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.5;
    color: #111827;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sequence-config-item {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 16px;
    padding: 8px 0;
    border: 1px solid #dbe3f0;
    border-width: 1px 0 0;
    background: transparent;
}

.sequence-config-item:last-child {
    padding-bottom: 0;
}

.sequence-config-info {
    min-width: 0;
}

.sequence-config-title {
    overflow: hidden;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.5;
    color: #1f2937;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sequence-config-subtitle {
    margin-top: 4px;
    overflow: hidden;
    font-size: 13px;
    line-height: 1.6;
    color: #64748b;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sequence-config-path {
    margin-left: 8px;
    color: #94a3b8;
}

.sequence-config-form {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
}

.sequence-config-form :deep(.el-form-item) {
    margin-bottom: 0;
}

.sequence-config-form :deep(.el-form-item__label) {
    margin-bottom: 2px;
    line-height: 18px;
}

@media (max-width: 768px) {
    .mapping-panel {
        padding: 20px;
    }

    .panel-header,
    .panel-actions {
        flex-direction: column;
        align-items: flex-start;
    }

    .sequence-config-item {
        grid-template-columns: 1fr;
    }

    .sequence-config-form {
        grid-template-columns: 1fr;
    }
}
</style>
