<template>
    <div class="document-search-word">
        <div class="search-toolbar">
            <el-input v-model="keyword" class="search-input" placeholder="请输入关键字" clearable
                @keyup.enter.native="handleSearch" @clear="resetSearch">
                <el-button slot="append" class="search-btn" icon="el-icon-search" @click="handleSearch"
                    :loading="searching" :disabled="searching">
                    <span v-if="!searching">搜索</span>
                    <span v-else>搜索中...</span>
                </el-button>
            </el-input>
            <div class="search-status" v-if="searchResults.length">
                <span class="result-indicator">{{ currentResultIndex + 1 }}/{{ searchResults.length }}</span>
                <el-button class="nav-btn" type="text" icon="el-icon-arrow-up" :disabled="!searchResults.length"
                    @click="goToPrevious">
                    上一处
                </el-button>
                <el-button class="nav-btn" type="text" icon="el-icon-arrow-down" :disabled="!searchResults.length"
                    @click="goToNext">
                    下一处
                </el-button>
            </div>
        </div>

        <div class="viewer-container">
            <div ref="docWrapper" class="doc-wrapper">
                <div ref="docxContainer" class="doc-content"></div>
            </div>

            <transition name="fade">
                <div v-if="loading" key="loading" class="overlay state-panel">
                    <i class="el-icon-loading state-icon"></i>
                    <p>正在加载 Word 文档，请稍候...</p>
                </div>
                <div v-else-if="error" key="error" class="overlay state-panel">
                    <i class="el-icon-warning-outline state-icon"></i>
                    <p>{{ error }}</p>
                    <el-button type="primary" @click="reload">重新加载</el-button>
                </div>
                <div v-else-if="!docUrl" key="no-url" class="overlay state-panel">
                    <i class="el-icon-document state-icon"></i>
                    <p>暂未指定 Word 文件，请通过路由参数 url 传入文件地址。</p>
                </div>
            </transition>
        </div>
    </div>
</template>

<script>
import * as docxPreview from 'docx-preview/dist/docx-preview.js'

const DEFAULT_DOC_URL = 'http://192.168.0.14:9090/smart-bid/technicalSolutionDatabase/2025/11/11/887b35d28b2149b6a7555fb639be9411.docx'

export default {
    name: 'DocumentSearchWord',
    data() {
        return {
            docUrl: '',
            keyword: '',
            loading: false,
            error: null,
            searchResults: [],
            currentResultIndex: -1,
            searching: false,
            searchSegments: [],
            docRendered: false,
            abortController: null,
        }
    },
    mounted() {
        this.docUrl = this.$route.query.url || DEFAULT_DOC_URL
        if (this.docUrl) {
            this.loadDocument()
        }
    },
    beforeDestroy() {
        this.cancelFetch()
    },
    watch: {
        '$route.query.url'(newUrl, oldUrl) {
            if (newUrl !== oldUrl) {
                this.docUrl = newUrl || DEFAULT_DOC_URL
                this.resetViewerState()
                if (this.docUrl) {
                    this.loadDocument()
                }
            }
        },
    },
    methods: {
        cancelFetch() {
            if (this.abortController) {
                this.abortController.abort()
                this.abortController = null
            }
        },

        async loadDocument() {
            if (!this.docUrl) return
            this.loading = true
            this.error = null
            this.docRendered = false
            this.clearHighlights()
            this.searchSegments = []
            this.cancelFetch()
            const container = this.$refs.docxContainer
            if (container) {
                container.innerHTML = ''
            }

            try {
                const headers = {}
                const token = this.$store?.getters?.token || window?.sessionStorage?.getItem('token')
                if (token) {
                    headers.Authorization = token.startsWith('Bearer ') ? token : `Bearer ${token}`
                }
                this.abortController = new AbortController()
                const response = await fetch(this.docUrl, {
                    headers,
                    credentials: 'include',
                    signal: this.abortController.signal,
                })
                if (!response.ok) {
                    throw new Error(`加载 Word 文档失败：${response.statusText}`)
                }
                const arrayBuffer = await response.arrayBuffer()
                if (!container) {
                    throw new Error('未找到文档容器')
                }
                await docxPreview.renderAsync(arrayBuffer, container, null, {
                    className: 'docx-viewer',
                    inWrapper: true,
                    hideWrapperOnPrint: false,
                    ignoreWidth: false,
                    ignoreHeight: false,
                    ignoreFonts: false,
                    breakPages: false,
                    ignoreLastRenderedPageBreak: true,
                    experimental: false,
                    trimXmlDeclaration: true,
                    useBase64URL: false,
                    renderChanges: false,
                    renderHeaders: true,
                    renderFooters: true,
                    renderFootnotes: true,
                    renderEndnotes: true,
                    renderComments: false,
                    renderAltChunks: true,
                    useMathMLPolyfill: false,
                    debug: false,
                })
                this.normalizeTableStyles(container)
                this.docRendered = true
                this.loading = false
                this.$nextTick(() => {
                    this.prepareSearchSegments()
                    if (this.keyword.trim()) {
                        this.highlightMatches()
                    }
                })
            } catch (error) {
                if (error.name === 'AbortError') return
                console.error('Word 文档预览失败:', error)
                this.loading = false
                this.error = error?.message || 'Word 文档加载失败，请稍后重试'
                this.docRendered = false
            } finally {
                this.abortController = null
            }
        },

        getElementTextContent(element) {
            if (!element) return ''
            return (element.textContent || '').trim()
        },

        prepareSearchSegments(forceReset = false) {
            if (!this.docRendered) {
                if (forceReset) {
                    this.searchSegments = []
                }
                return
            }
            const container = this.$refs.docxContainer
            if (!container) return

            const selectors = [
                '.docx-viewer p',
                '.docx-viewer li',
                '.docx-viewer td',
                '.docx-viewer th',
                '.docx-viewer h1',
                '.docx-viewer h2',
                '.docx-viewer h3',
                '.docx-viewer h4',
                '.docx-viewer h5',
                '.docx-viewer h6',
                '.docx-viewer span',
            ]

            const allElements = Array.from(container.querySelectorAll(selectors.join(',')))
            const targetTags = ['p', 'li', 'td', 'th', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span']
            
            const elements = allElements.filter((el) => {
                const text = this.getElementTextContent(el)
                if (text.length === 0) return false
                
                let parent = el.parentElement
                while (parent && parent !== container) {
                    const parentTag = parent.tagName ? parent.tagName.toLowerCase() : ''
                    if (targetTags.includes(parentTag)) {
                        return false
                    }
                    parent = parent.parentElement
                }
                return true
            })

            elements.forEach((el, idx) => {
                if (typeof el.dataset.originalHtml === 'undefined') {
                    const cleanHtml = this.cleanHtmlFromMarks(el.innerHTML)
                    el.dataset.originalHtml = cleanHtml
                } else {
                    el.dataset.originalHtml = this.cleanHtmlFromMarks(el.dataset.originalHtml)
                }
                if (typeof el.dataset.originalText === 'undefined') {
                    el.dataset.originalText = this.getElementTextContent(el)
                }
                el.dataset.segmentIndex = idx
            })

            this.searchSegments = elements
        },

        async handleSearch() {
            const keyword = this.keyword.trim()
            if (!keyword) {
                this.resetSearch()
                return
            }
            if (!this.docRendered) {
                this.prepareSearchSegments()
            }
            this.searching = true
            await this.$nextTick()
            try {
                this.highlightMatches()
            } finally {
                this.searching = false
            }
        },

        cleanHtmlFromMarks(html) {
            if (!html) return html
            const tempDiv = document.createElement('div')
            tempDiv.innerHTML = html
            const marks = tempDiv.querySelectorAll('mark')
            marks.forEach(mark => {
                const parent = mark.parentNode
                while (mark.firstChild) {
                    parent.insertBefore(mark.firstChild, mark)
                }
                parent.removeChild(mark)
            })
            return tempDiv.innerHTML
        },

        highlightTextInNode(node, pattern, results, segmentIndex, parentElement, markIndexRef) {
            if (node.nodeType === Node.TEXT_NODE) {
                const text = node.textContent
                if (!text || !text.trim()) return
                
                pattern.lastIndex = 0
                const textMatches = []
                let match
                while ((match = pattern.exec(text)) !== null) {
                    textMatches.push({
                        index: match.index,
                        length: match[0].length,
                        text: match[0]
                    })
                }
                
                if (textMatches.length === 0) return
                
                const parent = node.parentNode
                if (!parent) return
                
                let lastIndex = 0
                const fragment = document.createDocumentFragment()
                
                textMatches.forEach((matchInfo) => {
                    if (lastIndex < matchInfo.index) {
                        const beforeText = text.substring(lastIndex, matchInfo.index)
                        if (beforeText) {
                            fragment.appendChild(document.createTextNode(beforeText))
                        }
                    }
                    
                    const mark = document.createElement('mark')
                    mark.className = 'search-highlight'
                    mark.textContent = matchInfo.text
                    fragment.appendChild(mark)
                    
                    results.push({
                        element: mark,
                        segmentIndex: segmentIndex,
                        markIndex: markIndexRef.value,
                        parentElement: parentElement
                    })
                    
                    markIndexRef.value++
                    lastIndex = matchInfo.index + matchInfo.length
                })
                
                if (lastIndex < text.length) {
                    const afterText = text.substring(lastIndex)
                    if (afterText) {
                        fragment.appendChild(document.createTextNode(afterText))
                    }
                }
                
                if (fragment.childNodes.length > 0) {
                    parent.replaceChild(fragment, node)
                }
            } else if (node.nodeType === Node.ELEMENT_NODE) {
                const tagName = node.tagName ? node.tagName.toLowerCase() : ''
                if (tagName === 'mark' || tagName === 'script' || tagName === 'style' || tagName === 'noscript') {
                    return
                }
                
                const children = Array.from(node.childNodes)
                children.forEach(child => {
                    this.highlightTextInNode(child, pattern, results, segmentIndex, parentElement, markIndexRef)
                })
            }
        },

        highlightMatches() {
            const keyword = this.keyword.trim()
            if (!keyword) {
                this.resetSearch()
                return
            }
            if (!this.searchSegments.length) {
                this.prepareSearchSegments()
            }

            const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
            const pattern = new RegExp(`(${escapedKeyword})`, 'gi')

            this.clearHighlights()

            const results = []
            let totalTextMatches = 0
            
            this.searchSegments.forEach((el, segmentIndex) => {
                const originalText = el.dataset.originalText
                if (typeof originalText === 'undefined' || !originalText) return
                
                pattern.lastIndex = 0
                const textMatchCount = (originalText.match(pattern) || []).length
                if (textMatchCount === 0) return
                
                totalTextMatches += textMatchCount
                
                const originalHtml = el.dataset.originalHtml
                if (typeof originalHtml === 'undefined') return
                
                const cleanHtml = this.cleanHtmlFromMarks(originalHtml)
                el.innerHTML = cleanHtml
                el.dataset.originalHtml = cleanHtml
                
                const existingMarks = el.querySelectorAll('mark')
                if (existingMarks.length > 0) {
                    console.warn(`Segment ${segmentIndex} still has ${existingMarks.length} mark tags after cleaning`)
                }
                
                const markIndexRef = { value: 0 }
                const children = Array.from(el.childNodes)
                children.forEach(child => {
                    this.highlightTextInNode(child, pattern, results, segmentIndex, el, markIndexRef)
                })
                
                const createdMarks = el.querySelectorAll('mark.search-highlight')
                if (createdMarks.length !== textMatchCount) {
                    console.warn(`Segment ${segmentIndex}: expected ${textMatchCount} marks, created ${createdMarks.length}`)
                }
            })

            if (results.length !== totalTextMatches) {
                console.warn(`Total matches mismatch: expected ${totalTextMatches}, got ${results.length}`)
            }

            this.searchResults = results
            
            if (!results.length) {
                this.currentResultIndex = -1
                this.$message && this.$message.info(`未找到"${keyword}"相关内容`)
                return
            }

            this.currentResultIndex = 0
            this.updateActiveHighlight()
            this.$nextTick(() => {
                this.navigateToResult(this.currentResultIndex)
            })
        },

        getResultElement(result) {
            if (!result) return null
            
            if (result.element && document.body.contains(result.element)) {
                return result.element
            }
            
            if (result.parentElement && result.segmentIndex !== undefined && result.markIndex !== undefined) {
                const parent = result.parentElement
                if (document.body.contains(parent)) {
                    const marks = parent.querySelectorAll('mark.search-highlight')
                    if (marks[result.markIndex]) {
                        result.element = marks[result.markIndex]
                        return result.element
                    }
                }
            }
            
            if (result.segmentIndex !== undefined && result.markIndex !== undefined && this.searchSegments[result.segmentIndex]) {
                const segment = this.searchSegments[result.segmentIndex]
                if (document.body.contains(segment)) {
                    const marks = segment.querySelectorAll('mark.search-highlight')
                    if (marks[result.markIndex]) {
                        result.element = marks[result.markIndex]
                        result.parentElement = segment
                        return result.element
                    }
                }
            }
            
            return null
        },

        updateActiveHighlight() {
            this.searchResults.forEach((item, idx) => {
                const element = this.getResultElement(item)
                if (!element) return
                
                if (idx === this.currentResultIndex) {
                    element.classList.add('is-active', 'is-current')
                } else {
                    element.classList.remove('is-active', 'is-current')
                }
            })
        },

        calculateOffsetTop(element, container) {
            let offset = 0
            let node = element
            while (node && node !== container) {
                offset += node.offsetTop || 0
                node = node.offsetParent
            }
            return offset
        },

        navigateToResult(index, useSmoothScroll = false) {
            if (!this.searchResults.length || index < 0 || index >= this.searchResults.length) return
            this.currentResultIndex = index
            this.updateActiveHighlight()

            this.$nextTick(() => {
                requestAnimationFrame(() => {
                    requestAnimationFrame(() => {
                        const result = this.searchResults[this.currentResultIndex]
                        const target = this.getResultElement(result)
                        const wrapper = this.$refs.docWrapper
                        if (!target || !wrapper) return

                        if (!document.body.contains(target)) {
                            return
                        }

                        const targetRect = target.getBoundingClientRect()
                        if (targetRect.width === 0 && targetRect.height === 0) {
                            return
                        }

                        try {
                            const wrapperRect = wrapper.getBoundingClientRect()
                            const currentScrollTop = wrapper.scrollTop
                            const targetTopRelativeToWrapper = targetRect.top - wrapperRect.top + currentScrollTop
                            const margin = 24
                            const desiredScrollTop = Math.max(targetTopRelativeToWrapper - margin, 0)
                            
                            wrapper.scrollTop = desiredScrollTop
                        } catch (error) {
                            try {
                                let absoluteTop = 0
                                let node = target
                                while (node && node !== wrapper) {
                                    absoluteTop += node.offsetTop || 0
                                    node = node.offsetParent
                                }
                                const margin = 24
                                wrapper.scrollTop = Math.max(absoluteTop - margin, 0)
                            } catch (e) {
                                console.error('Scroll error:', e)
                            }
                        }
                    })
                })
            })
        },

        async goToPrevious() {
            if (!this.searchResults.length) return
            let targetIndex = this.currentResultIndex
            if (targetIndex === -1) {
                targetIndex = this.searchResults.length - 1
            } else {
                targetIndex -= 1
                if (targetIndex < 0) {
                    targetIndex = this.searchResults.length - 1
                }
            }
            this.navigateToResult(targetIndex)
        },

        async goToNext() {
            if (!this.searchResults.length) return
            let targetIndex = this.currentResultIndex
            if (targetIndex === -1) {
                targetIndex = 0
            } else {
                targetIndex += 1
                if (targetIndex > this.searchResults.length - 1) {
                    targetIndex = 0
                }
            }
            this.navigateToResult(targetIndex)
        },

        clearHighlights() {
            if (!this.searchSegments.length) return
            this.searchSegments.forEach((el) => {
                if (typeof el.dataset.originalHtml !== 'undefined') {
                    el.innerHTML = el.dataset.originalHtml
                }
            })
        },

        resetSearch() {
            this.clearHighlights()
            this.searchResults = []
            this.currentResultIndex = -1
        },

        resetViewerState() {
            this.clearHighlights()
            this.searchResults = []
            this.currentResultIndex = -1
            this.searchSegments = []
            this.docRendered = false
            this.loading = !!this.docUrl
            this.error = null
            const container = this.$refs.docxContainer
            if (container) {
                container.innerHTML = ''
            }
        },

        reload() {
            if (!this.docUrl) return
            this.loadDocument()
        },

        normalizeTableStyles(container) {
            if (!container) return
            const tables = container.querySelectorAll('table')
            tables.forEach((table) => {
                table.style.width = 'auto'
                table.style.tableLayout = 'auto'
                table.style.textAlign = 'left'
                table.style.margin = '0 auto'
                const pTags = table.querySelectorAll('td p.docx-viewer_tableparagraph')
                pTags.forEach((p) => {
                    p.style.lineHeight = '1.2'
                })
            })
        }
    },
}
</script>

<style scoped lang="scss">
.document-search-word {
    display: flex;
    flex-direction: column;
    height: calc(100vh - 84px);
    background: linear-gradient(180deg, #f4f7ff 0%, #ffffff 100%);
    padding: 16px;
    box-sizing: border-box;
}

.document-search-word article.docx-article-wrapper {
    display: block;
    margin: 0 auto 32px;
    background: #ffffff;
    border-radius: 16px;
    box-shadow: 0 20px 48px rgba(25, 64, 158, 0.12);
    padding: 48px 56px;
    box-sizing: border-box;
    color: #1f2a62;
    line-height: 1.75;
    font-size: 14px;
}

.search-toolbar {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 16px;
    margin-bottom: 16px;
    flex-wrap: wrap;
}

.search-input {
    flex: 0 0 360px;
    width: 360px;
    max-width: 100%;
}

.search-status {
    display: flex;
    align-items: center;
    gap: 8px;
    color: #506dff;
}

.result-indicator {
    font-weight: 600;
}

.nav-btn {
    padding: 0 8px;
}


.viewer-container {
    flex: 1;
    background: #ffffff;
    border-radius: 12px;
    overflow: hidden;
    position: relative;
    display: flex;
    flex-direction: column;
    min-height: 0;
    max-height: 100%;
    height: 0;
}

.doc-wrapper {
    flex: 1;
    padding: 24px;
    background: #eef2ff;
    position: relative;
    width: 100%;
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    min-height: 0;
}

.doc-content {
    max-width: 960px;
    margin: 0 auto;
    background: #ffffff;
    border-radius: 16px;
    box-shadow: 0 18px 40px rgba(25, 64, 158, 0.12);
    padding: 48px 56px;
}

::v-deep .docx-wrapper {
    margin: 0 auto;
    background: linear-gradient(180deg, #fdfdff 0%, #f6f8ff 100%);
    border-radius: 16px;
    box-shadow: 0 24px 48px rgba(25, 64, 158, 0.16);
    overflow: hidden;
    border: 1px solid rgba(68, 112, 255, 0.18);
}

::v-deep .docx-viewer-wrapper>section.docx-viewer {
    background: white;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
    margin-bottom: 5px !important;
}


.overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(245, 247, 255, 0.92);
    z-index: 10;
    text-align: center;
}

.state-panel {
    flex-direction: column;
    gap: 12px;
    color: #6072a1;
}

.state-icon {
    font-size: 28px;
    color: #1f72ea;
}

.fade-enter-active,
.fade-leave-active {
    transition: opacity 0.25s ease;
}

.fade-enter,
.fade-leave-to {
    opacity: 0;
}

::v-deep .search-highlight {
    background: rgba(255, 241, 168, 0.9);
    padding: 0 2px;
    border-radius: 2px;
}

::v-deep .search-highlight.is-active,
::v-deep .search-highlight.is-current {
    background: #206dff !important;
    color: #ffffff;
    box-shadow: 0 0 0 2px rgba(32, 109, 255, 0.35);
    border-radius: 4px;
    transition: background 0.2s ease, box-shadow 0.2s ease;
}
</style>