import SwiftUI
import AppKit

/// Turns off the sidebar list's *elastic overscroll* (the rubber-band bounce past the
/// top/bottom of its content).
///
/// Why: the sidebar/home/new/notifications buttons live in the window's unified toolbar,
/// whose macOS 26 Liquid Glass samples and blurs whatever sits beneath it — normally shielded
/// by `.toolbarBackground(AppTheme.background)` (see `WindowToolbarSurface`). Like the dormancy
/// dropout `ToolbarGlassKeeper` cures, that shield briefly falls through on a relayout and the
/// glass re-samples the saturated `BuildBanner` strip below the toolbar — flashing the toolbar
/// buttons the banner's tint (canary-yellow on canary, red on dev). Pulling the sidebar list
/// *up* past its top rubber-bands it, and that bounce is exactly such a relayout — which is why
/// the flash only appears on scroll-up. Removing the elastic bounce removes the trigger; the
/// list still scrolls normally, it just stops at its content edges instead of springing past
/// them.
///
/// Reaches the list's `NSScrollView` the way `SidebarColumnConfigurator` reaches the split view:
/// an invisible host in the sidebar walks up to the enclosing `NSSplitView`, then down into its
/// sidebar column (`arrangedSubviews[0]`) to the outermost scroll view — the main list's. The
/// nested `SidebarPane` scroll views sit deeper, inside the list's rows, so they're left with
/// their own bounce.
struct SidebarScrollElasticityConfigurator: NSViewRepresentable {
    func makeNSView(context: Context) -> NSView {
        let view = NSView(frame: .zero)
        // Not in a window yet during makeNSView; the MainActor hop lets the view tree resolve
        // first (same pattern as `SidebarColumnConfigurator`).
        Task { @MainActor in configure(from: view) }
        return view
    }

    func updateNSView(_ nsView: NSView, context: Context) {
        // Reassert on updates in case SwiftUI rebuilt the list's scroll view under us.
        Task { @MainActor in configure(from: nsView) }
    }

    @MainActor
    private func configure(from view: NSView) {
        guard let splitView = enclosingSplitView(of: view),
              // arrangedSubviews[0] is the sidebar column (arrangedSubviews[1] is the detail
              // pane); the split view lays out with `arrangesAllSubviews="no"`, so plain
              // `subviews.first` isn't reliably the sidebar.
              let sidebar = splitView.arrangedSubviews.first ?? splitView.subviews.first,
              let scrollView = listScrollView(in: sidebar)
        else { return }
        if scrollView.verticalScrollElasticity != .none {
            scrollView.verticalScrollElasticity = .none
        }
    }

    private func enclosingSplitView(of view: NSView) -> NSSplitView? {
        var current: NSView? = view.superview
        while let candidate = current {
            if let split = candidate as? NSSplitView { return split }
            current = candidate.superview
        }
        return nil
    }

    /// Breadth-first search for the sidebar list's own scroll view: the shallowest `NSScrollView`
    /// in the sidebar column. On macOS 26 a `.sidebar`-style `List` is backed by a SwiftUI view
    /// (its `documentView` is a plain `NSView`, not an `NSTableView`), so identity comes from
    /// position in the tree, not the document type. The nested `SidebarPane` scroll views live
    /// deeper — inside this list's rows — so BFS reaches the outer list first and leaves the
    /// pane scroll views (and their own bounce) untouched.
    private func listScrollView(in root: NSView) -> NSScrollView? {
        var queue = [root]
        while !queue.isEmpty {
            let next = queue.removeFirst()
            if let scroll = next as? NSScrollView { return scroll }
            queue.append(contentsOf: next.subviews)
        }
        return nil
    }
}
