/// 04-interactions.md's **⌘N target rule** (settled), as a pure function of the three things it
/// reads — the selection, the last-active lane, and the snapshot (`NewCardTargetTests`).
///
/// The rule verbatim, and each clause's branch below:
///
/// > with a card selected, the new card is created in that card's lane, immediately after it
/// > (paste-anchor consistency); with a lane selected, appended at its bottom (Return consistency);
/// > a multi-selection anchors at its last member in flatten order (lane `order`, then card
/// > `order`, the multi-drag order; the same anchor serves paste): creation follows the last
/// > selected card, or appends to the last selected lane; … with nothing selected — or a
/// > **trash** selection, which never anchors creation — the **last-active lane** — the lane
/// > that most recently held selection or a creation in this window session — falling back to the
/// > first lane. … **Zero-lane board**: card creation … disable[s] via menu validation until a
/// > lane exists.
///
/// **A pure function rather than a method on the store** for the reason every rule in this codebase
/// that can be one is: the branches are lines of test rather than UI states to drive, and the menu
/// item's `disabled` and its action then read the *same* answer instead of two hand-kept-in-sync
/// conditions.
///
/// ### What it deliberately does not decide
///
/// - **Return on a selected lane** is the same target as this rule's lane branch, but it is reached
///   by grammar rather than by the menu; it passes its lane directly rather than coming here. (The
///   lane header once carried a new-card button with the same carve-out — click names its target,
///   selection notwithstanding — but the button itself is gone; Return is the one direct-target path
///   left.)
enum NewCardTarget {

    /// Where a new card goes: which lane, and which card it lands immediately after (`nil` = the
    /// lane's bottom). Exactly `NewCardPlaceholder`'s two anchoring fields, because that is what
    /// this resolves *into*.
    struct Resolution: Equatable {
        let laneID: ItemID
        let anchorCardID: ItemID?
    }

    /// The target, or `nil` when there is none — **the zero-lane board**, where "New Card,
    /// Return-creation, and Paste with a card payload disable via menu validation until a lane
    /// exists". `nil` is therefore the menu item's `disabled` condition as well as its refusal, so
    /// the two can never disagree.
    ///
    /// - Parameters:
    ///   - selection: the board's current selection, container included. A `.trash` selection
    ///     "never anchors creation" and is treated exactly as an empty one — the settled precedent
    ///     04 ▸ Clipboard cites for paste, applied here to its source rule ("a trashed card's live
    ///     disk-lane never leaks in as 'the selected card's lane'").
    ///   - lastActiveLaneID: `TransientBoardState.lastActiveLaneID`, already cleared by the reload
    ///     rule if its lane vanished — but re-checked here anyway, because a caller need not have
    ///     reloaded since the lane went.
    static func resolve(
        selection: ItemReferenceSet,
        lastActiveLaneID: ItemID?,
        snapshot: BoardModel
    ) -> Resolution? {
        // **Collapsed lanes are not creation targets** (03-board-ui.md § Lane ▸ Collapsed lanes:
        // "⌘N new-card targeting skips collapsed lanes"): the placeholder is a pseudo-card drawn in the
        // lane's masonry (`LaneSlot.placeholder`), and a folded lane draws none — so a card created
        // there would be a focused text field rendered nowhere, with no way to type a title into it and
        // no way out but Escape. Skipping is therefore not a preference but the only coherent answer,
        // and it is applied to every branch below by narrowing the lane list once.
        //
        // **A board whose every lane is folded has no target at all**, which is exactly the zero-lane
        // board's answer and reaches the same place: `nil` is New Card's `disabled` condition as well
        // as its refusal, so the item greys out rather than doing nothing when pressed.
        let lanes = snapshot.lanes.filter { !LaneLayoutMath.isCollapsed($0) }
        guard !lanes.isEmpty else { return nil }

        // **A selection inside a folded lane falls through rather than refusing** — the stale
        // selection's rule verbatim, for its reason: the user pressed ⌘N and the board has lanes it
        // can create into. So collapsing a lane with one of its cards selected leaves ⌘N working, at
        // the last-active lane below. `flattenAnchor` itself is deliberately untouched: it is
        // **shared with paste** ("the same anchor serves paste"), and a paste into a folded lane is
        // perfectly coherent — it writes a rank, it renders nothing, and the card is there when the
        // lane unfolds.
        if let anchor = flattenAnchor(selection: selection, snapshot: snapshot),
           lanes.contains(where: { $0.id == anchor.laneID }) {
            return anchor
        }
        // Nothing selected, a trash selection, or a stale one — the ids name nothing the board
        // renders, a selection the next reload will drop. Falls through rather than refusing: the
        // user pressed ⌘N and the board has lanes. The target is then the lane that most recently
        // held selection or a creation, and the first lane when there is no such lane (or it has
        // since gone).
        if let lastActiveLaneID, let lane = lanes.first(where: { $0.id == lastActiveLaneID }) {
            return Resolution(laneID: lane.id, anchorCardID: nil)
        }
        return lanes.first.map { Resolution(laneID: $0.id, anchorCardID: nil) }
    }

    /// **The shared anchor, on its own** — "a multi-selection anchors at its last member in flatten
    /// order (lane `order`, then card `order`, the multi-drag order; the same anchor serves paste)".
    ///
    /// Extracted rather than left inside `resolve` because paste needs *exactly this clause* and not
    /// the two that surround it. Card paste is `resolve` verbatim (the last-active-lane fallback and
    /// all), but **lane paste has a different fallback** — "nothing selected = the board's right end",
    /// never the last-active lane — so it takes the anchor and stops. Two derivations of "the last
    /// member in flatten order" would be two chances for creation and paste to disagree about the one
    /// rule 04 says they share.
    ///
    /// `nil` covers the three cases that anchor nothing, which the callers then answer their own way:
    /// an empty selection, a **trash** one ("a trash selection never anchors paste",
    /// settled — and "a trashed card's live disk-lane never leaks in as 'the selected card's lane'",
    /// which falls out of never looking at the trashed side at all), and a stale one whose ids name
    /// nothing the board renders.
    static func flattenAnchor(selection: ItemReferenceSet, snapshot: BoardModel) -> Resolution? {
        guard selection.container == .board, !selection.ids.isEmpty else { return nil }

        // The snapshot's lanes and cards are already in display order, so the flatten order is one
        // walk, and the *last* hit is the anchor. Selection is homogeneous (cards XOR lanes), so only
        // one of the two branches ever fires within a walk; a sole selection is simply the degenerate
        // one-member case of the same rule.
        var anchor: Resolution?
        for lane in snapshot.lanes {
            // A selected lane: creation appends at its bottom, Return consistency; paste lands after
            // the lane itself.
            if selection.ids.contains(lane.id) {
                anchor = Resolution(laneID: lane.id, anchorCardID: nil)
            }
            // A selected card: its lane, immediately after it — paste-anchor consistency.
            for card in lane.cards where selection.ids.contains(card.id) {
                anchor = Resolution(laneID: lane.id, anchorCardID: card.id)
            }
        }
        return anchor
    }
}
