import Foundation
import Vision
import UIKit

enum OCRError: Error {
    case noImage
}

/// 端侧文字识别(Apple Vision,100% 本地,无网络)。
/// 用于「记录指标 · 拍照识别」:VL 直接读密集小字不稳,改为先 OCR 出文本,再交 LLM 结构化。
enum OCRService {

    /// 识别图中文字,按阅读顺序(自上而下、行内自左而右)拼成纯文本。
    /// 中英文混排;表格行会尽量保持在同一行,便于 LLM 把「指标名 数值 范围 单位」对齐解析。
    static func recognizeText(in cgImage: CGImage) async throws -> String {
        try await withCheckedThrowingContinuation { (cont: CheckedContinuation<String, Error>) in
            DispatchQueue.global(qos: .userInitiated).async {
                let request = VNRecognizeTextRequest()
                request.recognitionLevel = .accurate
                request.usesLanguageCorrection = true
                // 中文(简/繁)+ 英文;化验单常见中英文与数字混排。
                request.recognitionLanguages = ["zh-Hans", "zh-Hant", "en-US"]
                let handler = VNImageRequestHandler(cgImage: cgImage, orientation: .up, options: [:])
                do {
                    try handler.perform([request])
                    let obs = request.results ?? []
                    cont.resume(returning: assemble(obs))
                } catch {
                    cont.resume(throwing: error)
                }
            }
        }
    }

    /// UIImage 便捷入口。
    static func recognizeText(in image: UIImage) async throws -> String {
        guard let cg = image.cgImage else { throw OCRError.noImage }
        return try await recognizeText(in: cg)
    }

    /// 把散落的 observation 还原成阅读顺序文本。
    /// Vision 坐标系原点在左下、y 向上;按 midY 降序分行(同行 y 接近),行内按 minX 升序。
    private static func assemble(_ obs: [VNRecognizedTextObservation]) -> String {
        let items: [(rect: CGRect, text: String)] = obs.compactMap { o in
            guard let t = o.topCandidates(1).first?.string, !t.isEmpty else { return nil }
            return (o.boundingBox, t)
        }
        guard !items.isEmpty else { return "" }

        let sorted = items.sorted { $0.rect.midY > $1.rect.midY }
        let yTol: CGFloat = 0.012   // 行高容差(归一化坐标);同一行的 cell midY 差异通常 < 此值
        var rows: [[(rect: CGRect, text: String)]] = []
        var rowY: [CGFloat] = []    // 各行内 midY 的运行平均,做锚点比单看首元素更稳(抗轻微行漂移)
        for item in sorted {
            if let i = rows.indices.last, abs(rowY[i] - item.rect.midY) < yTol {
                rows[i].append(item)
                rowY[i] = (rowY[i] * CGFloat(rows[i].count - 1) + item.rect.midY) / CGFloat(rows[i].count)
            } else {
                rows.append([item])
                rowY.append(item.rect.midY)
            }
        }
        return rows.map { row in
            row.sorted { $0.rect.minX < $1.rect.minX }
                .map(\.text)
                .joined(separator: "  ")
        }.joined(separator: "\n")
    }
}
