import SwiftUI

// MARK: - View ▸ Show Comments

/// View ▸ Show Comments (checkmark toggle, **no default chord**) — 11-command-nexus.md;
/// 05-card-window.md ▸ The comments column.
///
/// ### One bit, app-wide, persisted — and the checkmark *is* the bit
///
/// > **View ▸ Show Comments** is a checkmark toggle à la Show Trash, and its choice is **app-wide and
/// > persisted across restarts**. One bit, no content-derived auto-show: checked, every card window
/// > carries the pane (a comment-less card shows the empty thread and the composer — the invitation
/// > is the point); unchecked, threads and drafts are out of sight until the user says otherwise, the
/// > Show Trash bargain. The checkmark reads the bit — the menu never lies.
///
/// `@AppStorage` is that sentence with no machinery under it: the row's `isOn` reads the very default
/// every card window's pane reads, so there is no per-window mirror to keep in step and no auto-show
/// rule that could disagree with the tick. "Deleting the last comment never closes the pane" needs no
/// code at all for the same reason — nothing but this row writes the key.
///
/// **No App Group.** The suite is `UserDefaults.standard`, which the sandbox already scopes to this
/// one app (`AppPreferences`); the group suite the 2026-07-29 ruling named went with the App Group
/// itself when the edition split collapsed.
///
/// Validation is **scope and nothing else**: with no card window in front there is no `cardComments`
/// focused value and the row disables. The read-only lock is deliberately not part of it — showing a
/// pane is not a mutation, exactly as entering Edit is not (`EditBodyCommand`).
struct ShowCommentsCommand: View {

    @FocusedValue(\.cardComments) private var comments
    @AppStorage(AppPreferences.showCommentsKey) private var isShown = true

    /// The row's validation, as a value a test can hold — `EditBodyCommand.isEnabled`'s shape, for
    /// its reason: a menu item's `.disabled` is otherwise only observable by driving the menu bar.
    static func isEnabled(_ comments: CardComments?) -> Bool {
        comments != nil
    }

    var body: some View {
        Toggle("Show Comments", isOn: $isShown)
            .disabled(!Self.isEnabled(comments))
    }
}

// MARK: - View ▸ Comments Beside Body

/// View ▸ Comments Beside Body (checkmark toggle, **no default chord**) — "checked = side-by-side
/// (default), unchecked = body over comments; app-wide, persisted" (11-command-nexus.md;
/// 05-card-window.md ▸ Composition).
///
/// `ShowCommentsCommand`'s shape exactly, and for its reasons — one persisted bit, the checkmark
/// reading it, scope-only validation. The two rows sit together because they are the pane's two
/// user-facing facts and 11 lists them adjacent.
///
/// It stays enabled while Show Comments is off. The row is a *layout* preference, not a second
/// visibility switch, and a user arranging their window before turning the pane on is doing something
/// perfectly ordinary — the alternative (disabling it) would also make the checkmark lie about a bit
/// that is still stored and still applies the moment the pane appears.
struct CommentsBesideBodyCommand: View {

    @FocusedValue(\.cardComments) private var comments
    @AppStorage(AppPreferences.commentsBesideBodyKey) private var isBeside = true

    static func isEnabled(_ comments: CardComments?) -> Bool {
        comments != nil
    }

    var body: some View {
        Toggle("Comments Beside Body", isOn: $isBeside)
            .disabled(!Self.isEnabled(comments))
    }
}

// MARK: - File ▸ Add Comment

/// File ▸ Add Comment (**no default chord**) — card window (11-command-nexus.md;
/// 05-card-window.md ▸ The comments column).
///
/// ### One gesture, two effects, in the one order that works
///
/// > **File ▸ Add Comment** flips the bit on when it's off (the gesture *is* the user choosing to see
/// > comments — same persistence) and focuses the composer in one gesture.
///
/// The write comes first and the focus request second, because the composer does not exist to be
/// focused until the pane is mounted. The request is a counter on the window's handle rather than a
/// call into a text view (`CardComments.focusComposerRequests`), so the pane picks it up on the
/// update after the one that mounted it — which is the only ordering that survives the pane arriving
/// in the same frame.
///
/// **Flipping the bit is persisted like any other flip of it.** That is the ruling, stated in the
/// design as "the same user choice": a user who reaches for Add Comment has said they want to see
/// comments, and a visibility that reverted at the next window would make the row a one-shot.
///
/// Validation is scope. The read-only lock is not part of it, `EditBodyCommand`'s rule: this focuses
/// a text surface, and 02-architecture.md keeps editor buffers alive under the lock (only their saves
/// suspend), so a locked board can still be typed into and its text copied out. What the lock does
/// disable is the composer's Post button and its paperclip, in place, where they are.
struct AddCommentCommand: View {

    @FocusedValue(\.cardComments) private var comments
    @AppStorage(AppPreferences.showCommentsKey) private var isShown = true

    static func isEnabled(_ comments: CardComments?) -> Bool {
        comments != nil
    }

    /// What the row does to the two pieces of state it touches, as a pure pair a test can drive: the
    /// bit's new value, and whether a focus request is owed.
    ///
    /// It is extracted for the reason every menu-row rule in this codebase is: "turns it on when it
    /// is off, and focuses either way" is one sentence with two clauses, and the clause that
    /// regresses silently is the second one — a row that only focused when it had just turned the
    /// pane on would look correct in the demo and be wrong every time after.
    static func effect(isShown: Bool) -> (isShown: Bool, focusesComposer: Bool) {
        (isShown: true, focusesComposer: true)
    }

    var body: some View {
        Button("Add Comment") {
            let effect = Self.effect(isShown: isShown)
            isShown = effect.isShown
            guard effect.focusesComposer else { return }
            comments?.focusComposer()
        }
        .disabled(!Self.isEnabled(comments))
    }
}
