// SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11

//
//  MRZScanner.swift

import Vision
import UIKit

struct MRZScanner {
    static func scan(image: UIImage, roi: CGRect? = nil, completion: @escaping (String, [CGRect]) -> Void) {
        guard let cgImage = image.cgImage else {
            completion("Image not valid", [])
            return
        }

        let request = VNRecognizeTextRequest { (request, error) in
            if let error = error {
                print("Vision error: \(error)")
            }
            guard let observations = request.results as? [VNRecognizedTextObservation] else {
                print("No text observations found")
                completion("No text found", [])
                return
            }

            // print("Found \(observations.count) text observations")

            var mrzLines: [String] = []
            var boxes: [CGRect] = []

            // Sort lines from top to bottom
            let sortedObservations = observations.sorted { $0.boundingBox.minY > $1.boundingBox.minY }

            for (index, obs) in sortedObservations.enumerated() {
                if let candidate = obs.topCandidates(1).first {
                    let text = candidate.string
                    let confidence = candidate.confidence
                    // print("Line \(index): '\(text)' (confidence: \(confidence), position: \(obs.boundingBox))")

                    // Check if this looks like an MRZ line (either contains "<" or matches MRZ pattern)
                    // TD1 format (ID cards): 30 chars, TD3 format (passports): 44 chars
                    if text.contains("<") ||
                       text.matches(pattern: "^[A-Z0-9<]{30}$") || //TD1 //case where there's no '<' in MRZ
                       text.matches(pattern: "^[A-Z0-9<]{44}$")  //TD3
                       {
                        // print("Matched MRZ pattern: \(text)")
                        mrzLines.append(text)
                        boxes.append(obs.boundingBox)
                        // Check if we have a complete MRZ
                        if (mrzLines.count == 2 && mrzLines.allSatisfy { $0.count == 44 }) || // TD3 - passport
                           (mrzLines.count == 3 && mrzLines.allSatisfy { $0.count == 30 }) {  // TD1 - ID card
                            break
                        }
                    } else {
                        // print("Did not match MRZ pattern: \(text)")
                    }
                }
            }

            if mrzLines.isEmpty {
                print("No MRZ lines found")
                completion("", [])
            } else {
                print("Found \(mrzLines.count) MRZ lines")
                completion(mrzLines.joined(separator: "\n"), boxes)
            }
        }
        request.recognitionLevel = .accurate
        request.usesLanguageCorrection = false
        request.recognitionLanguages = ["en"]

        // Use provided ROI. If not use as bottom 20%
        if let roi = roi {
            // print("[MRZScanner] Using provided ROI: \(roi) (image size: \(cgImage.width)x\(cgImage.height))")
            request.regionOfInterest = roi
        } else {
            let imageHeight = CGFloat(cgImage.height)
            let roiHeight = imageHeight * 0.2 // Bottom 20%
            let defaultRoi = CGRect(x: 0, y: 0, width: 1.0, height: roiHeight / imageHeight)
            // print("[MRZScanner] Using default ROI: \(defaultRoi) (image size: \(cgImage.width)x\(cgImage.height), roi height: \(roiHeight))")
            request.regionOfInterest = defaultRoi
        }

        let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
        DispatchQueue.global(qos: .userInitiated).async {
            do {
                try handler.perform([request])
            } catch {
                print("Failed to perform recognition: \(error)")
            }
        }
    }
}

extension String {
    func matches(pattern: String) -> Bool {
        return range(of: pattern, options: .regularExpression) != nil
    }
}
