import SwiftUI

// MARK: - The shared shape

/// The shape behind every menu row this milestone ships **before** the window that answers it.
///
/// 11-command-nexus.md's own contract runs both directions: "a command absent here doesn't exist, and
/// adding one means adding a row here first" — so once a row *is* in the Nexus, shipping the window
/// behind it is a validation-and-action change, not a menu change. `FutureCommand` below is that
/// reading, applied: the row exists now, stably titled and stably chorded — `NSUserKeyEquivalents`
/// already resolves it, so a user can remap it today — with validation pinned to `false` and the
/// action a no-op until the milestone named at the call site fills both in. That milestone's whole
/// diff then reads as "flip `.disabled`, fill the closure" rather than "add a menu item", which is
/// also why every call site below carries the codebase's `mN-` marker for a component still owed.
///
/// **The title never moves once a row ships**, disabled or not: a command wired live later must not
/// gain a second spelling on the way (04-interactions.md ▸ Configurable bindings — "toggles keep one
/// stable title, checkmark state only" — which applies to a row that has not started ticking yet
/// exactly as it does to one that has).
///
/// There was a `FutureToggleCommand` beside this — a disabled `Toggle` for a checkmark row — and it
/// went with the last of its call sites (Raw Source, `RawSourceCommand`). Every row still owed is a
/// plain command; a future checkmark row brings its scaffold back with it rather than keeping an
/// unused one warm.
struct FutureCommand: View {
    let title: String
    var key: KeyEquivalent?
    var modifiers: EventModifiers = .command

    var body: some View {
        Button(title) {
            // No-op: the milestone named at the call site wires this in.
        }
        .keyboardShortcut(key.map { KeyboardShortcut($0, modifiers: modifiers) })
        .disabled(true)
    }
}

// File ▸ Save as Template was the scaffold here and is now live, beside the command it mirrors
// (`SaveAsTemplateCommand`, in `AppCommands.swift` next to `DuplicateBoardCommand`, whose flush →
// cancellable copy → banner sequence it repeats with a different destination). The diff this file
// predicts, once more: the title did not move, the chord stayed absent, the validation and the
// action filled in — the last of them being 09's carve-out from the read-only lock, which is why it
// could not simply borrow Duplicate's predicate.
//
// File ▸ Add Attachment… (⇧⌘A) was the scaffold here and is now live, beside the focused value it
// reads (`AddAttachmentCommand`, in `CardAttachments.swift`) — the diff this file predicts: the
// title and the chord did not move, the validation and the action filled in. The attachment row's
// context menu (Open / Remove / Reveal in Finder) shipped with it, on the row view that now exists.

// MARK: - Edit ▸ Find Next / Find Previous

/// Edit ▸ Find Next / Find Previous (⌘G / ⇧⌘G) — the card window's find bar stepping,
/// "disabled in the board window — board search is a live filter, not a cursor"
/// (11-command-nexus.md).
///
/// ### They are live for exactly one find, and disabled for the others on purpose
///
/// The card window has three finds (`CardWindowFindRoute`), and two of them are **`NSTextFinder`**'s —
/// the body's and an authoring editor's. `NSTextView` already answers ⌘G and ⇧⌘G through the responder
/// chain, and an *enabled* menu item's key equivalent fires before the responder chain is consulted,
/// so a row that claimed the chord unconditionally would break the stepping it exists to provide. So
/// these validate on the **thread** find alone — the one find with no responder to fall through to,
/// because its bar is the app's own — and stay disabled everywhere else, which lets the platform's
/// stepping keep working where the platform owns the find.
///
/// `.disabled` on the rows rather than a guard in the action, for the reason every menu row here
/// wears its validation: a key equivalent that fires and does nothing is a chord the user cannot tell
/// from a broken one.
struct FindSteppingCommands: View {

    @FocusedValue(\.cardComments) private var comments

    /// The row's validation, as a value a test can hold: the pane's find bar is up, which is the only
    /// state in which this app owns ⌘G.
    static func isEnabled(_ comments: CardComments?) -> Bool {
        comments?.find.isShowing == true
    }

    var body: some View {
        Button("Find Next") { comments?.find.step(forward: true) }
            .keyboardShortcut("g", modifiers: .command)
            .disabled(!Self.isEnabled(comments))
        Button("Find Previous") { comments?.find.step(forward: false) }
            .keyboardShortcut("g", modifiers: [.shift, .command])
            .disabled(!Self.isEnabled(comments))
    }
}

// MARK: - View ▸ Edit Body / Raw Source / History

/// View ▸ Edit Body (⌘E) / Raw Source (⌥⌘E) / History — the card window's three view-state rows
/// (11-command-nexus.md).
///
/// **Edit Body and Raw Source are both live** (`EditBodyCommand`, `RawSourceCommand`, each beside the
/// focused value it reads): the body column's Preview/Edit toggle, and the window-level outlet whose
/// toggling-off *applies*. Each diff was the one `FutureCommand` promises — the title and the chord
/// did not move, the validation and the action filled in — and the pair also carries the clause that
/// joins them, "Edit Body disables while Raw Source is active" (05-card-window.md).
///
// View ▸ History used to anticipate the sidebar's git commit-trail section (`CardWindowView`),
// which left with app-managed git (strategy/01-git-excision.md, 2026-08-08). The row is re-tagged
// rather than removed: it now anticipates the foreign-change journal successor (01-git-excision.md
// ▸ Successors — deferred, its design and timing not yet ruled). It stays unconditionally disabled
// here — there is no journal yet to focus — and the sidebar no longer reserves a place for it; the
// row itself is the only surviving reservation of the slot.
/// The comments pane's two rows join them (11-command-nexus.md lists Show Comments and Comments
/// Beside Body between Edit Body and Raw Source): both are live, both are app-wide persisted bits,
/// and both are scoped to the card window (`ShowCommentsCommand`, `CommentsBesideBodyCommand`).
///
/// **Show Sidebar joined at the end** (05-card-window.md ▸ Composition, the toolbar-toggle card):
/// the trailing attributes sidebar's own visibility bit, `ShowCommentsCommand`'s shape exactly and
/// mirroring the toolbar's own item (`CardToolbar`) the way View ▸ Show Trash mirrors its toolbar
/// twin. Appended rather than interleaved with the comments pair, to leave the block 11 already
/// documents (Edit Body · Show Comments · Comments Beside Body · Raw Source) in the order it names —
/// this row is additive, not a resequencing.
struct CardViewCommands: View {
    var body: some View {
        EditBodyCommand()
        ShowCommentsCommand()
        CommentsBesideBodyCommand()
        RawSourceCommand()
        FutureCommand(title: "History")
        ShowSidebarCommand()
    }
}
