//
//  AlertOperation.swift
//  MVMCoreUI
//
//  Created by Scott Pfeil on 4/11/23.
//  Copyright © 2023 Verizon Wireless. All rights reserved.
//

import MVMCore
import Dispatch
import Combine

public class AlertOperation: MVMCoreOperation {
    
    private actor Properties {
        private var isDisplayed: Bool = false
        
        func set(displayed: Bool) {
            isDisplayed = displayed
        }
        
        func getIsDisplayed() -> Bool {
            return isDisplayed
        }
    }
    private var properties = Properties()
        
    public let alertController: AlertController
    
    public let alertObject: AlertObject

    /// For tracking isVisible changes of the alert controller.
    private var cancellable: Cancellable?
    
    /// Blocks the navigation queue to ensure no other navigation happens while an alert is displayed.
    private var blockingOperation: MVMCoreOperation?
    
    public init(with alert: AlertController, alertObject: AlertObject) {
        self.alertController = alert
        self.alertObject = alertObject
        super.init()
    }
    
    deinit {
        stopObservingAlertView()
    }
    
    public override func main() {
        guard !checkAndHandleForCancellation() else { return }
        
        // Observe for when it is removed.
        observeForCurrentAlertViewDismissal()
        
        Task(priority: .high) {
            guard let viewControllerToPresentOn = await NavigationHandler.shared().getViewControllerToPresentOn() else {
                markAsFinished()
                return
            }
            
            // Presents the alert.
            let presentationOperation = await NavigationOperation(with: .present(viewController: alertController, onController: viewControllerToPresentOn), tryToReplace: false)
            
            let blockingOperation = MVMCoreOperation()
            blockingOperation.addDependency(presentationOperation)
            self.blockingOperation = blockingOperation
            
            // Block other navigation until this alert is removed.
            NavigationHandler.shared().navigationQueue.addOperation(blockingOperation)
            
            await NavigationHandler.shared().navigate(with: presentationOperation)
            
            // We finished but it was not displayed yet. It's possible that it was cancelled. Finish this task
            if await !self.properties.getIsDisplayed() {
                self.markAsFinished()
            } else {
                (MVMCoreObject.sharedInstance()?.loggingDelegate as? MVMCoreUILoggingDelegateProtocol)?.logAlert(with: self.alertObject)
                if self.isCancelled {
                    await self.dismissAlertView()
                }
            }
        }
    }

    public override func cancel() {
        super.cancel()
        Task { @MainActor in
            self.alertObject.alertDelegate?.alertCancelled(self.alertController)
            await self.dismissAlertView()
        }
    }
    
    private func dismissAlertView() async {
        guard await properties.getIsDisplayed() else { return }
        await withCheckedContinuation { continuation in
            Task {
                let dismissOperation = await NavigationOperation(with: .dismiss(viewController: alertController))
                dismissOperation.queuePriority = .veryHigh
                let task = Task(priority: .high) { await NavigationHandler.shared().navigate(with: dismissOperation) }
                blockingOperation?.markAsFinished()
                _ = await task.result
                continuation.resume()
            }
        }
    }
    
    public override func markAsFinished() {
        blockingOperation?.markAsFinished()
        super.markAsFinished()
    }
    
    // MARK: Observer Functions

    private func observeForCurrentAlertViewDismissal() {
        stopObservingAlertView()
        cancellable = alertController.publisher(for: \AlertController.visible).sink() { [weak self] visible in
            guard let self = self else { return }
            Task {
                guard await self.properties.getIsDisplayed() != visible else { return }
                await self.properties.set(displayed: visible)
                Task { @MainActor in
                    if visible {
                        self.alertObject.alertDelegate?.alertShown(self.alertController)
                    } else {
                        self.alertObject.alertDelegate?.alertDismissed(self.alertController)
                        
                        // Is visible was set to NO, meaning that the alertview is no longer visible.
                        self.stopObservingAlertView()
                        self.markAsFinished()
                    }
                }
            }
        }
    }

    private func stopObservingAlertView() {
        cancellable?.cancel()
    }
}
