import AppKit

/// File ▸ Share…'s AppKit half — `NSSharingServicePicker`, shown over the staged `.zip`
/// (`BoardShareStager`) and torn down once its interaction has genuinely finished (design ruling
/// 2026-08-09, card 72691b11).
///
/// **Deliberately untested**, `PrintCommand`'s own line drawn in the same place: "The picker
/// presentation itself stays untested (AppKit boundary, consistent with Print's own boundary)".
/// What is tested is everything on the other side of that boundary — `BoardShareStager`'s staging
/// (pure `FileManager`/`Process` work) and `ShareBoardCommand.isEnabled` (a pure function of three
/// facts) — so this file is deliberately thin: it owns exactly the two things that need `NSApp` and
/// a live window to exist at all, presentation and cleanup timing, and nothing else.
///
/// ### Anchor: the toolbar, or the window's center
///
/// The ruling's own words: "anchored to the board window's toolbar (or window center fallback)".
/// AppKit does not expose a public way to fetch a *specific* `NSToolbarItem`'s view for a
/// customizable toolbar whose items the user may have rearranged or removed (`WindowToolbarController`
/// builds items from a user-editable configuration; there is no "the Share button" to point at,
/// because the ruling asks for a menu row, not a toolbar item) — so the closest honest reading of
/// "anchored to the toolbar" reachable through public API is the content view's top-trailing
/// corner, immediately below wherever the toolbar strip is drawn, when a toolbar is showing at all.
/// **Flagged for owner review** (the card's DECISIONS comment): a real toolbar-item anchor would
/// need a dedicated Share toolbar button, which the ruling's Surface bullet does not ask for.
@MainActor
enum BoardShareAnchor {

    /// The view and rect `NSSharingServicePicker.show(relativeTo:of:preferredEdge:)` wants —
    /// window center whenever there is no window or no visible toolbar to approximate.
    static func anchor(in window: NSWindow?) -> (view: NSView, rect: NSRect, edge: NSRectEdge) {
        guard let window, let contentView = window.contentView else {
            // A command validated against the focus system (`ShareBoardCommand` requires a
            // focused board window) should never actually reach this — pure defensive fallback so
            // the picker still has *some* view to anchor to rather than a crash.
            let view = NSView()
            return (view, view.bounds, .minY)
        }

        let bounds = contentView.bounds
        guard window.toolbar?.isVisible == true else {
            let center = NSRect(x: bounds.midX, y: bounds.midY, width: 1, height: 1)
            return (contentView, center, .minY)
        }

        let corner = NSRect(x: bounds.maxX - 1, y: bounds.maxY - 1, width: 1, height: 1)
        return (contentView, corner, .maxY)
    }
}

/// One picker, one staged archive, one cleanup — self-retained until the interaction is over,
/// `PrintCommand.swift`'s `PrintCompletion` doing the identical trick for the identical reason:
/// `NSSharingServicePicker` (like `NSPrintOperation`) holds its delegate weakly, and the object
/// presenting it is otherwise a local that would deallocate the instant `present(anchorWindow:)`
/// returns, before the user has clicked anything in the popover it just opened.
///
/// ### When the cleanup actually runs
///
/// Three terminal states, and cleanup on all three — the ruling's "removed after the picker
/// dismisses or errors", read as *the interaction concluding* rather than only the popover
/// closing, because a chosen service's own work (Mail composing a message, AirDrop's transfer) can
/// genuinely outlive the small popover UI disappearing:
///
/// - **Dismissed without choosing** (`didChoose: nil`) — nothing will ever read the file; clean up
///   immediately.
/// - **The chosen service failed** (`didFailToShareItems`) — nothing shared; clean up.
/// - **The chosen service succeeded** (`didShareItems`) — the recipient side has what it needs
///   (Mail copied the attachment into the draft, AirDrop's transfer completed); clean up.
///
/// `sharingServicePicker(_:delegateFor:)` — not setting `service.delegate` inside `didChoose` — is
/// the documented hook for supplying the delegate the picker's own internal `perform()` call will
/// use, and it is what makes the two service callbacks below reachable at all.
// `NSSharingServicePickerDelegate`/`NSSharingServiceDelegate` are not themselves `@MainActor` in
// the SDK overlay (unlike `NSWindowDelegate`, which `HostedWindowController` conforms to with no
// such note) — the conformance is isolated explicitly here, which is safe by construction: AppKit
// invokes every one of these callbacks on the main thread, they touch nothing but this object's own
// `archive`/`untilTheInteractionEnds`, and `present(anchorWindow:)` is only ever called from the
// share command's own `@MainActor` task.
@MainActor
final class BoardSharePresentation: NSObject, @MainActor NSSharingServicePickerDelegate, @MainActor NSSharingServiceDelegate {

    private let archive: BoardShareStager.StagedArchive
    private var untilTheInteractionEnds: BoardSharePresentation?

    init(archive: BoardShareStager.StagedArchive) {
        self.archive = archive
        super.init()
        untilTheInteractionEnds = self
    }

    /// Shows the picker over `archive.zipURL`, anchored per `BoardShareAnchor`.
    func present(anchorWindow: NSWindow?) {
        let picker = NSSharingServicePicker(items: [archive.zipURL])
        picker.delegate = self
        let (view, rect, edge) = BoardShareAnchor.anchor(in: anchorWindow)
        picker.show(relativeTo: rect, of: view, preferredEdge: edge)
    }

    // MARK: - NSSharingServicePickerDelegate

    func sharingServicePicker(
        _ sharingServicePicker: NSSharingServicePicker,
        didChoose service: NSSharingService?
    ) {
        guard service != nil else {
            // The user closed the popover without picking anything — nothing is going to read the
            // staged file from here on.
            cleanup()
            return
        }
        // Left non-nil deliberately: `delegateFor:` below is what actually wires the completion
        // callbacks onto the service the picker is about to perform.
    }

    func sharingServicePicker(
        _ sharingServicePicker: NSSharingServicePicker,
        delegateFor sharingService: NSSharingService
    ) -> (any NSSharingServiceDelegate)? {
        self
    }

    // MARK: - NSSharingServiceDelegate

    func sharingService(_ sharingService: NSSharingService, didShareItems items: [Any]) {
        cleanup()
    }

    func sharingService(_ sharingService: NSSharingService, didFailToShareItems items: [Any], error: any Error) {
        cleanup()
    }

    private func cleanup() {
        BoardShareStager.cleanup(archive)
        untilTheInteractionEnds = nil
    }
}
