<template>
    <div class="document-search-word">
        <div class="viewer-container">
            <transition name="slide-fade">
                <div v-if="showSearchBar" class="floating-search">
                    <div class="search-toolbar" :class="{ 'is-searching': searching }">
                        <div class="search-box">
                            <el-input v-model="keyword" class="search-input" placeholder="输入关键字搜索" clearable
                                ref="keywordInput" @keyup.enter.native="handleSearch" @clear="resetSearch">
                            </el-input>
                            <button class="icon-btn search-icon" :disabled="searching" @click="handleSearch">
                                <i class="el-icon-search" v-if="!searching"></i>
                                <i class="el-icon-loading" v-else></i>
                            </button>
                        </div>
                        <div class="search-status">
                            <span class="result-indicator">{{ resultDisplay }}</span>
                            <button class="icon-btn" :disabled="!searchResults.length" @click="goToPrevious">
                                <i class="el-icon-arrow-up"></i>
                            </button>
                            <button class="icon-btn" :disabled="!searchResults.length" @click="goToNext">
                                <i class="el-icon-arrow-down"></i>
                            </button>
                            <button class="icon-btn" @click="handleCloseSearch">
                                <i class="el-icon-close"></i>
                            </button>
                        </div>
                    </div>
                </div>
            </transition>
            <button v-if="!showSearchBar" class="floating-search-btn" @click="toggleSearchBar">
                <i class="el-icon-search"></i>
            </button>
            <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',
    props: {
        fileUrl: {
            type: String,
            default: ''
        }
    },
    computed: {
        resultDisplay() {
            const total = this.searchResults.length
            if (!total) return '0/0'
            const current = this.currentResultIndex >= 0 ? this.currentResultIndex + 1 : 0
            return `${current}/${total}`
        }
    },
    data() {
        return {
            docUrl: '',
            keyword: '',
            loading: false,
            error: null,
            searchResults: [],
            currentResultIndex: -1,
            searching: false,
            searchSegments: [],
            docRendered: false,
            abortController: null,
            scrollAnimationFrame: null,
            showSearchBar: false,
        }
    },
    mounted() {
        // 优先使用 fileUrl prop，然后是路由参数，最后是默认 URL
        const initialUrl = this.fileUrl || this.$route?.query?.url || DEFAULT_DOC_URL
        if (initialUrl && initialUrl !== this.docUrl) {
            this.applyDocUrl(initialUrl)
        } else if (this.docUrl && !this.docRendered) {
            // 如果 docUrl 已设置但文档未渲染，重新加载
            this.loadDocument()
        }
    },
    beforeDestroy() {
        this.cancelFetch()
        this.cancelScrollAnimation()
    },
    watch: {
        fileUrl: {
            immediate: true,
            handler(newVal, oldVal) {
                // 当 fileUrl 变化时，总是重新加载文档
                if (newVal && newVal !== oldVal) {
                    this.applyDocUrl(newVal)
                } else if (newVal && !this.docUrl) {
                    // 如果新值存在但 docUrl 为空，也加载
                    this.applyDocUrl(newVal)
                } else if (!newVal && this.docUrl) {
                    // 如果新值为空但 docUrl 有值，重置状态
                    this.resetViewerState()
                }
            }
        },
        '$route.query.url'(newUrl, oldUrl) {
            if (this.fileUrl) return
            if (newUrl !== oldUrl) {
                const target = newUrl || DEFAULT_DOC_URL
                this.applyDocUrl(target)
            }
        },
    },
    methods: {
        toggleSearchBar() {
            this.showSearchBar = true
            this.$nextTick(() => {
                if (this.$refs.keywordInput) {
                    this.$refs.keywordInput.focus()
                }
            })
        },
        handleCloseSearch() {
            this.keyword = ''
            this.resetSearch()
            this.showSearchBar = false
        },
        applyDocUrl(url) {
            const resolved = url || ''
            // 如果 URL 相同且文档已渲染，不重复加载
            if (resolved === this.docUrl && this.docRendered) {
                return
            }
            // 如果 URL 变化，先取消之前的请求和重置状态
            if (resolved !== this.docUrl) {
                this.cancelFetch()
                this.cancelScrollAnimation()
                this.resetViewerState()
            }
            this.docUrl = resolved
            if (this.docUrl) {
                this.loadDocument()
            } else {
                this.resetViewerState()
            }
        },
        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()
        },

        getSearchableTextContent(element) {
            if (!element) return ''
            let text = ''
            const walker = document.createTreeWalker(
                element,
                NodeFilter.SHOW_TEXT,
                {
                    acceptNode: (node) => {
                        const parent = node.parentElement
                        if (!parent) return NodeFilter.FILTER_REJECT
                        const tagName = parent.tagName ? parent.tagName.toLowerCase() : ''
                        if (tagName === 'mark' || tagName === 'script' || tagName === 'style' || tagName === 'noscript') {
                            return NodeFilter.FILTER_REJECT
                        }
                        return NodeFilter.FILTER_ACCEPT
                    }
                }
            )
            let node
            while ((node = walker.nextNode())) {
                if (node.textContent) {
                    text += node.textContent
                }
            }
            return text
        },

        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()
            }
            // 立即设置 searching 状态，确保动画能够显示
            this.searching = true
            // 使用 requestAnimationFrame 确保 DOM 更新后再执行搜索
            await this.$nextTick()
            await new Promise(resolve => requestAnimationFrame(() => {
                requestAnimationFrame(resolve)
            }))
            try {
                // 执行搜索，确保有足够时间显示动画
                const searchPromise = Promise.resolve(this.highlightMatches())
                const minDelayPromise = new Promise(resolve => setTimeout(resolve, 300))
                await Promise.all([searchPromise, minDelayPromise])
            } 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
        },

        collectTextNodes(element, textNodes) {
            if (!element) return
            const walker = document.createTreeWalker(
                element,
                NodeFilter.SHOW_TEXT,
                {
                    acceptNode: (node) => {
                        const parent = node.parentElement
                        if (!parent) return NodeFilter.FILTER_REJECT
                        const tagName = parent.tagName ? parent.tagName.toLowerCase() : ''
                        if (tagName === 'mark' || tagName === 'script' || tagName === 'style' || tagName === 'noscript') {
                            return NodeFilter.FILTER_REJECT
                        }
                        return NodeFilter.FILTER_ACCEPT
                    }
                }
            )
            let node
            while ((node = walker.nextNode())) {
                if (node.textContent) {
                    textNodes.push(node)
                }
            }
        },

        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 originalHtml = el.dataset.originalHtml
                if (typeof originalHtml === 'undefined') return
                
                const cleanHtml = this.cleanHtmlFromMarks(originalHtml)
                el.innerHTML = cleanHtml
                el.dataset.originalHtml = cleanHtml
                
                const searchableText = this.getSearchableTextContent(el)
                if (!searchableText) return
                
                pattern.lastIndex = 0
                const textMatchCount = (searchableText.match(pattern) || []).length
                if (textMatchCount === 0) return
                
                totalTextMatches += textMatchCount
                
                const existingMarks = el.querySelectorAll('mark')
                if (existingMarks.length > 0) {
                    console.warn(`Segment ${segmentIndex} still has ${existingMarks.length} mark tags after cleaning`)
                }
                
                const textNodes = []
                this.collectTextNodes(el, textNodes)
                
                if (textNodes.length === 0) return
                
                const markIndexRef = { value: 0 }
                
                const nodeMap = []
                let fullText = ''
                let currentOffset = 0
                
                textNodes.forEach((textNode, nodeIndex) => {
                    const text = textNode.textContent || ''
                    const startOffset = currentOffset
                    const endOffset = currentOffset + text.length
                    
                    nodeMap.push({
                        textNode,
                        text,
                        startOffset,
                        endOffset,
                        nodeIndex
                    })
                    
                    fullText += text
                    currentOffset = endOffset
                })
                
                if (!fullText) return
                
                pattern.lastIndex = 0
                const allMatches = []
                let match
                while ((match = pattern.exec(fullText)) !== null) {
                    allMatches.push({
                        start: match.index,
                        end: match.index + match[0].length,
                        text: match[0]
                    })
                }
                
                if (allMatches.length === 0) return
                
                const nodeReplacements = new Map()
                const processedMatches = new Set()
                
                allMatches.forEach((matchInfo, matchIndex) => {
                    const matchStart = matchInfo.start
                    const matchEnd = matchInfo.end
                    const matchKey = `${matchStart}-${matchEnd}`
                    
                    if (processedMatches.has(matchKey)) {
                        return
                    }
                    processedMatches.add(matchKey)
                    
                    const affectedNodes = []
                    for (let i = 0; i < nodeMap.length; i++) {
                        const nodeInfo = nodeMap[i]
                        if (nodeInfo.endOffset > matchStart && nodeInfo.startOffset < matchEnd) {
                            affectedNodes.push({
                                ...nodeInfo,
                                matchStartInNode: Math.max(0, matchStart - nodeInfo.startOffset),
                                matchEndInNode: Math.min(nodeInfo.text.length, matchEnd - nodeInfo.startOffset)
                            })
                        }
                    }
                    
                    if (affectedNodes.length === 0) return
                    
                    affectedNodes.forEach((nodeInfo, idx) => {
                        if (!nodeReplacements.has(nodeInfo.nodeIndex)) {
                            nodeReplacements.set(nodeInfo.nodeIndex, {
                                textNode: nodeInfo.textNode,
                                ranges: [],
                                matchIndices: new Set()
                            })
                        }
                        
                        const replacement = nodeReplacements.get(nodeInfo.nodeIndex)
                        const isFirst = idx === 0
                        const isLast = idx === affectedNodes.length - 1
                        
                        if (isFirst && isLast) {
                            const rangeKey = `${nodeInfo.matchStartInNode}-${nodeInfo.matchEndInNode}-${matchIndex}`
                            if (!replacement.matchIndices.has(rangeKey)) {
                                replacement.ranges.push({
                                    start: nodeInfo.matchStartInNode,
                                    end: nodeInfo.matchEndInNode,
                                    isFullMatch: true,
                                    matchIndex: matchIndex,
                                    shouldAddToResults: true
                                })
                                replacement.matchIndices.add(rangeKey)
                            }
                        } else if (isFirst) {
                            const rangeKey = `${nodeInfo.matchStartInNode}-${nodeInfo.text.length}-${matchIndex}`
                            if (!replacement.matchIndices.has(rangeKey)) {
                                replacement.ranges.push({
                                    start: nodeInfo.matchStartInNode,
                                    end: nodeInfo.text.length,
                                    isStart: true,
                                    matchIndex: matchIndex,
                                    shouldAddToResults: true
                                })
                                replacement.matchIndices.add(rangeKey)
                            }
                        } else if (isLast) {
                            const rangeKey = `0-${nodeInfo.matchEndInNode}-${matchIndex}`
                            if (!replacement.matchIndices.has(rangeKey)) {
                                replacement.ranges.push({
                                    start: 0,
                                    end: nodeInfo.matchEndInNode,
                                    isEnd: true,
                                    matchIndex: matchIndex,
                                    shouldAddToResults: false
                                })
                                replacement.matchIndices.add(rangeKey)
                            }
                        } else {
                            const rangeKey = `0-${nodeInfo.text.length}-${matchIndex}`
                            if (!replacement.matchIndices.has(rangeKey)) {
                                replacement.ranges.push({
                                    start: 0,
                                    end: nodeInfo.text.length,
                                    isMiddle: true,
                                    matchIndex: matchIndex,
                                    shouldAddToResults: false
                                })
                                replacement.matchIndices.add(rangeKey)
                            }
                        }
                    })
                })
                
                for (let i = textNodes.length - 1; i >= 0; i--) {
                    const textNode = textNodes[i]
                    const parent = textNode.parentNode
                    if (!parent || !document.body.contains(textNode)) continue
                    
                    if (nodeReplacements.has(i)) {
                        const replacement = nodeReplacements.get(i)
                        const originalText = replacement.textNode.textContent
                        let ranges = replacement.ranges
                        
                        ranges.sort((a, b) => {
                            if (a.start !== b.start) return a.start - b.start
                            return a.end - b.end
                        })
                        
                        const mergedRanges = []
                        ranges.forEach(range => {
                            if (mergedRanges.length === 0) {
                                mergedRanges.push({ ...range })
                            } else {
                                const lastRange = mergedRanges[mergedRanges.length - 1]
                                if (range.start <= lastRange.end) {
                                    lastRange.end = Math.max(lastRange.end, range.end)
                                } else {
                                    mergedRanges.push({ ...range })
                                }
                            }
                        })
                        
                        const fragment = document.createDocumentFragment()
                        let lastIndex = 0
                        
                        mergedRanges.forEach(range => {
                            if (lastIndex < range.start) {
                                const beforeText = originalText.substring(lastIndex, range.start)
                                if (beforeText) {
                                    fragment.appendChild(document.createTextNode(beforeText))
                                }
                            }
                            
                            const mark = document.createElement('mark')
                            mark.className = 'search-highlight'
                            mark.textContent = originalText.substring(range.start, range.end)
                            fragment.appendChild(mark)
                            
                            if (range.shouldAddToResults !== false) {
                                results.push({
                                    element: mark,
                                    segmentIndex: segmentIndex,
                                    markIndex: markIndexRef.value,
                                    parentElement: el
                                })
                            }
                            
                            markIndexRef.value++
                            
                            lastIndex = range.end
                        })
                        
                        if (lastIndex < originalText.length) {
                            const afterText = originalText.substring(lastIndex)
                            if (afterText) {
                                fragment.appendChild(document.createTextNode(afterText))
                            }
                        }
                        
                        if (fragment.childNodes.length > 0) {
                            try {
                                parent.replaceChild(fragment, textNode)
                            } catch (e) {
                                console.warn(`Failed to replace text node in segment ${segmentIndex}:`, e)
                            }
                        }
                    }
                }
            })

            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, false)
            })
        },

        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
        },

        cancelScrollAnimation() {
            if (this.scrollAnimationFrame) {
                cancelAnimationFrame(this.scrollAnimationFrame)
                this.scrollAnimationFrame = null
            }
        },

        smoothScrollTo(container, target, baseDuration = 350) {
            if (!container) return
            const maxScroll = container.scrollHeight - container.clientHeight
            const finalTarget = Math.min(Math.max(target, 0), Math.max(maxScroll, 0))
            const start = container.scrollTop
            const change = finalTarget - start
            if (Math.abs(change) < 1) {
                container.scrollTop = finalTarget
                return
            }

            const distance = Math.abs(change)
            const viewportHeight = container.clientHeight
            const distanceFactor = Math.min(distance / Math.max(viewportHeight, 1), 2.2)
            const duration = Math.min(600, baseDuration + distanceFactor * 90)

            const startTime = performance.now()
            this.cancelScrollAnimation()

            const easeOutExpo = (t) => {
                return t === 1 ? 1 : 1 - Math.pow(2, -10 * t)
            }

            const step = (now) => {
                const elapsed = now - startTime
                const progress = Math.min(elapsed / duration, 1)
                const eased = easeOutExpo(progress)
                const currentPosition = start + change * eased
                
                container.scrollTop = Math.min(Math.max(currentPosition, 0), maxScroll)

                if (progress < 1) {
                    this.scrollAnimationFrame = requestAnimationFrame(step)
                } else {
                    container.scrollTop = finalTarget
                    this.scrollAnimationFrame = null
                }
            }

            this.scrollAnimationFrame = requestAnimationFrame(step)
        },

        navigateToResult(index, useSmoothScroll = true) {
            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
                        }

                        const wrapperRect = wrapper.getBoundingClientRect()
                        const margin = 24
                        
                        const isInViewport = targetRect.top >= wrapperRect.top + margin && 
                                           targetRect.bottom <= wrapperRect.bottom - margin &&
                                           targetRect.left >= wrapperRect.left &&
                                           targetRect.right <= wrapperRect.right
                        
                        if (isInViewport) {
                            return
                        }

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

                        performScroll()
                    })
                })
            })
        },

        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: #f8f9fa;
    padding: 0;
    box-sizing: border-box;
    overflow: hidden;
}

.document-search-word article.docx-article-wrapper {
    display: block;
    margin: 0;
    background: transparent;
    border-radius: 0;
    box-shadow: none;
    padding: 0;
    box-sizing: border-box;
    color: #303133;
    line-height: 1.8;
    font-size: 15px;
    // 启用文本选择和复制
    user-select: text;
    -webkit-user-select: text;
    -moz-user-select: text;
    -ms-user-select: text;
    cursor: text;
}

.floating-search {
    position: absolute;
    top: 20px;
    right: 20px;
    width: auto;
    max-width: calc(100% - 40px);
    z-index: 6;
    pointer-events: none;
    
    @media (max-width: 768px) {
        top: 12px;
        right: 12px;
        max-width: calc(100% - 24px);
    }
}

.floating-search-btn {
    position: absolute;
    top: 20px;
    right: 20px;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: none;
    background: #2b68ff;
    color: #fff;
    font-size: 18px;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 4px 12px rgba(43, 104, 255, 0.3);
    cursor: pointer;
    transition: all 0.2s ease;
    z-index: 5;
    
    @media (max-width: 768px) {
        top: 12px;
        right: 12px;
        width: 40px;
        height: 40px;
        font-size: 16px;
    }
}

.floating-search-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 16px rgba(43, 104, 255, 0.4);
    background: #1d4fd8;
}

.floating-search-btn:active {
    transform: translateY(0);
    box-shadow: 0 2px 8px rgba(43, 104, 255, 0.3);
}

.search-toolbar {
    display: flex;
    align-items: center;
    gap: 14px;
    background: rgba(255, 255, 255, 0.98);
    border-radius: 999px;
    padding: 8px 12px;
    box-shadow: 0 8px 26px rgba(31, 114, 234, 0.18);
    border: 1px solid rgba(226, 231, 239, 0.8);
    pointer-events: auto;
    position: relative;
    overflow: hidden;
}

.search-toolbar::after {
    content: '';
    position: absolute;
    inset: 4px;
    border-radius: 999px;
    border: 1px solid transparent;
    pointer-events: none;
}

.search-toolbar.is-searching {
    &::after {
        border-color: rgba(43, 104, 255, 0.4);
        animation: search-pulse 1.2s ease-in-out infinite;
    }
    
    // 确保搜索图标在搜索时显示加载动画
    .search-icon {
        i.el-icon-loading {
            display: inline-block !important;
            animation: rotating 1s linear infinite !important;
        }
    }
}

@keyframes search-pulse {
    0% {
        opacity: 0.25;
        transform: scale(0.98);
    }
    50% {
        opacity: 1;
        transform: scale(1);
    }
    100% {
        opacity: 0.25;
        transform: scale(0.98);
    }
}

.slide-fade-enter-active,
.slide-fade-leave-active {
    transition: opacity 0.2s ease, transform 0.2s ease;
}

.slide-fade-enter,
.slide-fade-leave-to {
    opacity: 0;
    transform: translateY(-6px);
}

.search-box {
    display: flex;
    align-items: center;
    gap: 4px;
    background: #fff;
    border: 1px solid #e4e7f0;
    border-radius: 999px;
    padding: 0 6px 0 10px;
}

.search-input {
    width: 190px;
}

.search-input ::v-deep .el-input__inner {
    border: none;
    box-shadow: none;
    background: transparent;
    padding: 0;
    height: 30px;
    font-size: 13px;
}

.search-input ::v-deep .el-input__suffix,
.search-input ::v-deep .el-input__prefix {
    display: none;
}

.icon-btn {
    width: 28px;
    height: 28px;
    border: none;
    background: transparent;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #6c7388;
    cursor: pointer;
    transition: background 0.2s ease, color 0.2s ease;
}

.icon-btn:hover {
    background: rgba(64, 158, 255, 0.12);
    color: #2b68ff;
}

.icon-btn:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}

.search-icon {
    background: #2b68ff;
    color: #fff;
    
    // 确保加载图标有旋转动画
    i.el-icon-loading {
        display: inline-block;
        animation: rotating 1s linear infinite;
        font-size: 16px;
    }
    
    i.el-icon-search {
        font-size: 16px;
    }
}

.search-icon:hover {
    background: #1d4fd8;
    color: #fff;
}

.search-icon:disabled {
    opacity: 0.8;
    cursor: wait;
}

@keyframes rotating {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}

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

.result-indicator {
    font-size: 12px;
    color: #4f5875;
    padding-left: 10px;
    font-weight: 500;
}


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

.doc-wrapper {
    flex: 1;
    padding: 40px 20px;
    background: #f5f7fa;
    position: relative;
    width: 100%;
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    min-height: 0;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    will-change: scroll-position;
    // 启用文本选择
    user-select: text;
    -webkit-user-select: text;
    -moz-user-select: text;
    -ms-user-select: text;
    
    // 自定义滚动条
    &::-webkit-scrollbar {
        width: 8px;
    }
    
    &::-webkit-scrollbar-track {
        background: transparent;
        border-radius: 4px;
    }
    
    &::-webkit-scrollbar-thumb {
        background: rgba(0, 0, 0, 0.2);
        border-radius: 4px;
        transition: background 0.3s;
        
        &:hover {
            background: rgba(0, 0, 0, 0.3);
        }
    }
}

.doc-content {
    max-width: 900px;
    margin: 0 auto;
    background: #ffffff;
    border-radius: 8px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
    padding: 60px 80px;
    min-height: calc(100vh - 200px);
    // 启用文本选择和复制
    user-select: text;
    -webkit-user-select: text;
    -moz-user-select: text;
    -ms-user-select: text;
    // 优化选中文本的样式
    ::selection {
        background: rgba(64, 158, 255, 0.2);
        color: #303133;
    }
    ::-moz-selection {
        background: rgba(64, 158, 255, 0.2);
        color: #303133;
    }
    
    @media (max-width: 1200px) {
        padding: 50px 60px;
        max-width: 95%;
    }
    
    @media (max-width: 768px) {
        padding: 40px 30px;
        max-width: 100%;
        border-radius: 0;
    }
}

::v-deep .docx-wrapper {
    margin: 0;
    background: transparent;
    border-radius: 0;
    box-shadow: none;
    overflow: visible;
    border: none;
    // 启用文本选择
    user-select: text;
    -webkit-user-select: text;
    -moz-user-select: text;
    -ms-user-select: text;
}

::v-deep .docx-viewer-wrapper {
    background: transparent;
    // 启用文本选择
    user-select: text;
    -webkit-user-select: text;
    -moz-user-select: text;
    -ms-user-select: text;
}

::v-deep .docx-viewer-wrapper>section.docx-viewer {
    background: transparent;
    box-shadow: none;
    margin-bottom: 0 !important;
    padding: 0;
    // 启用文本选择
    user-select: text;
    -webkit-user-select: text;
    -moz-user-select: text;
    -ms-user-select: text;
}

::v-deep .docx-viewer {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    color: #303133;
    line-height: 1.8;
    // 启用文本选择和复制
    user-select: text !important;
    -webkit-user-select: text !important;
    -moz-user-select: text !important;
    -ms-user-select: text !important;
    // 优化选中文本的样式
    ::selection {
        background: rgba(64, 158, 255, 0.25) !important;
        color: #303133 !important;
    }
    ::-moz-selection {
        background: rgba(64, 158, 255, 0.25) !important;
        color: #303133 !important;
    }
    
    // 确保所有子元素都可以选择文本
    * {
        user-select: text !important;
        -webkit-user-select: text !important;
        -moz-user-select: text !important;
        -ms-user-select: text !important;
    }
    
    p {
        margin: 0.8em 0;
        word-wrap: break-word;
        word-break: break-word;
        cursor: text;
    }
    
    h1, h2, h3, h4, h5, h6 {
        margin: 1.2em 0 0.8em 0;
        font-weight: 600;
        line-height: 1.4;
        cursor: text;
    }
    
    table {
        border-collapse: collapse;
        width: 100%;
        margin: 1em 0;
        cursor: text;
        
        td, th {
            padding: 8px 12px;
            border: 1px solid #e4e7ed;
            cursor: text;
        }
        
        th {
            background: #f5f7fa;
            font-weight: 600;
        }
    }
    
    ul, ol {
        margin: 0.8em 0;
        padding-left: 2em;
        cursor: text;
    }
    
    li {
        margin: 0.4em 0;
        cursor: text;
    }
    
    img {
        max-width: 100%;
        height: auto;
        display: block;
        margin: 1em auto;
        user-select: none;
        -webkit-user-select: none;
        cursor: default;
    }
    
    // 确保链接可以选择文本
    a {
        cursor: pointer;
        user-select: text !important;
    }
    
    // 确保代码块可以选择文本
    code, pre {
        user-select: text !important;
        cursor: text;
    }
}


.overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(4px);
    z-index: 10;
    text-align: center;
}

.state-panel {
    flex-direction: column;
    gap: 16px;
    color: #606266;
    
    p {
        margin: 0;
        font-size: 14px;
        color: #909399;
    }
}

.state-icon {
    font-size: 24px;
    color: #409EFF;
    animation: rotate 1s linear infinite;
}

@keyframes rotate {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}

.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.85);
    padding: 2px 4px;
    border-radius: 3px;
    transition: all 0.2s ease;
    // 确保高亮标记内的文本可以选择
    user-select: text !important;
    -webkit-user-select: text !important;
    -moz-user-select: text !important;
    -ms-user-select: text !important;
    cursor: text;
}

::v-deep .search-highlight.is-active,
::v-deep .search-highlight.is-current {
    background: linear-gradient(135deg, #409EFF 0%, #1d4fd8 100%) !important;
    color: #ffffff;
    box-shadow: 0 2px 8px rgba(64, 158, 255, 0.4);
    border-radius: 4px;
    padding: 2px 4px;
    font-weight: 500;
    transition: all 0.2s ease;
    // 确保激活的高亮标记内的文本可以选择
    user-select: text !important;
    -webkit-user-select: text !important;
    -moz-user-select: text !important;
    -ms-user-select: text !important;
    cursor: text;
}
</style>