import CoreGraphics
import Vision

struct RecognizedTextLine: Equatable {
    let text: String
    let confidence: Double
    let normalizedBounds: CGRect
}

struct OCRTextPayload: Equatable {
    let rawText: String
    let lines: [RecognizedTextLine]
    let averageConfidence: Double
}

protocol CardTextRecognizing {
    func recognizeText(in image: PreparedImage) async throws -> OCRTextPayload
}

enum VisionCardOCRServiceError: LocalizedError {
    case noRecognizedText

    var errorDescription: String? {
        switch self {
        case .noRecognizedText:
            return "Vision OCR could not find readable card text."
        }
    }
}

struct VisionCardOCRService: CardTextRecognizing {
    private let customWords = [
        "Pokémon", "Charizard", "Glurak", "Pikachu", "Blastoise", "Venusaur",
        "Illustration", "Scarlet", "Violet", "Trainer", "Holo", "Rare", "Ultra",
        "VMAX", "GX", "ex", "Base", "Set", "Promo", "Shiny"
    ]

    func recognizeText(in image: PreparedImage) async throws -> OCRTextPayload {
        try await withCheckedThrowingContinuation { continuation in
            let request = VNRecognizeTextRequest { request, error in
                if let error {
                    continuation.resume(throwing: error)
                    return
                }

                let observations = (request.results as? [VNRecognizedTextObservation]) ?? []
                let lines = observations.compactMap { observation -> RecognizedTextLine? in
                    guard let candidate = observation.topCandidates(1).first else { return nil }
                    return RecognizedTextLine(
                        text: candidate.string,
                        confidence: Double(candidate.confidence),
                        normalizedBounds: observation.boundingBox
                    )
                }

                guard !lines.isEmpty else {
                    continuation.resume(throwing: VisionCardOCRServiceError.noRecognizedText)
                    return
                }

                let average = lines.map(\.confidence).reduce(0, +) / Double(lines.count)
                continuation.resume(returning: OCRTextPayload(
                    rawText: lines.map(\.text).joined(separator: "\n"),
                    lines: lines,
                    averageConfidence: average
                ))
            }

            request.recognitionLevel = .accurate
            request.recognitionLanguages = ["en-US", "de-DE"]
            request.usesLanguageCorrection = true
            request.customWords = customWords

            let handler = VNImageRequestHandler(cgImage: image.analysisCGImage, options: [:])

            do {
                try handler.perform([request])
            } catch {
                continuation.resume(throwing: error)
            }
        }
    }
}
