//
//  CameraSession+CodeScanner.swift
//  VisionCamera
//
//  Created by Marc Rousavy on 11.10.23.
//  Copyright © 2023 mrousavy. All rights reserved.
//

import AVFoundation
import Foundation

extension CameraSession: AVCaptureMetadataOutputObjectsDelegate {
  public func metadataOutput(_: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from _: AVCaptureConnection) {
    guard let onCodeScanned = delegate?.onCodeScanned else {
      // No delegate callback
      return
    }
    guard !metadataObjects.isEmpty else {
      // No codes detected
      return
    }
    guard let device = videoDeviceInput?.device else {
      // No cameraId set
      return
    }
    let size = device.activeFormat.videoDimensions

    // Map codes to JS values
    let codes = metadataObjects.map { object in
      var value: String?
      var corners: [CGPoint]?
      if let code = object as? AVMetadataMachineReadableCodeObject {
        value = code.stringValue
        corners = code.corners.map {
          CGPoint(x: $0.x * Double(size.width), y: $0.y * Double(size.height))
        }
      }
      let x = object.bounds.origin.x * Double(size.width)
      let y = object.bounds.origin.y * Double(size.height)
      let w = object.bounds.width * Double(size.width)
      let h = object.bounds.height * Double(size.height)
      let frame = CGRect(x: x, y: y, width: w, height: h)

      return Code(type: object.type, value: value, frame: frame, corners: corners)
    }

    // Call delegate (JS) event
    onCodeScanned(codes, CodeScannerFrame(width: size.width, height: size.height))
  }

  /**
   A scanned QR/Barcode.
   */
  struct Code {
    /**
     Type of the scanned Code
     */
    let type: AVMetadataObject.ObjectType
    /**
     Decoded value of the code
     */
    let value: String?
    /**
     Location of the code on-screen, relative to the video output layer
     */
    let frame: CGRect

    /**
      Location of the code on-screen, relative to the video output layer
     */
    let corners: [CGPoint]?

    /**
     Converts this Code to a JS Object (Dictionary)
     */
    func toJSValue() -> [String: AnyHashable] {
      return [
        "type": type.descriptor,
        "value": value,
        "frame": [
          "x": frame.origin.x,
          "y": frame.origin.y,
          "width": frame.size.width,
          "height": frame.size.height,
        ],
        "corners": corners?.map { [
          "x": $0.x,
          "y": $0.y,
        ] } ?? [],
      ]
    }
  }

  struct CodeScannerFrame {
    let width: Int32
    let height: Int32

    func toJSValue() -> [String: AnyHashable] {
      return [
        "width": width,
        "height": height,
      ]
    }
  }
}