import SwiftUI

#if DEBUG
/// **Render-cost counters for the board strip — DEBUG only, and the only way a test can tell "one
/// card repainted" from "the whole board rebuilt".**
///
/// Ported from the pathfinder (`Kanban/Views/MasonryLayout.swift`'s `BoardRenderMetrics`), which is
/// where the shape comes from: a flat bag of counters, a `reset()`, and one `count…` call at the top
/// of each body being watched. What is new here is the masonry's **column** vocabulary and the
/// trash column's own counter — Lanework's strip has three body levels, not two.
///
/// ### What it is for
///
/// The 2026-07-31 drag-performance work shipped four gates — `LaneView.==` / `CardFaceView.==`
/// applied through `.equatable()` (84f909a), the resting-layout cache (a51ad75), the marquee's
/// de-observation (f6105d4) and the resize hold (409f430) — and every one of them is a claim about
/// *how many bodies run*. A claim like that is unfalsifiable from the outside: the board looks the
/// same either way, only the frame rate differs, and a frame rate is a machine fact rather than a
/// test. These counters are what turn each gate into an assertion (`BoardRenderPerformanceTests`).
///
/// The reading half is RENDER-INSTRUMENTATION.md at the repo root, beside DRAG-REORDER.md: the
/// Instruments 26 SwiftUI template, the Hitches thresholds, and how the drag signposts
/// (`DragSignposts`) line up against these numbers.
///
/// ### Why the storage is what it is
///
/// `nonisolated(unsafe) static var`, exactly as the pathfinder has it. In practice every write
/// happens on the main actor — SwiftUI runs body evaluation *and* `Layout` callbacks there — but the
/// `Layout` conformance itself is `nonisolated`, so a main-actor-isolated counter would not be
/// reachable from `MasonryLayout.sizeThatFits`. The unsafety is real and deliberately accepted: this
/// type does not exist in a release build, and the alternative is a lock on the layout hot path of a
/// board the whole point is to keep fast.
///
/// **Nothing in the app reads these.** They are written by the bodies below and read by tests. A
/// counter that branched would be instrumentation that changed the thing it measures.
enum BoardRenderMetrics {

    /// Every `subview.sizeThatFits(…)` `MasonryLayout` actually performed — the number its
    /// measurement cache exists to drive down.
    nonisolated(unsafe) static var masonryMeasurements = 0

    /// Measurement requests served from that cache instead.
    nonisolated(unsafe) static var masonryCacheHits = 0

    /// `MasonryLayout.sizeThatFits` calls — SwiftUI probes a layout more than once per pass, which
    /// is the multiplier the cache absorbs.
    nonisolated(unsafe) static var masonrySizeThatFitsCalls = 0

    /// `MasonryLayout.placeSubviews` calls.
    nonisolated(unsafe) static var masonryPlaceCalls = 0

    /// `CardFaceView.body` evaluations — both homes, board and trash, since it is one view.
    nonisolated(unsafe) static var cardBodyEvaluations = 0

    /// `BoardView.body` evaluations — the strip itself.
    ///
    /// Not one of the pathfinder's counters, and it earns its place by being the **discriminator**:
    /// "a lane re-ran" means one of two completely different things depending on whether the strip
    /// re-ran with it. Strip **and** lanes is a parent pass whose `.equatable()` gate did not
    /// suppress; lanes with the strip *still* is a direct Observation invalidation, which no gate has
    /// any say over (`LaneView.==`'s own note). Without this number the two are indistinguishable
    /// from a test, and the first diagnosis this instrumentation produced turned on exactly that
    /// distinction (RENDER-INSTRUMENTATION.md ▸ What the first run found).
    nonisolated(unsafe) static var stripBodyEvaluations = 0

    /// `LaneView.body` evaluations (the pathfinder's `columnBodyEvaluations`, renamed to Lanework's
    /// vocabulary: a lane is the kanban column, and a *column* is an interior masonry track).
    nonisolated(unsafe) static var laneBodyEvaluations = 0

    /// `TrashLaneView.body` evaluations — counted separately from the lanes because the column is
    /// not one: it renders only while shown, and it re-runs for reasons the lanes do not have.
    nonisolated(unsafe) static var trashLaneBodyEvaluations = 0

    /// Every container body the strip draws — what "≤ N container bodies" is asserted against, so a
    /// regression cannot hide by moving from one counter to the other.
    static var containerBodyEvaluations: Int {
        laneBodyEvaluations + trashLaneBodyEvaluations
    }

    static func reset() {
        masonryMeasurements = 0
        masonryCacheHits = 0
        masonrySizeThatFitsCalls = 0
        masonryPlaceCalls = 0
        cardBodyEvaluations = 0
        stripBodyEvaluations = 0
        laneBodyEvaluations = 0
        trashLaneBodyEvaluations = 0
    }

    static func countStripBody() { stripBodyEvaluations += 1 }
    static func countCardBody() { cardBodyEvaluations += 1 }
    static func countLaneBody() { laneBodyEvaluations += 1 }
    static func countTrashLaneBody() { trashLaneBodyEvaluations += 1 }
}
#endif
