import AppKit
import SwiftUI
import Testing
@testable import Kanban

/// **10-accessibility.md's one hard number**, as arithmetic a suite can hold still: "The ≥ 4.5:1
/// automatic-contrast rule binds where text does sit on a user-chosen color: the **board**
/// background (palette pairs verified at design time; arbitrary hex computes its text color at
/// runtime against that threshold). An `#RRGGBBAA` background with alpha computes against the color
/// **composited over its effective backdrop** in the active appearance."
///
/// Three layers, tested at three altitudes:
///
/// 1. **`ContrastMath`** — WCAG relative luminance, contrast ratio and source-over compositing, pure
///    functions of four numbers. Pinned against the standard's own published values, because the
///    whole point of citing WCAG is that the numbers are not ours to choose.
/// 2. **The decision** — which of the two label vocabularies the text takes, including the case the
///    design's threshold cannot be met by either, which is a real outcome for a mid-grey hex and not
///    a defensive branch.
/// 3. **`BoardTextInk`** — the board's application: that the rule binds to *every* colour the board
///    paints (palette name and hand-written hex alike, one path), that a translucent one resolves
///    differently in the two appearances, and that a board painting nothing is left alone.
///
/// The palette's own suite closes the loop. 03-board-ui.md ▸ Styling ▸ Controls promises the palette
/// wells are "AA-verified at design time … pinned by a computed-contrast unit test over all
/// pairs"; a pair is a background *and its ink*, so the promise is only keepable by checking the ink
/// the seam chooses — which `PaletteContrastTests` does, in both appearances. That is the whole of
/// the difference between the two paths: the palette is a fixed set and can be checked in advance,
/// a hand-written hex arrives from a file and can only be computed as it renders.
///
/// The candidate inks are **injected** into the pure layer rather than read from AppKit, so every
/// branch is reachable and no assertion depends on the exact alpha Apple ships `labelColor` at this
/// year. The live colours are exercised separately, in claims that stay true whatever those values
/// are.

// MARK: - Relative luminance

@Suite("Contrast ▸ WCAG relative luminance")
struct RelativeLuminanceTests {

    private func luminance(_ hex: String) throws -> Double {
        let color = try #require(NSColor(paletteHex: hex), "'\(hex)' did not parse")
        let srgb = try #require(SRGBColor(color), "'\(hex)' is not sRGB-convertible")
        return ContrastMath.relativeLuminance(of: srgb)
    }

    /// The three values every implementation of this formula is checked against. `#808080` is the
    /// interesting one: the channel is 0.502, and the answer is 0.216 rather than 0.5 — the gap
    /// between "half the bits" and "half the light" is exactly what makes eyeballing contrast
    /// unreliable and this function necessary.
    @Test("White is 1, black is 0, mid-grey is 0.2159")
    func theStandardsPublishedValues() throws {
        #expect(abs(try luminance("#FFFFFF") - 1) < 0.0001)
        #expect(abs(try luminance("#000000") - 0) < 0.0001)
        #expect(abs(try luminance("#808080") - 0.2159) < 0.0005)
    }

    /// The channel weights, isolated: a pure primary's luminance *is* its coefficient, since the
    /// other two channels linearise to zero and the primary linearises to one. A transposed pair
    /// (green and red are the easy ones to swap) would sail past every grey test above.
    @Test("Each primary weighs its WCAG coefficient")
    func theChannelWeights() throws {
        #expect(abs(try luminance("#FF0000") - 0.2126) < 0.0001)
        #expect(abs(try luminance("#00FF00") - 0.7152) < 0.0001)
        #expect(abs(try luminance("#0000FF") - 0.0722) < 0.0001)
    }

    /// **The linear leg below the knee** — WCAG's transfer function is a piecewise curve, and a
    /// `pow(c, 2.2)` shortcut gets the near-blacks wrong. `#050505` is 0.0196, under the 0.03928
    /// threshold, so the answer is a plain division by 12.92; the shortcut would say ~0.00019, an
    /// eightfold error on exactly the values a dark hand-written board background lands on.
    @Test("Near-black uses the linear leg of the transfer function")
    func theTransferFunctionsLinearLeg() throws {
        #expect(abs(try luminance("#050505") - (5.0 / 255.0) / 12.92) < 0.000001)
    }

    /// Monotone: a lighter colour has a higher luminance. Cheap, and it catches a sign or an inverted
    /// branch that the three fixed points above could conceivably straddle.
    @Test("Luminance rises with lightness")
    func luminanceIsMonotone() throws {
        let ramp = ["#000000", "#202020", "#404040", "#808080", "#C0C0C0", "#FFFFFF"]
        let values = try ramp.map { try luminance($0) }
        #expect(values == values.sorted())
        #expect(Set(values).count == ramp.count)
    }
}

// MARK: - Contrast ratio

@Suite("Contrast ▸ ratio")
struct ContrastRatioTests {

    private func color(_ hex: String) throws -> SRGBColor {
        try #require(NSColor(paletteHex: hex).flatMap(SRGBColor.init), "'\(hex)' did not parse")
    }

    /// The range's two ends: 21:1 is the maximum the formula can produce, 1:1 is a colour against
    /// itself.
    @Test("Black on white is 21:1 and a colour on itself is 1:1")
    func theRangesEnds() throws {
        let white = try color("#FFFFFF")
        let black = try color("#000000")
        #expect(abs(ContrastMath.contrastRatio(white, black) - 21) < 0.001)
        #expect(abs(ContrastMath.contrastRatio(white, white) - 1) < 0.0001)
        #expect(abs(ContrastMath.contrastRatio(black, black) - 1) < 0.0001)
    }

    /// Symmetric — the lighter colour always takes the numerator, so no caller has to know which
    /// argument is the text and which is the surface.
    @Test("The ratio is symmetric in its arguments")
    func theRatioIsSymmetric() throws {
        let a = try color("#1F2E45")
        let b = try color("#D5D5D5")
        #expect(ContrastMath.contrastRatio(a, b) == ContrastMath.contrastRatio(b, a))
    }

    /// The threshold is the design's, spelled once (10-accessibility.md).
    @Test("The AA threshold is 4.5")
    func theThresholdIsTheDesigns() {
        #expect(ContrastMath.aaThreshold == 4.5)
    }
}

// MARK: - Alpha compositing

@Suite("Contrast ▸ source-over compositing")
struct CompositeTests {

    private let white = SRGBColor(red: 1, green: 1, blue: 1)
    private let black = SRGBColor(red: 0, green: 0, blue: 0)

    @Test("A fully opaque source replaces the backdrop, a fully transparent one vanishes")
    func theTwoDegenerateAlphas() {
        let red = SRGBColor(red: 1, green: 0, blue: 0)
        #expect(ContrastMath.composite(red, over: white) == red)
        #expect(ContrastMath.composite(SRGBColor(red: 1, green: 0, blue: 0, alpha: 0), over: white) == white)
    }

    /// Half of a colour and half of what is behind it — and the arithmetic that says the *same*
    /// translucent value is two different colours over two different backdrops, which is the whole
    /// reason 10-accessibility.md asks for the composite rather than the written value.
    @Test("A half-alpha source lands halfway to its backdrop")
    func aHalfAlphaSourceMeetsItsBackdropHalfway() {
        let halfBlack = SRGBColor(red: 0, green: 0, blue: 0, alpha: 0.5)
        let overWhite = ContrastMath.composite(halfBlack, over: white)
        let overBlack = ContrastMath.composite(halfBlack, over: black)
        #expect(abs(overWhite.red - 0.5) < 0.0001)
        #expect(abs(overBlack.red - 0) < 0.0001)
        #expect(overWhite.alpha == 1)
    }

    /// Straight (non-premultiplied) alpha, with a **translucent backdrop** — the general form. The
    /// board never composites two washes today, but a seam that only worked against an opaque
    /// backdrop would be one special case pretending to be a rule.
    @Test("Two translucent layers combine their alphas")
    func aTranslucentBackdropCombines() {
        let source = SRGBColor(red: 1, green: 0, blue: 0, alpha: 0.5)
        let backdrop = SRGBColor(red: 0, green: 0, blue: 1, alpha: 0.5)
        let result = ContrastMath.composite(source, over: backdrop)
        #expect(abs(result.alpha - 0.75) < 0.0001)
        // 0.5 of the source over 0.25 of the backdrop, renormalised by the 0.75 output alpha.
        #expect(abs(result.red - (0.5 / 0.75)) < 0.0001)
        #expect(abs(result.blue - (0.25 / 0.75)) < 0.0001)
    }

    /// Total rather than crashing: two invisible layers have no colour, and the seam has to say so
    /// without dividing by zero.
    @Test("Compositing nothing over nothing stays transparent")
    func theFullyTransparentCase() {
        let nothing = SRGBColor(red: 1, green: 1, blue: 1, alpha: 0)
        #expect(ContrastMath.composite(nothing, over: nothing).alpha == 0)
    }
}

// MARK: - The decision

@Suite("Contrast ▸ which ink the text takes")
struct InkChoiceTests {

    /// The system's inks as of writing — 85% black and 85% white (`NSColor.labelColor`). Written
    /// down here rather than resolved so the branch each case exercises is the branch it claims to;
    /// the live values are checked separately in `BoardTextInkTests`.
    private let labels = ContrastMath.Ink(
        light: SRGBColor(red: 0, green: 0, blue: 0, alpha: 0.85),
        dark: SRGBColor(red: 1, green: 1, blue: 1, alpha: 0.85)
    )

    /// Full-strength ink, which is what Increase Contrast moves the labels towards — and the only
    /// way to reach the both-candidates-pass branch, since two 85% labels never both clear 4.5:1.
    private let opaqueLabels = ContrastMath.Ink(
        light: SRGBColor(red: 0, green: 0, blue: 0),
        dark: SRGBColor(red: 1, green: 1, blue: 1)
    )

    private let opaque = SRGBColor(red: 0.5, green: 0.5, blue: 0.5)

    private func hex(_ value: String) throws -> SRGBColor {
        try #require(NSColor(paletteHex: value).flatMap(SRGBColor.init), "'\(value)' did not parse")
    }

    /// **The card's case, from the light side.** A near-black hand-written background in a light
    /// window: the native ink is dark glyphs on a dark surface, which is unreadable, so the decision
    /// crosses to the dark appearance's ink and the header renders in light glyphs.
    @Test("A dark hex in the light appearance takes the dark appearance's ink")
    func aDarkBackgroundFlipsTheLightAppearance() throws {
        let choice = ContrastMath.inkChoice(
            background: try hex("#101010"),
            backdrop: try hex("#ECECEC"),
            ink: labels,
            native: .light
        )
        #expect(choice.scheme == .dark)
        #expect(choice.meetsAA)
    }

    /// The same case from the other side — a pale background in a dark window.
    @Test("A light hex in the dark appearance takes the light appearance's ink")
    func aLightBackgroundFlipsTheDarkAppearance() throws {
        let choice = ContrastMath.inkChoice(
            background: try hex("#F5F5DC"),
            backdrop: try hex("#1E1E1E"),
            ink: labels,
            native: .dark
        )
        #expect(choice.scheme == .light)
        #expect(choice.meetsAA)
    }

    /// The rule's *quiet* half: when the native ink already clears the threshold, nothing moves. A
    /// dark board in a dark window is the common case, and a decision that flipped it anyway would
    /// be a redesign rather than an accommodation.
    @Test("A background the native ink already reads on is left alone")
    func theNativeInkIsKeptWhenItPasses() throws {
        let dark = ContrastMath.inkChoice(
            background: try hex("#101010"),
            backdrop: try hex("#1E1E1E"),
            ink: labels,
            native: .dark
        )
        #expect(dark.scheme == .dark)
        #expect(dark.meetsAA)

        let light = ContrastMath.inkChoice(
            background: try hex("#F5F5DC"),
            backdrop: try hex("#ECECEC"),
            ink: labels,
            native: .light
        )
        #expect(light.scheme == .light)
        #expect(light.meetsAA)
    }

    /// **Both candidates pass → the appearance-native one wins**, in each direction.
    ///
    /// Reachable only with full-strength ink (see `opaqueLabels`), and only in a sliver: pure black
    /// clears 4.5:1 above L = 0.175 and pure white clears it below L = 0.1833, so the window where
    /// both pass is eight thousandths of a luminance wide. `#767676` (channel 0.4603) sits in the
    /// middle of it at L ≈ 0.179, where both score ≈ 4.58. Two 85%-alpha labels never both clear the
    /// threshold at all, which is why this case needs the opaque pair to exist — and why the rule is
    /// stated anyway: Increase Contrast is exactly what moves the system's labels here.
    @Test("When both inks clear the threshold the native one is preferred")
    func bothPassingPrefersTheNativeAppearance() {
        let crossover = SRGBColor(red: 0.4603, green: 0.4603, blue: 0.4603)
        let asLight = ContrastMath.inkChoice(
            background: crossover, backdrop: opaque, ink: opaqueLabels, native: .light
        )
        let asDark = ContrastMath.inkChoice(
            background: crossover, backdrop: opaque, ink: opaqueLabels, native: .dark
        )
        #expect(asLight.meetsAA && asDark.meetsAA, "the crossover surface should clear 4.5:1 both ways")
        #expect(asLight.scheme == .light)
        #expect(asDark.scheme == .dark)
    }

    /// **The documented fallback.** A hand-written mid-grey has no readable ink in the system's
    /// vocabulary: 85%-alpha labels top out in the low fours against `#6E6E6E` in *both*
    /// appearances. The rule is to paint the better of the two anyway and report the miss — the
    /// alternatives being to override the user's colour (which "the bytes stay as written" forbids)
    /// or to invent an ink no other window in the app uses.
    @Test("When neither ink clears the threshold the higher-ratio one is used")
    func neitherPassingTakesTheHigherRatio() throws {
        let grey = try hex("#6E6E6E")
        for native in [ColorScheme.light, .dark] {
            let choice = ContrastMath.inkChoice(
                background: grey, backdrop: opaque, ink: labels, native: native
            )
            #expect(!choice.meetsAA, "\(native) unexpectedly cleared AA on #6E6E6E")
            // The answer is the same whichever appearance asked: with neither passing, the decision
            // is the arithmetic's alone.
            #expect(choice.scheme == .dark)
            let rejected = ContrastMath.ratio(of: labels.light, on: grey)
            #expect(choice.ratio > rejected)
        }
    }

    /// **The threshold is a boundary, not a region.** Walking a grey ramp, the decision's `meetsAA`
    /// flag must agree with the ratio it reports on every step — a decision that claimed a pass at
    /// 4.49 or a miss at 4.51 would make the design's number decorative.
    @Test("meetsAA agrees with the reported ratio at the boundary")
    func theThresholdIsExact() {
        for step in 0...255 {
            let level = Double(step) / 255
            let surface = SRGBColor(red: level, green: level, blue: level)
            let choice = ContrastMath.inkChoice(
                background: surface, backdrop: opaque, ink: labels, native: .light
            )
            #expect(choice.meetsAA == (choice.ratio >= ContrastMath.aaThreshold))
            // And the chosen ink is never worse than the one passed over.
            let other: ColorScheme = choice.scheme == .dark ? .light : .dark
            let rejected = ContrastMath.ratio(of: labels[other], on: surface)
            #expect(choice.ratio >= rejected || choice.meetsAA)
        }
    }

    /// **Alpha is what makes the appearance matter** — 10-accessibility.md's "light and dark resolve
    /// differently", as two decisions that disagree about the same frontmatter value.
    ///
    /// `background: #00000080` is a mid-grey over the light window background and a near-black over
    /// the dark one, and the two surfaces are not merely different shades: the light one lands in the
    /// dead zone where *no* label ink clears AA (≈ 4.1:1 at best), while the dark one clears it four
    /// times over. Composite against the wrong appearance's backdrop and this board is reported as
    /// fine when it is not, or as unreadable when it is.
    @Test("A translucent hex resolves against the appearance's own backdrop")
    func alphaMakesTheAppearanceDecisive() throws {
        let translucent = try hex("#00000080")
        let inLight = ContrastMath.inkChoice(
            background: translucent, backdrop: try hex("#ECECEC"), ink: labels, native: .light
        )
        let inDark = ContrastMath.inkChoice(
            background: translucent, backdrop: try hex("#1E1E1E"), ink: labels, native: .dark
        )
        #expect(inLight.scheme == .light)
        #expect(!inLight.meetsAA, "half-black over the light window background is the mid-grey dead zone")
        #expect(inDark.scheme == .dark)
        #expect(inDark.meetsAA)
        #expect(inDark.ratio > inLight.ratio * 2)
    }

    /// The backdrop **flipping a decision on its own**: `#FFFFFF80` is a pale surface in either
    /// appearance, because half of white is still lighter than the dark window background. So the
    /// dark appearance's native ink fails on it and the header renders in dark glyphs inside a dark
    /// window — a board the composite gets right and the written value alone gets backwards (`#FFFFFF`
    /// at 50% would read as "half transparent, so leave it alone").
    @Test("A pale translucent hex forces dark glyphs even in a dark window")
    func aPaleTranslucentHexFlipsTheDarkAppearance() throws {
        let translucent = try hex("#FFFFFF80")
        let inDark = ContrastMath.inkChoice(
            background: translucent, backdrop: try hex("#1E1E1E"), ink: labels, native: .dark
        )
        #expect(inDark.scheme == .light)
        #expect(inDark.meetsAA)
    }

    /// The backdrop is *only* consulted where there is alpha to resolve: an opaque `#RRGGBB` value
    /// covers the window background completely, so the two appearances see the same surface and
    /// reach the same ink.
    @Test("An opaque hex ignores its backdrop")
    func anOpaqueHexIsBackdropIndependent() throws {
        let value = try hex("#005152")
        let overLight = ContrastMath.inkChoice(
            background: value, backdrop: try hex("#ECECEC"), ink: labels, native: .light
        )
        let overDark = ContrastMath.inkChoice(
            background: value, backdrop: try hex("#1E1E1E"), ink: labels, native: .light
        )
        #expect(overLight == overDark)
    }
}

// MARK: - The board's application

@Suite("Contrast ▸ the board's painted background")
@MainActor
struct BoardTextInkTests {

    /// **The predicate is "does the board paint anything", not "where did the value come from"** —
    /// the same `if let` `BoardView.boardBackground` takes, routed through the same
    /// `Palette.nsColor(for:)`. A palette name and a hex both name a surface the text has to be
    /// legible on; two spellings of the question could disagree about a value, and one cannot.
    @Test("Every value the board paints has a colour, and nothing else does")
    func whatCountsAsAPaintedBackground() {
        #expect(BoardTextInk.paintedColor(.valid("#1E1E1E")) != nil)
        #expect(BoardTextInk.paintedColor(.valid("#1E1E1E80")) != nil)
        #expect(BoardTextInk.paintedColor(.valid("#1e1e1e")) != nil)
        #expect(BoardTextInk.paintedColor(.valid("smokey-ocean")) != nil)
        #expect(BoardTextInk.paintedColor(.valid("chalk")) != nil)
        // A foreground-table name in the `background` field resolves, because the two-table split is a
        // picker split and not a namespace (`Palette`) — so it paints, so it decides an ink.
        #expect(BoardTextInk.paintedColor(.valid("carnation")) != nil)
        #expect(BoardTextInk.paintedColor(.valid("#12345")) == nil)
        #expect(BoardTextInk.paintedColor(.valid("FF0000")) == nil)
        #expect(BoardTextInk.paintedColor(.valid("chartreuse")) == nil)
        #expect(BoardTextInk.paintedColor(FieldValue<String>.missing) == nil)
        #expect(BoardTextInk.paintedColor(.malformed(raw: "[a, b]")) == nil)
    }

    /// Only a board that paints **nothing** keeps the window's own appearance — the empty key, the
    /// malformed value, the typo. The surface is then the system's own, and the system's answer is
    /// the right one.
    @Test("A missing key, a malformed value and a typo leave the appearance alone")
    func anUnpaintedBoardIsLeftAlone() {
        for field: FieldValue<String> in [.missing, .malformed(raw: "[a, b]"), .valid("chartreuse")] {
            #expect(BoardTextInk.scheme(forBoardBackground: field, appearance: .light) == .light)
            #expect(BoardTextInk.scheme(forBoardBackground: field, appearance: .dark) == .dark)
            #expect(BoardTextInk.choice(forBoardBackground: field, appearance: .light) == nil)
        }
    }

    /// A **palette name** goes through the same door as a hex — which is the fix this card closed.
    /// `smokey-ocean` is a near-black navy: under Aqua's own label it scored 1.58:1, and the board
    /// now renders it in light glyphs instead.
    @Test("A palette background decides an ink like any other painted colour")
    func aPaletteNameRoutesThroughTheSameRule() throws {
        let choice = try #require(
            BoardTextInk.choice(forBoardBackground: .valid("smokey-ocean"), appearance: .light)
        )
        #expect(choice.scheme == .dark)
        #expect(choice.meetsAA)
        #expect(BoardTextInk.scheme(forBoardBackground: .valid("chalk"), appearance: .dark) == .light)
    }

    /// The end-to-end claim, against the **live** system colours rather than written-down ones: a
    /// near-black board reads in light glyphs and a near-white one in dark glyphs, in either
    /// appearance. Whatever alpha Apple ships `labelColor` at, these two must hold — they are the
    /// reason the card exists.
    @Test("The extremes resolve to the readable ink in both appearances")
    func theExtremesResolveCorrectly() {
        for appearance in [ColorScheme.light, .dark] {
            #expect(BoardTextInk.scheme(forBoardBackground: .valid("#050505"), appearance: appearance) == .dark)
            #expect(BoardTextInk.scheme(forBoardBackground: .valid("#FAFAFA"), appearance: appearance) == .light)
        }
    }

    /// `#00000080` over the two window backgrounds — the design's own worked example, through the
    /// live `NSColor.windowBackgroundColor` in each appearance. The value on disk is one string; the
    /// surface it makes is not, and the ink follows the surface.
    @Test("A translucent board colour resolves against the appearance's window background")
    func theBackdropIsResolvedInTheActiveAppearance() {
        let halfBlack = FieldValue<String>.valid("#00000080")
        #expect(BoardTextInk.scheme(forBoardBackground: halfBlack, appearance: .light) == .light)
        #expect(BoardTextInk.scheme(forBoardBackground: halfBlack, appearance: .dark) == .dark)
        // The same value over the *pale* half of a light window is a mid-grey; over the dark half it
        // is nearly black. Two surfaces, so two luminances.
        let light = BoardTextInk.windowBackdrop(in: .light)
        let dark = BoardTextInk.windowBackdrop(in: .dark)
        #expect(ContrastMath.relativeLuminance(of: light) > ContrastMath.relativeLuminance(of: dark))
        #expect(light.alpha == 1 && dark.alpha == 1, "the window background must be opaque to be a backdrop")
    }

    /// The live inks, as a sanity claim that survives any future tuning of the system palette: the
    /// light appearance's label is the darker of the two, and each reads on its own appearance's
    /// window background.
    @Test("The live label inks are dark-on-light and light-on-dark")
    func theLiveInksAreOrientedCorrectly() {
        let ink = BoardTextInk.labelInk()
        let light = ContrastMath.composite(ink.light, over: BoardTextInk.windowBackdrop(in: .light))
        let dark = ContrastMath.composite(ink.dark, over: BoardTextInk.windowBackdrop(in: .dark))
        #expect(ContrastMath.relativeLuminance(of: light) < ContrastMath.relativeLuminance(of: dark))
        #expect(ContrastMath.ratio(of: ink.light, on: BoardTextInk.windowBackdrop(in: .light)) > 4.5)
        #expect(ContrastMath.ratio(of: ink.dark, on: BoardTextInk.windowBackdrop(in: .dark)) > 4.5)
    }

    /// The fixture board's hand-written background (`#1E1E1E`, `PaletteHexTests`' own example) —
    /// the value a real board on disk carries, decided end to end.
    @Test("The rich fixture's hand-written background reads in light glyphs")
    func theFixturesBackgroundResolves() throws {
        let choice = try #require(
            BoardTextInk.choice(forBoardBackground: .valid("#1E1E1E"), appearance: .light)
        )
        #expect(choice.scheme == .dark)
        #expect(choice.meetsAA)
    }
}

// MARK: - The palette's AA claim, made true

@Suite("Contrast ▸ the palette's AA claim")
@MainActor
struct PaletteContrastTests {

    /// **03-board-ui.md ▸ Styling ▸ Controls' promise, computed — and this test *is* the promise**:
    /// "the background grid offers the palette colors — every pair AA-verified at design time
    /// (10-accessibility.md), the claim pinned by a computed-contrast unit test over all pairs so
    /// palette drift can never silently break it."
    ///
    /// **This is also the gate the palette grew through.** Four backgrounds were added on 2026-08-09;
    /// the reason that was a safe thing to do is that a new well with no readable ink fails here
    /// rather than shipping, which is exactly the first failure mode named below.
    ///
    /// A **pair** is a background and the ink its text is drawn in, so the claim cannot be settled by
    /// a table of colours alone — only by the code that chooses the ink. What is asserted here is
    /// therefore end to end and in both appearances: for every well, the scheme `BoardTextInk`
    /// *selects* clears 4.5:1 on the colour that well paints. Nothing weaker would be the design's
    /// claim; nothing stronger is true, since no single ink reads on all of them.
    ///
    /// Two ways to fail, both of them the point. A **new well** whose colour has no readable ink at
    /// all — a mid-grey, the dead zone `InkChoiceTests.neitherPassingTakesTheHigherRatio` documents —
    /// fails here instead of shipping. And a regression in the *selection* fails here too: with the
    /// appearance-native label, which is what the board drew before this card, every one of the
    /// wells failed in one appearance (every dark well under Aqua, `chalk` and `aluminum` under Dark
    /// Aqua), so this test would have caught the m4 bug it now guards against returning.
    @Test("The ink the board picks clears 4.5:1 on every palette background, in both appearances")
    func everyPaletteBackgroundHasAReadableInk() throws {
        let ink = BoardTextInk.labelInk()
        for entry in Palette.backgrounds {
            let color = try #require(
                NSColor(paletteHex: entry.hex).flatMap(SRGBColor.init),
                "palette background '\(entry.name)' did not parse"
            )
            for appearance in [ColorScheme.light, .dark] {
                let choice = try #require(
                    BoardTextInk.choice(forBoardBackground: .valid(entry.name), appearance: appearance),
                    "palette background '\(entry.name)' decided no ink"
                )
                // The decision is taken on the very colour the well paints — a name that resolved to
                // something else would make every ratio below a measurement of the wrong surface.
                #expect(BoardTextInk.paintedColor(.valid(entry.name)) == color)
                #expect(
                    choice.meetsAA,
                    """
                    '\(entry.name)' (\(entry.hex)) in the \(appearance) appearance: the chosen \
                    \(choice.scheme) ink reaches only \(choice.ratio):1
                    """
                )
                // And the ink that was chosen is the one that scores — not merely a passing ink that
                // some other branch would have picked.
                #expect(abs(ContrastMath.ratio(of: ink[choice.scheme], on: color) - choice.ratio) < 0.0001)
            }
        }
    }

    /// The other half of "verified at design time": **the appearance-native label is not enough**,
    /// which is why the selection has to happen at all.
    ///
    /// Every well is a colour the system's own label fails on in one of the two
    /// appearances — the dark ones under Aqua, `chalk` and `aluminum` under Dark Aqua. Stating
    /// it as a test keeps the reasoning from decaying into folklore: if a future palette were tame
    /// enough that the native label always worked, this would fail and the seam's board-side wiring
    /// could be reconsidered rather than carried on faith.
    @Test("No palette background is readable under the appearance-native label in both appearances")
    func theNativeLabelIsNeverEnough() throws {
        for entry in Palette.backgrounds {
            let color = try #require(NSColor(paletteHex: entry.hex).flatMap(SRGBColor.init))
            let ink = BoardTextInk.labelInk()
            let native = [ColorScheme.light, .dark].filter {
                ContrastMath.ratio(of: ink[$0], on: color) >= ContrastMath.aaThreshold
            }
            #expect(
                native.count == 1,
                "'\(entry.name)' reads under \(native.count) native labels — the palette has changed shape"
            )
        }
    }

    /// Every well is opaque, which is why the design can verify them at all: a palette well with
    /// alpha would make its own contrast a function of the appearance's window background, and
    /// "verified at design time" would stop being a statement anyone could check.
    @Test("No palette background carries alpha")
    func thePaletteIsOpaque() throws {
        for entry in Palette.backgrounds {
            let color = try #require(NSColor(paletteHex: entry.hex).flatMap(SRGBColor.init))
            #expect(color.alpha == 1, "'\(entry.name)' is translucent")
        }
    }
}
