import CoreGraphics

/// Where a drag preview attaches to the pointer, as pure arithmetic (`DragPreviewAnchorTests`).
///
/// **SwiftUI gives no anchor control.** `.onDrag(_:preview:)` lays the preview over the *grabbed
/// view's* frame, centred on it — which is exactly right while the preview is the size of what was
/// grabbed (a card face lifting off its own plate), and wrong the moment it is not. A lane is
/// grabbed by its title bar and its replica is the whole lane (03-board-ui.md § Motion: "a faithful,
/// full-size replica of the dragged item — the whole lane, not the strip of title bar that was
/// grabbed"), so centring that replica on the bar hangs half a lane above the pointer: the cursor
/// ends up in the middle of the image instead of on the pixel it grabbed.
///
/// The one lever the API does leave is the preview's own geometry, and **transparent padding moves a
/// view's centre without moving the view**: pad the side opposite the anchor by however much the
/// anchor is off-centre and the padded box's centre lands exactly on it. The system then centres
/// *that* box on the grabbed view, which puts the replica's title bar over the real one — and, since
/// the replica is drawn at the lane's own size, every other pixel of it over the lane it was lifted
/// from. That is the Finder-icon promise stated as geometry: the cursor stays where it grabbed.
///
/// Axis-agnostic, and said once because both axes ask the same question. The horizontal one answers
/// zero for a lane — a lane replica is as wide as its lane, so its header's centre is already the
/// box's — and the arithmetic is written for both anyway rather than assuming that stays true.
///
/// The compensation is **invariant under symmetric padding**: the drag replicas wear a transparent
/// margin so their shadows are not clipped (`BoardMetrics.replicaPadding`), and adding `p` to both
/// ends adds `p` to the length and `p` to the anchor, which cancels. So a caller may compute this
/// from the replica's own size and apply it outside that margin, in either order.
enum DragPreviewAnchor {

    /// Extra transparent length to add before and after a preview along one axis.
    struct Padding: Equatable, Sendable {

        var before: CGFloat = 0
        var after: CGFloat = 0

        static let none = Padding()
    }

    /// The padding that makes `anchor` — a distance from the preview's leading edge — the centre of
    /// a preview `length` long.
    ///
    /// Only ever one side of it: an anchor above the middle needs room before it, one below needs
    /// room after it, and the exact middle needs none. The anchor is clamped into the preview, so a
    /// stale measurement can shift the image by at most its own length.
    ///
    /// Degenerate input — an unmeasured, zero or non-finite length — pads nothing, which draws the
    /// preview exactly as it was drawn before this existed rather than flinging it off the pointer.
    static func padding(length: CGFloat, anchor: CGFloat) -> Padding {
        guard length.isFinite, anchor.isFinite, length > 0 else { return .none }
        let clamped = min(max(anchor, 0), length)
        let delta = length - 2 * clamped
        return delta > 0 ? Padding(before: delta) : Padding(after: -delta)
    }
}
