import Combine
import Foundation
import SwiftUI

enum ScanRoute: Hashable {
    case result
    case manual
}

@MainActor
final class ScanFlowModel: ObservableObject {
    @Published var path: [ScanRoute] = []
    @Published var currentSession: RecognitionSession?
    @Published var isRecognizing = false
    @Published var transientMessage: String?

    let cameraService: CameraService
    let photoLibraryService: PhotoLibraryService
    let networkMonitor: NetworkMonitor

    private let pipeline: CardRecognitionPipeline

    init() {
        let cameraService = CameraService()
        let photoLibraryService = PhotoLibraryService()
        let networkMonitor = NetworkMonitor()

        self.cameraService = cameraService
        self.photoLibraryService = photoLibraryService
        self.networkMonitor = networkMonitor
        self.pipeline = CardRecognitionPipeline(
            networkStatusProvider: networkMonitor,
            cloudOCRClient: StubCloudOCRClient(),
            fallbackOCR: VisionCardOCRService(),
            enhancer: NoOpCardFieldEnhancer()
        )
    }

    func startObservers() {
        networkMonitor.startMonitoring()
    }

    func stopObservers() {
        networkMonitor.stopMonitoring()
        cameraService.stopSession()
    }

    func prepareVisibleServices() async {
        await cameraService.prepareIfAuthorized()
        await photoLibraryService.refreshRecentsIfPossible()
    }

    func enableCamera() async {
        await cameraService.requestAccessAndStart()
    }

    func capturePhoto() async {
        do {
            isRecognizing = true
            let image = try await cameraService.capturePhoto()
            try await recognize(image: image)
        } catch {
            transientMessage = error.localizedDescription
        }
        isRecognizing = false
    }

    func importRecentPhoto(_ item: RecentPhotoItem) async {
        isRecognizing = true
        defer { isRecognizing = false }

        guard let image = await photoLibraryService.loadImage(for: item) else {
            transientMessage = "The selected photo could not be loaded."
            return
        }

        do {
            try await recognize(image: image)
        } catch {
            currentSession = RecognitionSession(
                draft: CardRecognitionDraft.manualPrefill(rawText: currentSession?.draft.rawText ?? ""),
                thumbnailJPEGData: currentSession?.thumbnailJPEGData
            )
            path = [.manual]
            transientMessage = error.localizedDescription
        }
    }

    func startManualEntry() {
        if currentSession == nil {
            currentSession = RecognitionSession(draft: .manualPrefill())
        } else {
            currentSession?.draft.source = .manual
            currentSession?.draft.confidence = .low
        }
        path = [.manual]
    }

    func openManualEntryFromResult() {
        guard currentSession != nil else {
            startManualEntry()
            return
        }
        currentSession?.draft.source = .manual
        if path.last != .manual {
            path.append(.manual)
        }
    }

    func updateDraft(_ draft: CardRecognitionDraft) {
        currentSession?.draft = draft
    }

    func returnToScan(message: String? = nil) {
        currentSession = nil
        path = []
        transientMessage = message
    }

    private func recognize(image: UIImage) async throws {
        let session = try await pipeline.recognizeCard(in: image)
        currentSession = session

        if session.draft.hasDetectedContent {
            path = [.result]
        } else {
            currentSession?.draft.source = .manual
            currentSession?.draft.confidence = .low
            path = [.manual]
        }
    }
}
