import OpenClawKit
import SwiftUI
import VisionKit

enum QRScannerResult: Equatable {
    case gatewayLink(GatewayConnectDeepLink)
    case setupCode(String)
}

@MainActor
struct GatewayPendingTargetSuppression {
    enum Owner: Equatable {
        case qrScanner
        case setupLink
    }

    private var value: (owner: Owner, lease: GatewayConnectionController.AutoConnectSuppressionLease)?

    mutating func replace(
        owner: Owner,
        lease: GatewayConnectionController.AutoConnectSuppressionLease)
    {
        self.value = (owner, lease)
    }

    mutating func take(ifOwnedBy owner: Owner? = nil) -> GatewayConnectionController.AutoConnectSuppressionLease? {
        guard let value = self.value else { return nil }
        if let owner, value.owner != owner { return nil }
        self.value = nil
        return value.lease
    }

    mutating func resumeAutoConnect(_ owner: Owner? = nil, controller: GatewayConnectionController) {
        guard let lease = self.take(ifOwnedBy: owner) else { return }
        controller.resumeAutoConnect(after: lease)
    }

    mutating func releaseAutoConnect(_ owner: Owner? = nil, controller: GatewayConnectionController) {
        guard let lease = self.take(ifOwnedBy: owner) else { return }
        controller.releaseAutoConnectSuppression(after: lease)
    }
}

@MainActor
final class QRScannerResultHandoff {
    /// SwiftUI's onDismiss can precede VisionKit's AV capture teardown. Delay
    /// pairing UI briefly so it cannot race the scanner's camera shutdown.
    static let defaultSettlingNanoseconds: UInt64 = 1_200_000_000

    private let settlingNanoseconds: UInt64
    private var pendingResult: QRScannerResult?
    private var deliveryTask: Task<Void, Never>?
    private var activeScanID: UInt64 = 0

    init(settlingNanoseconds: UInt64 = QRScannerResultHandoff.defaultSettlingNanoseconds) {
        self.settlingNanoseconds = settlingNanoseconds
    }

    @discardableResult
    func beginScan() -> UInt64 {
        self.cancel()
        return self.activeScanID
    }

    func isActive(scanID: UInt64) -> Bool {
        scanID == self.activeScanID && self.pendingResult == nil
    }

    @discardableResult
    func queue(_ result: QRScannerResult, scanID: UInt64) -> Bool {
        // Camera and Photos can finish together; the first valid result owns this scan.
        guard self.isActive(scanID: scanID) else { return false }
        self.pendingResult = result
        return true
    }

    @discardableResult
    func processAfterDismissal(
        _ process: @escaping @MainActor (QRScannerResult) -> Void) -> Task<Void, Never>?
    {
        guard let result = self.pendingResult else {
            self.cancel()
            return nil
        }
        self.pendingResult = nil
        self.activeScanID &+= 1
        self.deliveryTask?.cancel()
        let settlingNanoseconds = self.settlingNanoseconds
        let task = Task { @MainActor in
            do {
                try await Task.sleep(nanoseconds: settlingNanoseconds)
            } catch {
                return
            }
            process(result)
        }
        self.deliveryTask = task
        return task
    }

    func cancel() {
        self.activeScanID &+= 1
        self.deliveryTask?.cancel()
        self.deliveryTask = nil
        self.pendingResult = nil
    }
}

struct QRScannerView: UIViewControllerRepresentable {
    let onResult: (QRScannerResult) -> Void
    let onError: (String) -> Void
    let onDismiss: () -> Void

    func makeUIViewController(context: Context) -> UIViewController {
        guard DataScannerViewController.isSupported else {
            context.coordinator.reportError("QR scanning is not supported on this device.")
            return UIViewController()
        }
        guard DataScannerViewController.isAvailable else {
            context.coordinator.reportError("Camera scanning is currently unavailable.")
            return UIViewController()
        }
        return QRScannerContainerViewController(coordinator: context.coordinator)
    }

    func updateUIViewController(_: UIViewController, context _: Context) {}

    static func dismantleUIViewController(_ uiViewController: UIViewController, coordinator: Coordinator) {
        if let scanner = uiViewController as? QRScannerContainerViewController {
            scanner.stopScannerCapture()
        }
        coordinator.parent.onDismiss()
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }

    final class QRScannerContainerViewController: UIViewController {
        private let coordinator: Coordinator
        private let scanner: DataScannerViewController
        private var didStartScanning = false

        init(coordinator: Coordinator) {
            self.coordinator = coordinator
            self.scanner = DataScannerViewController(
                recognizedDataTypes: [.barcode(symbologies: [.qr])],
                isHighlightingEnabled: true)
            super.init(nibName: nil, bundle: nil)
            self.scanner.delegate = coordinator
        }

        @available(*, unavailable)
        required init?(coder _: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }

        override func viewDidLoad() {
            super.viewDidLoad()
            self.view.backgroundColor = .systemBackground
            self.addChild(self.scanner)
            self.scanner.view.translatesAutoresizingMaskIntoConstraints = false
            self.view.addSubview(self.scanner.view)
            NSLayoutConstraint.activate([
                self.scanner.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
                self.scanner.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
                self.scanner.view.topAnchor.constraint(equalTo: self.view.topAnchor),
                self.scanner.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
            ])
            self.scanner.didMove(toParent: self)
        }

        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            // VisionKit owns the camera session; start it only after UIKit has
            // presented the scanner so sheet teardown cannot race construction.
            self.startScanningIfNeeded()
        }

        override func viewWillDisappear(_ animated: Bool) {
            self.stopScannerCapture()
            super.viewWillDisappear(animated)
        }

        func stopScannerCapture() {
            self.scanner.stopScanning()
            self.didStartScanning = false
        }

        private func startScanningIfNeeded() {
            guard !self.didStartScanning else { return }
            do {
                try self.scanner.startScanning()
                self.didStartScanning = true
            } catch {
                self.coordinator.reportError("Could not start QR scanner.")
            }
        }
    }

    final class Coordinator: NSObject, DataScannerViewControllerDelegate {
        let parent: QRScannerView
        private var handled = false
        private var reportedError = false

        init(parent: QRScannerView) {
            self.parent = parent
        }

        func reportError(_ message: String) {
            guard !self.reportedError else { return }
            self.reportedError = true
            Task { @MainActor in
                self.parent.onError(message)
            }
        }

        func dataScanner(
            _ scanner: DataScannerViewController,
            didAdd items: [RecognizedItem],
            allItems _: [RecognizedItem])
        {
            guard !self.handled else { return }
            for item in items {
                guard case let .barcode(barcode) = item,
                      let payload = barcode.payloadStringValue
                else { continue }

                if let link = GatewayConnectDeepLink.fromSetupInput(payload) {
                    self.deliver(.gatewayLink(link), scanner: scanner)
                    return
                }
                if AppleReviewDemoMode.isSetupCode(payload) {
                    self.deliver(.setupCode(payload), scanner: scanner)
                    return
                }
            }
        }

        private func deliver(_ result: QRScannerResult, scanner: DataScannerViewController) {
            self.handled = true
            // DataScannerViewController has no teardown-completion callback. Stop capture
            // before owners dismiss the sheet and later present pairing UI.
            scanner.stopScanning()
            Task { @MainActor in
                self.parent.onResult(result)
            }
        }

        func dataScanner(_: DataScannerViewController, didRemove _: [RecognizedItem], allItems _: [RecognizedItem]) {}

        func dataScanner(
            _: DataScannerViewController,
            becameUnavailableWithError _: DataScannerViewController.ScanningUnavailable)
        {
            self.reportError("Camera is not available on this device.")
        }
    }
}
