// Sources/UI/Pairing/QRScannerView.swift
// T-2.2: AVFoundation QR scanner wrapped as UIViewRepresentable

import AVFoundation
import SwiftUI

/// A fullscreen AVFoundation-backed QR code scanner.
///
/// Calls `onScan` exactly once with the raw QR string, then stops the
/// capture session so the caller can drive the next state transition.
struct QRScannerView: UIViewRepresentable {

    /// Called on the **main actor** with the raw decoded QR string.
    let onScan: @MainActor (String) -> Void

    // MARK: UIViewRepresentable

    func makeCoordinator() -> Coordinator {
        Coordinator(onScan: onScan)
    }

    func makeUIView(context: Context) -> PreviewView {
        let view = PreviewView()
        let coordinator = context.coordinator

        let session = coordinator.session

        // Input
        guard
            let device = AVCaptureDevice.default(for: .video),
            let input = try? AVCaptureDeviceInput(device: device)
        else {
            return view
        }

        guard session.canAddInput(input) else { return view }
        session.addInput(input)

        // Output
        let output = AVCaptureMetadataOutput()
        guard session.canAddOutput(output) else { return view }
        session.addOutput(output)

        output.setMetadataObjectsDelegate(coordinator,
                                          queue: DispatchQueue.main)
        output.metadataObjectTypes = [.qr]

        // Preview layer
        let previewLayer = AVCaptureVideoPreviewLayer(session: session)
        previewLayer.videoGravity = .resizeAspectFill
        view.previewLayer = previewLayer
        view.layer.addSublayer(previewLayer)

        coordinator.previewLayer = previewLayer

        // Start capture on a background thread to avoid blocking the main queue.
        DispatchQueue.global(qos: .userInitiated).async { [session] in
            session.startRunning()
        }

        return view
    }

    func updateUIView(_ uiView: PreviewView, context: Context) {
        // Layout is handled inside PreviewView.layoutSubviews.
    }

    static func dismantleUIView(_ uiView: PreviewView, coordinator: Coordinator) {
        coordinator.stop()
    }

    // MARK: - Coordinator

    @MainActor
    final class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {

        let session = AVCaptureSession()
        var previewLayer: AVCaptureVideoPreviewLayer?

        private let onScan: @MainActor (String) -> Void
        private var hasScanned = false

        init(onScan: @MainActor @escaping (String) -> Void) {
            self.onScan = onScan
        }

        // Called on main queue (set via setMetadataObjectsDelegate).
        nonisolated func metadataOutput(
            _ output: AVCaptureMetadataOutput,
            didOutput metadataObjects: [AVMetadataObject],
            from connection: AVCaptureConnection
        ) {
            nonisolated(unsafe) let objects = metadataObjects
            MainActor.assumeIsolated {
                guard !hasScanned else { return }
                guard
                    let object = objects.first as? AVMetadataMachineReadableCodeObject,
                    object.type == .qr,
                    let string = object.stringValue
                else { return }

                hasScanned = true
                stop()
                onScan(string)
            }
        }

        func stop() {
            guard session.isRunning else { return }
            DispatchQueue.global(qos: .userInitiated).async { [session] in
                session.stopRunning()
            }
        }
    }

    // MARK: - PreviewView

    /// UIView subclass that keeps the `AVCaptureVideoPreviewLayer` sized to its bounds.
    final class PreviewView: UIView {
        var previewLayer: AVCaptureVideoPreviewLayer?

        override func layoutSubviews() {
            super.layoutSubviews()
            previewLayer?.frame = bounds
        }
    }
}
