import SwiftUI

/// Presents `DirtyBufferGuard`'s blocked phase as the app's one modal moment on the write-failure
/// path (02-architecture.md § Write-failure surfacing).
///
/// ### The three choices, and why there is no fourth
///
/// Retry, save a copy elsewhere, discard. There is deliberately **no Cancel** — no "keep the window
/// open and think about it": the close is already stopped, so a fourth button would only mean
/// "stop asking", which is the silent failure this alert exists to prevent. Dismissing the alert
/// without choosing leaves the phase blocked and the alert comes back; the only ways out are the
/// three that put the text somewhere or knowingly let it go.
///
/// ### The copy destination
///
/// `copyDestination` supplies the URL. In a real window that is an `NSSavePanel` (or a
/// `fileExporter`) run from the button; here it is a closure so the flow is testable and so this
/// modifier stays free of file-picking machinery. Returning `nil` means the user backed out of the
/// panel — the phase stays blocked and the alert returns, which is the honest outcome.
///
/// ### Callers
///
/// m6's editor sessions (the card window's body and raw-source buffers) and m4's board-close
/// flush. Both attach this to the window that is trying to close.
public extension View {
    func dirtyBufferAlert(
        _ bufferGuard: DirtyBufferGuard,
        copyDestination: @escaping @MainActor () -> URL?
    ) -> some View {
        modifier(DirtyBufferAlertModifier(bufferGuard: bufferGuard, copyDestination: copyDestination))
    }
}

private struct DirtyBufferAlertModifier: ViewModifier {

    let bufferGuard: DirtyBufferGuard
    let copyDestination: @MainActor () -> URL?

    func body(content: Content) -> some View {
        content.alert(
            "Your changes couldn't be saved",
            isPresented: Binding(
                // A getter over the phase and a setter that does nothing: SwiftUI writes `false`
                // when the alert is dismissed by any route it manages, and honouring that would
                // close the window with the text still nowhere but memory. Only the three buttons
                // move the phase, so only they can take the alert down.
                get: { bufferGuard.phase != .idle },
                set: { _ in }
            ),
            presenting: blockingError
        ) { _ in
            Button("Try Again") {
                bufferGuard.retry()
            }
            Button("Save a Copy…") {
                guard let url = copyDestination() else { return }
                // A failed copy rethrows into a still-blocked phase, so the alert simply returns —
                // the same place the user already was, with nothing lost. There is no second error
                // surface to build here: this *is* the error surface.
                try? bufferGuard.saveCopy(to: url)
            }
            Button("Discard Changes", role: .destructive) {
                bufferGuard.discard()
            }
        } message: { error in
            Text("\(BannerCenter.headline(for: error))\n\nSave a copy somewhere else, or discard the changes to close.")
        }
    }

    private var blockingError: BoardWriteError? {
        if case let .blocked(error) = bufferGuard.phase { error } else { nil }
    }
}
