package com.all.pdfreader.pro.app.util

import android.util.Log
import com.github.barteksc.pdfviewer.PDFView
import com.tom_roush.pdfbox.pdmodel.PDDocument
import com.tom_roush.pdfbox.pdmodel.PDPage
import com.tom_roush.pdfbox.text.PDFTextStripper
import com.tom_roush.pdfbox.text.TextPosition
import java.io.File

class PDFSearchManager(private val pdfView: PDFView) {

    private var currentSearchResults: List<TextSearchResult> = emptyList()
    var currentHighlightPage = -1

    private var cachedDoc: PDDocument? = null      // 缓存已打开的文档
    private var cachedPath: String? = null         // 记录缓存对应的文件路径

    var currentSearchText: String? = null//记录当前的搜索文字
    var hasSearched: Boolean = false//记录当前是否进行过搜索

    data class TextSearchResult(
        val pageNumber: Int,
        val text: String,
        val positions: List<TextPositionInfo>
    )

    data class TextPositionInfo(
        val text: String,       // 当前字符或文本片段
        val x: Float,           // 字符左下角的 X 坐标（PDF页面坐标系）
        val y: Float,           // 字符基线（baseline）在页面上的 Y 坐标（PDF页面坐标系）
        val width: Float,       // 字符宽度
        val height: Float,      // 字符高度
        val pageWidth: Float,   // 当前 PDF 页宽
        val pageHeight: Float   // 当前 PDF 页高
    ) {
        /**
         * 将字符位置信息转换为 PDFView 可用的相对矩形 (RectF)
         *
         * @return RectF 返回一个相对页面比例的矩形（左上角和右下角的值在 0~1 范围）
         *
         * 说明：
         * - PDFBox 中 y 是字符基线 (baseline)，不是顶部，所以顶部需要计算为 y - height。（如果不这样那选中的位子会在文字的正下方）
         * - RectF 的 left/top/right/bottom 坐标是相对于页面尺寸的比例。
         * - 添加了 padding，使高亮框稍微大于文字，避免高亮框太紧。
         */
        fun getRelativeRect(): android.graphics.RectF {
            // padding：用于微调高亮框大小，按页面比例计算
            val padding = 2f / pageHeight

            // 计算相对页面坐标
            val left = x / pageWidth - padding             // 左边界
            val top = (y - height) / pageHeight - padding  // 顶部边界（基线减去字符高度，再减去 margin）
            val right = (x + width) / pageWidth + padding // 右边界
            val bottom = y / pageHeight + padding         // 底部边界（基线加上 margin）

            // 返回相对页面坐标的矩形
            return android.graphics.RectF(left, top, right, bottom)
        }
    }

    /**
     * 获取 PDDocument，若已缓存则直接返回，否则加载并缓存。
     * @param file 要加载的 PDF 文件
     * @param onLoaded 加载成功时回调（带 PDDocument 参数）
     * @param onError 加载失败时回调（可选）
     */
    fun getDocument(
        file: File,
        password: String?,
        onLoaded: ((PDDocument) -> Unit)? = null,
        onError: ((Exception) -> Unit)? = null
    ): PDDocument {
        if (cachedDoc == null || cachedPath != file.absolutePath) {
            try {
                // 如果是新的文件，则关闭旧文档
                cachedDoc?.close()
                cachedDoc = if (!password.isNullOrEmpty()) {
                    PDDocument.load(file, password)
                } else {
                    PDDocument.load(file)
                }
                cachedPath = file.absolutePath

                // 通知加载成功
                onLoaded?.invoke(cachedDoc!!)
            } catch (e: Exception) {
                onError?.invoke(e)
                throw e
            }
        } else {
            // 已缓存文档，仍可调用回调
            onLoaded?.invoke(cachedDoc!!)
        }
        return cachedDoc!!
    }

    fun getCachedDoc(): PDDocument? {
        return cachedDoc
    }

    /**
     * 释放 PDF 资源。
     */
    fun closeCachedDocument() {
        try {
            cachedDoc?.close()
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            cachedDoc = null
            cachedPath = null
        }
    }

    /**
     * 主搜索方法
     * @param pdfFile 要搜索的 PDF 文件
     * @param key 搜索关键词
     * @param targetPage 可选参数：指定页码（从 0 开始）。如果为 null 则搜索整本。
     */
    fun searchText(pdfFile: File, key: String, targetPage: Int? = null,password: String?): List<TextSearchResult> {
        Log.d("ocean", "searchText pdfFile->$pdfFile")
        Log.d("ocean", "searchText key->$key")
        Log.d("ocean", "searchText targetPage->$targetPage")
        val results = mutableListOf<TextSearchResult>()

        try {
            // 这里不再每次重新 load
            val doc = if (!password.isNullOrEmpty()) {
                PDDocument.load(pdfFile, password)
            } else {
                PDDocument.load(pdfFile)
            }

            val startPage = targetPage ?: 0
            val endPage = targetPage ?: (doc.numberOfPages - 1)

            for (pageIndex in startPage..endPage) {
                val page = doc.getPage(pageIndex)
                val pageResults = searchInPage(doc, pageIndex, key, page)
                results.add(pageResults)
            }

        } catch (e: Exception) {
            e.printStackTrace()
        }

        currentSearchResults = results
        return results
    }

    private fun searchInPage(
        document: PDDocument,
        pageIndex: Int,
        searchText: String,
        page: PDPage
    ): TextSearchResult {
        val positions = mutableListOf<TextPositionInfo>()
        val searchTextLower = searchText.toLowerCase()
        val pageWidth = page.mediaBox.width
        val pageHeight = page.mediaBox.height

        val textStripper = object : PDFTextStripper() {
            private val currentPagePositions = mutableListOf<TextPositionInfo>()

            override fun writeString(text: String, textPositions: List<TextPosition>) {
                // 收集文本位置信息
                for (tp in textPositions) {
                    val positionInfo = TextPositionInfo(
                        text = tp.unicode,
                        x = tp.xDirAdj,
                        y = tp.yDirAdj,
                        width = tp.widthDirAdj,
                        height = tp.heightDir,
                        pageWidth = pageWidth,
                        pageHeight = pageHeight
                    )
                    currentPagePositions.add(positionInfo)
                }
                super.writeString(text, textPositions)
            }

            override fun endPage(page: PDPage?) {
                // 在页面结束时进行搜索匹配
                val matchedPositions = findTextMatches(currentPagePositions, searchTextLower)
                if (matchedPositions.isNotEmpty()) {
                    positions.addAll(matchedPositions)
                }
                currentPagePositions.clear()
                super.endPage(page)
            }
        }

        textStripper.startPage = pageIndex + 1
        textStripper.endPage = pageIndex + 1

        try {
            textStripper.getText(document)
        } catch (e: Exception) {
            e.printStackTrace()
        }

        return TextSearchResult(
            pageNumber = pageIndex + 1,
            text = searchText,
            positions = positions
        )
    }

    /**
     * 在一页的 TextPositionInfo 列表中搜索指定文本，并返回每个匹配的高亮区域。
     *
     * 逻辑说明：
     * 1. 按视觉顺序（纵向 y, 横向 x）排序字符。
     * 2. 根据 y 坐标将字符分组为“同一行”，避免跨行高亮。
     * 3. 对每一行独立进行逐字符匹配：
     *    - 清理不可打印字符（零宽空格、控制字符等）。
     *    - 如果匹配成功，则将匹配字符合并为一个矩形区域。
     *
     * @param positions 当前页的所有字符位置信息（TextPositionInfo）。
     * @param searchText 要搜索的文本（不区分大小写）。
     * @return 匹配的高亮区域列表，每个 TextPositionInfo 对象表示一段匹配文字。
     */
    private fun findTextMatches(
        positions: List<TextPositionInfo>,
        searchText: String
    ): List<TextPositionInfo> {
        val matches = mutableListOf<TextPositionInfo>()
        val chars = searchText.lowercase().toCharArray()

        // 按纵向 y、横向 x 排序，保证字符视觉顺序
        val sortedPositions = positions.sortedWith(compareBy({ it.y }, { it.x }))

        // 按行拆分字符，避免高亮跨行
        val lines = mutableListOf<MutableList<TextPositionInfo>>()
        var currentLine = mutableListOf<TextPositionInfo>()
        var lastY = sortedPositions.firstOrNull()?.y ?: 0f
        val lineThreshold = 2f // y 差值小于阈值则视为同一行，可根据字体大小微调

        for (pos in sortedPositions) {
            if (currentLine.isNotEmpty() && kotlin.math.abs(pos.y - lastY) > lineThreshold) {
                // y 坐标变化超过阈值，说明换行
                lines.add(currentLine)
                currentLine = mutableListOf()
            }
            currentLine.add(pos)
            lastY = pos.y
        }
        if (currentLine.isNotEmpty()) lines.add(currentLine)

        // 对每一行独立匹配搜索文本
        for (line in lines) {
            var i = 0
            while (i <= line.size - chars.size) {
                var matched = true
                val matchedPositions = mutableListOf<TextPositionInfo>()

                for (j in chars.indices) {
                    // 清理不可打印字符（零宽空格、控制字符等）
                    val posChar = line[i + j].text.replace(Regex("\\p{C}"), "").lowercase()
                    if (posChar != chars[j].toString()) {
                        matched = false
                        break
                    }
                    matchedPositions.add(line[i + j])
                }

                if (matched) {
                    // 合并匹配字符为矩形区域
                    matches.add(mergePositions(matchedPositions, searchText))
                    i += chars.size
                } else {
                    i++
                }
            }
        }

        return matches
    }

    private fun mergePositions(
        positions: List<TextPositionInfo>,
        originalText: String
    ): TextPositionInfo {
        val first = positions.first()
        val minX = positions.minByOrNull { it.x }?.x ?: first.x
        val maxX = positions.maxByOrNull { it.x + it.width }?.let { it.x + it.width }
            ?: (first.x + first.width)
        val minY = positions.minByOrNull { it.y }?.y ?: first.y
        val maxY = positions.maxByOrNull { it.y + it.height }?.let { it.y + it.height }
            ?: (first.y + first.height)

        return TextPositionInfo(
            text = originalText,
            x = minX,
            y = minY,
            width = maxX - minX,
            height = maxY - minY,
            pageWidth = first.pageWidth,
            pageHeight = first.pageHeight
        )
    }

    /**
     * 获取当前页面的高亮区域
     */
    fun getHighlightsForPage(page: Int): List<TextPositionInfo> {
        return currentSearchResults
            .find { it.pageNumber == page }
            ?.positions ?: emptyList()
    }

    /**
     * 清除所有高亮
     */
    fun clearHighlights() {
        currentSearchResults = emptyList()
        pdfView.invalidate()
        currentSearchText = null
        hasSearched = false
    }

    /**
     * 判断当前页是否有可搜索文字
     * @param doc 当前 PDF 文档
     * @param pageIndex 当前页索引
     * @param minChars 阈值：认为一页有文字的最少字符数
     */
    fun pageHasText(doc: PDDocument, pageIndex: Int, minChars: Int = 10): Boolean {
        var charCount = 0

        val stripper = object : PDFTextStripper() {
            override fun processTextPosition(text: TextPosition) {
                //太小的文字当没有
                if (text.width > 1 && text.height > 2) {
                    charCount++
                }
                // 如果超过阈值，直接停止
                if (charCount >= minChars) {
                    throw StopParsingException()
                }
            }
        }

        return try {
            stripper.startPage = pageIndex + 1
            stripper.endPage = pageIndex + 1
            stripper.getText(doc)
            // 解析完，如果字符数达不到阈值就返回 false
            charCount >= minChars
        } catch (e: StopParsingException) {
            // 遇到足够字符，认为有文字
            true
        } catch (e: Exception) {
            e.printStackTrace()
            false
        }
    }


    private class StopParsingException : RuntimeException()
}