import Cocoa

@MainActor
final class ArchiveOperationCoordinator {
    private static let updateInterval: TimeInterval = 0.2

    let session: SZOperationSession

    private let progressController: ProgressDialogController
    private weak var parentWindow: NSWindow?
    private let deferredDisplay: Bool
    private let showDeadline: Date?
    private var timer: Timer?
    private var isSheetVisible = false

    init(operationTitle: String,
         initialFileName: String? = nil,
         parentWindow: NSWindow? = nil,
         deferredDisplay: Bool = false)
    {
        session = SZOperationSession()
        progressController = ProgressDialogController()
        progressController.operationTitle = operationTitle
        progressController.beginWaitingMode(fileName: initialFileName)
        session.progressDelegate = progressController

        self.parentWindow = parentWindow
        self.deferredDisplay = deferredDisplay
        showDeadline = deferredDisplay
            ? Date().addingTimeInterval(ProgressDialogController.deferredPresentationDelay)
            : nil
        progressController.showRequestHandler = { [weak self] in
            self?.showProgressIfNeeded()
        }

        session.passwordRequestHandler = { [weak self] title, message, initialValue, passwordPointer in
            self?.prepareForPromptIfNeeded()

            guard let password = szPromptForPasswordSync(title: title,
                                                         message: message,
                                                         initialValue: initialValue)
            else {
                return false
            }

            passwordPointer?.pointee = password as NSString
            return true
        }

        session.choiceRequestHandler = { [weak self] style, title, message, buttonTitles in
            self?.prepareForPromptIfNeeded()
            return szRunChoiceDialog(title: title,
                                     message: message ?? "",
                                     style: SZDialogPresenter.dialogStyle(for: style),
                                     buttons: buttonTitles)
        }
    }

    func start() {
        // Use the block API so the timer does not retain the coordinator.
        let timer = Timer(timeInterval: Self.updateInterval, repeats: true) { [weak self] _ in
            MainActor.assumeIsolated {
                self?.updateFromSession()
            }
        }
        self.timer = timer
        RunLoop.main.add(timer, forMode: .common)

        if !deferredDisplay {
            showProgressIfNeeded()
        }

        updateFromSession()
    }

    func finish() {
        timer?.invalidate()
        timer = nil
        updateFromSession()

        hideProgressIfVisible()
    }

    func requestChoice(style: SZOperationPromptStyle,
                       title: String,
                       message: String,
                       buttonTitles: [String]) -> Int
    {
        session.requestChoice(with: style,
                              title: title,
                              message: message,
                              buttonTitles: buttonTitles)
    }

    @objc private func updateFromSession() {
        let snapshot = session.snapshot()

        if progressController.progressShouldCancel(), !snapshot.isCancellationRequested {
            session.requestCancel()
        }

        if shouldShowProgress(for: snapshot) {
            showProgressIfNeeded()
        }

        if !snapshot.currentFileName.isEmpty {
            progressController.progressDidUpdateFileName(snapshot.currentFileName)
        }
        if snapshot.hasReportedProgress {
            progressController.progressDidUpdate(snapshot.progressFraction)
        }
        if snapshot.bytesTotal > 0 {
            progressController.progressDidUpdateBytesCompleted(snapshot.bytesCompleted,
                                                               total: snapshot.bytesTotal)
        }
        if snapshot.filesCompleted > 0 {
            progressController.progressDidUpdateFilesCompleted(snapshot.filesCompleted)
        }
    }

    private func prepareForPromptIfNeeded() {
        showProgressIfNeeded()
    }

    private func showProgressIfNeeded() {
        if isSheetVisible {
            return
        }

        if let parentWindow = szSheetParentWindow(parentWindow),
           let progressWindow = progressController.window
        {
            parentWindow.beginSheet(progressWindow) { _ in }
            isSheetVisible = true
            return
        }

        progressController.showWindowNowIfNeeded()
    }

    private func hideProgressIfVisible() {
        guard let progressWindow = progressController.window else {
            return
        }

        if isSheetVisible,
           let parentWindow
        {
            parentWindow.endSheet(progressWindow)
            isSheetVisible = false
        }

        progressWindow.close()
    }

    private func shouldShowProgress(for snapshot: SZOperationSnapshot) -> Bool {
        if snapshot.isWaitingForUserInteraction {
            return true
        }

        guard deferredDisplay, let showDeadline else {
            return false
        }
        return Date() >= showDeadline
    }
}
