import AppKit
import Foundation
import QuartzCore
import AlignerCore

@MainActor
final class Round1PerformancePoCController {
    static let overlayTitle = "Aligner Round01 Performance PoC"

    private let view = Round1PerformancePoCView()
    private lazy var coordinator = WindowCoordinator(
        contentView: view,
        title: Self.overlayTitle
    )
    private var autoCycleTimer: Timer?
    private var lifecycleTargetCycles = 0
    private var lifecycleCompletedCycles = 0
    private var lifecycleMaximumOverlayWindows = 0
    private var lifecycleMaximumVisibleOverlayWindows = 0
    private var lifecycleResidualOverlayWindows = 0
    private var lifecycleResidualVisibleOverlayWindows = 0

    func show(autoCycle: Bool) {
        view.onEscape = { [weak self] in
            self?.hide(reason: .escape)
        }
        view.beginFirstFrameMeasurement()
        coordinator.openQuickSwitch()
        coordinator.promoteForTextInput()
        view.window?.makeFirstResponder(view)
        view.layoutSubtreeIfNeeded()
        DispatchQueue.main.async { [weak view] in
            view?.recordFirstFrameIfNeeded()
        }
        view.startSimulatedScreenshotRefill()
        updateLifecycleWindowHighWaterMark()

        if autoCycle {
            startAutoCycle()
        }
    }

    func hide() {
        hide(reason: .userClosed)
    }

    func reportDictionary() -> [String: Any] {
        var report = view.reportDictionary()
        report["overlayVisible"] = coordinator.isQuickSwitchVisible
        report["overlayWindowCount"] = overlayWindowCount()
        report["visibleOverlayWindowCount"] = visibleOverlayWindowCount()
        report["lifecycleTargetCycles"] = lifecycleTargetCycles
        report["lifecycleCompletedCycles"] = lifecycleCompletedCycles
        report["lifecycleMaximumOverlayWindows"] = lifecycleMaximumOverlayWindows
        report["lifecycleMaximumVisibleOverlayWindows"] = lifecycleMaximumVisibleOverlayWindows
        report["lifecycleResidualOverlayWindows"] = lifecycleResidualOverlayWindows
        report["lifecycleResidualVisibleOverlayWindows"] = lifecycleResidualVisibleOverlayWindows
        report["lifecycleGatePassed"] = lifecycleTargetCycles == 0
            || (
                lifecycleCompletedCycles == lifecycleTargetCycles
                    && lifecycleMaximumOverlayWindows <= 1
                    && lifecycleMaximumVisibleOverlayWindows <= 1
                    && lifecycleResidualOverlayWindows <= 1
                    && lifecycleResidualVisibleOverlayWindows == 0
            )
        return report
    }

    func prepareLifecycleRun(targetCycles: Int) {
        lifecycleTargetCycles = max(0, targetCycles)
        lifecycleCompletedCycles = 0
        lifecycleMaximumOverlayWindows = 0
        lifecycleMaximumVisibleOverlayWindows = 0
        lifecycleResidualOverlayWindows = 0
        lifecycleResidualVisibleOverlayWindows = 0
        view.resetPerformanceMeasurements()
    }

    func markLifecycleCycleCompleted() {
        lifecycleCompletedCycles += 1
        updateLifecycleWindowHighWaterMark()
    }

    func finalizeLifecycleRun() {
        lifecycleResidualOverlayWindows = overlayWindowCount()
        lifecycleResidualVisibleOverlayWindows = visibleOverlayWindowCount()
        updateLifecycleWindowHighWaterMark()
    }

    private func hide(reason: DismissReason) {
        stopAutoCycle()
        view.stopSimulatedScreenshotRefill()
        coordinator.closeQuickSwitch(reason: reason)
        lifecycleResidualOverlayWindows = overlayWindowCount()
        lifecycleResidualVisibleOverlayWindows = visibleOverlayWindowCount()
    }

    private func startAutoCycle() {
        stopAutoCycle()
        autoCycleTimer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { [weak self] _ in
            Task { @MainActor in
                guard let self else { return }
                self.view.moveSelection(delta: 1)
                self.view.simulateHoverStep(delta: 7)
            }
        }
    }

    private func stopAutoCycle() {
        autoCycleTimer?.invalidate()
        autoCycleTimer = nil
    }

    private func updateLifecycleWindowHighWaterMark() {
        lifecycleMaximumOverlayWindows = max(
            lifecycleMaximumOverlayWindows,
            overlayWindowCount()
        )
        lifecycleMaximumVisibleOverlayWindows = max(
            lifecycleMaximumVisibleOverlayWindows,
            visibleOverlayWindowCount()
        )
    }

    private func overlayWindowCount() -> Int {
        NSApp.windows.filter { window in
            window.title == Self.overlayTitle
        }.count
    }

    private func visibleOverlayWindowCount() -> Int {
        NSApp.windows.filter { window in
            window.title == Self.overlayTitle && window.isVisible
        }.count
    }
}

struct Round1PerformancePoCOptions {
    let showPerformancePoC: Bool
    let autoCycle: Bool
    let autoHideAfter: TimeInterval?
    let quitAfter: TimeInterval?
    let reportPath: String?
    let lifecycleCycles: Int?
    let lifecycleInterval: TimeInterval
    let lifecycleVisibleDuration: TimeInterval

    static func parse(arguments: [String]) -> Round1PerformancePoCOptions {
        Round1PerformancePoCOptions(
            showPerformancePoC: arguments.contains("--round01-performance-poc")
                || arguments.contains("--round1-performance-poc"),
            autoCycle: arguments.contains("--round01-poc-auto-cycle")
                || arguments.contains("--round1-poc-auto-cycle"),
            autoHideAfter: timeInterval(for: "--round01-poc-auto-hide-after", in: arguments)
                ?? timeInterval(for: "--round1-poc-auto-hide-after", in: arguments),
            quitAfter: timeInterval(for: "--round01-poc-quit-after", in: arguments)
                ?? timeInterval(for: "--round1-poc-quit-after", in: arguments),
            reportPath: stringValue(for: "--round01-poc-report", in: arguments)
                ?? stringValue(for: "--round1-poc-report", in: arguments),
            lifecycleCycles: intValue(for: "--round01-poc-lifecycle-cycles", in: arguments)
                ?? intValue(for: "--round1-poc-lifecycle-cycles", in: arguments),
            lifecycleInterval: timeInterval(for: "--round01-poc-lifecycle-interval", in: arguments)
                ?? timeInterval(for: "--round1-poc-lifecycle-interval", in: arguments)
                ?? 0.03,
            lifecycleVisibleDuration: timeInterval(for: "--round01-poc-lifecycle-visible-duration", in: arguments)
                ?? timeInterval(for: "--round1-poc-lifecycle-visible-duration", in: arguments)
                ?? 0.24
        )
    }

    private static func timeInterval(for key: String, in arguments: [String]) -> TimeInterval? {
        let prefix = "\(key)="
        guard let argument = arguments.first(where: { $0.hasPrefix(prefix) }) else {
            return nil
        }

        return TimeInterval(argument.dropFirst(prefix.count))
    }

    private static func stringValue(for key: String, in arguments: [String]) -> String? {
        let prefix = "\(key)="
        guard let argument = arguments.first(where: { $0.hasPrefix(prefix) }) else {
            return nil
        }

        return String(argument.dropFirst(prefix.count))
    }

    private static func intValue(for key: String, in arguments: [String]) -> Int? {
        let prefix = "\(key)="
        guard let argument = arguments.first(where: { $0.hasPrefix(prefix) }) else {
            return nil
        }

        return Int(argument.dropFirst(prefix.count))
    }
}

@MainActor
private final class Round1PerformancePoCView: NSView {
    private static let cardCount = 200
    private static let appCount = 20

    private let backgroundLayer = CAGradientLayer()
    private let appShelfLayer = CALayer()
    private let waterfallLayer = CALayer()
    private let spotlightLayer = CAGradientLayer()
    private var cardLayers: [CALayer] = []
    private var thumbnailLayers: [CALayer] = []
    private var appLayers: [CALayer] = []
    private var trackingArea: NSTrackingArea?
    private var selectedIndex = 0
    private var hoveredIndex: Int?
    private var scrollOffset: CGFloat = 0
    private var scrollAdjustmentCount = 0
    private var simulatedScreenshotRefillCount = 0
    private var firstFrameMeasurementStart: CFTimeInterval?
    private var firstFrameLatencyMilliseconds: [Double] = []
    private var keyboardResponseLatencyMilliseconds: [Double] = []
    private var hoverResponseLatencyMilliseconds: [Double] = []
    private var screenshotRefillTask: Task<Void, Never>?
    var onEscape: (() -> Void)?

    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        wantsLayer = true
        setupLayers()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        wantsLayer = true
        setupLayers()
    }

    override var acceptsFirstResponder: Bool {
        true
    }

    override func updateTrackingAreas() {
        super.updateTrackingAreas()

        if let trackingArea {
            removeTrackingArea(trackingArea)
        }

        let area = NSTrackingArea(
            rect: bounds,
            options: [.mouseMoved, .activeAlways, .inVisibleRect],
            owner: self
        )
        addTrackingArea(area)
        trackingArea = area
    }

    override func layout() {
        super.layout()
        layoutLayers()
    }

    override func mouseMoved(with event: NSEvent) {
        let point = convert(event.locationInWindow, from: nil)
        guard let index = cardLayers.firstIndex(where: { $0.frame.offsetBy(dx: 0, dy: scrollOffset).contains(point) }) else { return }
        hoverCard(at: index, animated: true)
        selectCard(at: index)
    }

    override func keyDown(with event: NSEvent) {
        switch event.keyCode {
        case 48, 124, 125:
            moveSelection(delta: 1)
        case 123, 126:
            moveSelection(delta: -1)
        case 53:
            onEscape?()
        default:
            super.keyDown(with: event)
        }
    }

    override func scrollWheel(with event: NSEvent) {
        setScrollOffset(scrollOffset - event.scrollingDeltaY, animated: true)
    }

    func moveSelection(delta: Int) {
        guard !cardLayers.isEmpty else { return }
        let measurementStart = CACurrentMediaTime()
        let nextIndex = min(max(selectedIndex + delta, 0), cardLayers.count - 1)
        selectCard(at: nextIndex)
        CATransaction.flush()
        keyboardResponseLatencyMilliseconds.append(milliseconds(since: measurementStart))
    }

    func simulateHoverStep(delta: Int) {
        guard !cardLayers.isEmpty else { return }
        let baseIndex = hoveredIndex ?? selectedIndex
        let nextIndex = (baseIndex + delta) % cardLayers.count
        let measurementStart = CACurrentMediaTime()
        hoverCard(at: nextIndex, animated: true)
        selectCard(at: nextIndex)
        CATransaction.flush()
        hoverResponseLatencyMilliseconds.append(milliseconds(since: measurementStart))
    }

    func startSimulatedScreenshotRefill() {
        stopSimulatedScreenshotRefill()
        simulatedScreenshotRefillCount = 0
        screenshotRefillTask = Task { @MainActor [weak self] in
            guard let self else { return }
            let batchSize = 20
            for startIndex in stride(from: 0, to: self.thumbnailLayers.count, by: batchSize) {
                if Task.isCancelled { return }
                let endIndex = min(startIndex + batchSize, self.thumbnailLayers.count)
                self.refillSimulatedScreenshots(in: startIndex..<endIndex)
                try? await Task.sleep(nanoseconds: 20_000_000)
            }
        }
    }

    func stopSimulatedScreenshotRefill() {
        screenshotRefillTask?.cancel()
        screenshotRefillTask = nil
    }

    func beginFirstFrameMeasurement() {
        firstFrameMeasurementStart = CACurrentMediaTime()
    }

    func recordFirstFrameIfNeeded() {
        guard let firstFrameMeasurementStart else { return }

        CATransaction.flush()
        firstFrameLatencyMilliseconds.append(milliseconds(since: firstFrameMeasurementStart))
        self.firstFrameMeasurementStart = nil
    }

    func resetPerformanceMeasurements() {
        firstFrameMeasurementStart = nil
        firstFrameLatencyMilliseconds = []
        keyboardResponseLatencyMilliseconds = []
        hoverResponseLatencyMilliseconds = []
    }

    func reportDictionary() -> [String: Any] {
        let firstFrameP95 = percentile95(firstFrameLatencyMilliseconds)
        let keyboardP95 = percentile95(keyboardResponseLatencyMilliseconds)
        let hoverP95 = percentile95(hoverResponseLatencyMilliseconds)
        let firstFrameGatePassed = firstFrameP95.map { $0 < 150 } ?? false
        let keyboardGatePassed = keyboardP95.map { $0 < 50 } ?? false
        let hoverGatePassed = hoverP95.map { $0 <= 32 } ?? false
        let interactionPerformanceGatePassed = firstFrameGatePassed && keyboardGatePassed && hoverGatePassed

        return [
            "cardCount": Self.cardCount,
            "appCount": Self.appCount,
            "cardLayerCount": cardLayers.count,
            "appLayerCount": appLayers.count,
            "thumbnailLayerCount": thumbnailLayers.count,
            "simulatedScreenshotRefillCount": simulatedScreenshotRefillCount,
            "selectedIndex": selectedIndex,
            "scrollOffset": Double(scrollOffset),
            "maximumScrollOffset": Double(maximumScrollOffset()),
            "scrollAdjustmentCount": scrollAdjustmentCount,
            "waterfallScrollable": maximumScrollOffset() > 0,
            "spotlightVisible": spotlightLayer.opacity > 0,
            "spotlightAlignedWithSelectedCard": spotlightLayer.frame.equalTo(
                cardLayers[selectedIndex].frame.insetBy(dx: -10, dy: -8)
            ),
            "firstFrameLatencySampleCount": firstFrameLatencyMilliseconds.count,
            "firstFrameLatencyP95Milliseconds": jsonNumber(firstFrameP95),
            "firstFrameLatencyMaxMilliseconds": jsonNumber(firstFrameLatencyMilliseconds.max()),
            "keyboardResponseSampleCount": keyboardResponseLatencyMilliseconds.count,
            "keyboardResponseP95Milliseconds": jsonNumber(keyboardP95),
            "keyboardResponseMaxMilliseconds": jsonNumber(keyboardResponseLatencyMilliseconds.max()),
            "hoverResponseSampleCount": hoverResponseLatencyMilliseconds.count,
            "hoverResponseP95Milliseconds": jsonNumber(hoverP95),
            "hoverResponseMaxMilliseconds": jsonNumber(hoverResponseLatencyMilliseconds.max()),
            "performanceThresholds": [
                "firstFrameMilliseconds": 150,
                "keyboardP95Milliseconds": 50,
                "hoverP95Milliseconds": 32
            ],
            "interactionPerformanceGatePassed": interactionPerformanceGatePassed,
            "performanceGatePassed": interactionPerformanceGatePassed
        ]
    }

    private func setupLayers() {
        guard let rootLayer = layer else { return }

        rootLayer.masksToBounds = true
        backgroundLayer.colors = [
            NSColor.windowBackgroundColor.withAlphaComponent(0.92).cgColor,
            NSColor.controlBackgroundColor.withAlphaComponent(0.84).cgColor
        ]
        backgroundLayer.startPoint = CGPoint(x: 0.5, y: 1.0)
        backgroundLayer.endPoint = CGPoint(x: 0.5, y: 0.0)
        rootLayer.addSublayer(backgroundLayer)

        rootLayer.addSublayer(appShelfLayer)
        rootLayer.addSublayer(waterfallLayer)

        appLayers = (0..<Self.appCount).map { index in
            let appLayer = CALayer()
            appLayer.cornerRadius = 12
            appLayer.backgroundColor = appColor(index: index).cgColor
            appLayer.shadowColor = NSColor.black.cgColor
            appLayer.shadowOpacity = 0.18
            appLayer.shadowRadius = 8
            appLayer.shadowOffset = CGSize(width: 0, height: -1)
            appShelfLayer.addSublayer(appLayer)
            return appLayer
        }

        let cardsAndThumbnails: [(CALayer, CALayer)] = (0..<Self.cardCount).map { index in
            let cardLayer = CALayer()
            cardLayer.cornerRadius = 14
            cardLayer.backgroundColor = cardColor(index: index).cgColor
            cardLayer.borderColor = NSColor.separatorColor.withAlphaComponent(0.3).cgColor
            cardLayer.borderWidth = 1
            cardLayer.shadowColor = NSColor.black.cgColor
            cardLayer.shadowOpacity = 0.0
            cardLayer.shadowRadius = 0
            cardLayer.shadowOffset = CGSize(width: 0, height: -2)

            let thumbnailLayer = CALayer()
            thumbnailLayer.cornerRadius = 9
            thumbnailLayer.backgroundColor = NSColor.quaternaryLabelColor.withAlphaComponent(0.28).cgColor
            thumbnailLayer.borderColor = NSColor.separatorColor.withAlphaComponent(0.2).cgColor
            thumbnailLayer.borderWidth = 1
            cardLayer.addSublayer(thumbnailLayer)

            waterfallLayer.addSublayer(cardLayer)
            return (cardLayer, thumbnailLayer)
        }
        cardLayers = cardsAndThumbnails.map(\.0)
        thumbnailLayers = cardsAndThumbnails.map(\.1)

        spotlightLayer.colors = [
            NSColor.clear.cgColor,
            NSColor.white.withAlphaComponent(0.28).cgColor,
            NSColor.clear.cgColor
        ]
        spotlightLayer.locations = [0, 0.5, 1]
        spotlightLayer.startPoint = CGPoint(x: 0, y: 0.5)
        spotlightLayer.endPoint = CGPoint(x: 1, y: 0.5)
        spotlightLayer.opacity = 0
        waterfallLayer.addSublayer(spotlightLayer)
        selectCard(at: 0, animated: false)
    }

    private func layoutLayers() {
        CATransaction.begin()
        CATransaction.setDisableActions(true)

        backgroundLayer.frame = bounds
        appShelfLayer.frame = CGRect(x: 0, y: bounds.height - 108, width: bounds.width, height: 92)
        waterfallLayer.frame = bounds

        layoutAppShelf()
        layoutCards()
        applyScrollOffset(animated: false)
        positionSpotlight(animated: false)

        CATransaction.commit()
    }

    private func layoutAppShelf() {
        let iconSize: CGFloat = 48
        let gap: CGFloat = 14
        let totalWidth = CGFloat(appLayers.count) * iconSize + CGFloat(max(0, appLayers.count - 1)) * gap
        let startX = max(24, (bounds.width - totalWidth) / 2)

        for (index, layer) in appLayers.enumerated() {
            layer.frame = CGRect(
                x: startX + CGFloat(index) * (iconSize + gap),
                y: 20,
                width: iconSize,
                height: iconSize
            )
        }
    }

    private func layoutCards() {
        let columns = max(1, min(10, Int(bounds.width / 180)))
        let gap: CGFloat = 16
        let sideInset: CGFloat = max(32, (bounds.width - CGFloat(columns) * 156 - CGFloat(columns - 1) * gap) / 2)
        let topY = bounds.height - 204

        for (index, layer) in cardLayers.enumerated() {
            let column = index % columns
            let row = index / columns
            layer.frame = CGRect(
                x: sideInset + CGFloat(column) * (156 + gap),
                y: topY - CGFloat(row) * 104,
                width: 156,
                height: 86
            )
            thumbnailLayers[index].frame = CGRect(x: 12, y: 16, width: 132, height: 52)
        }
        setScrollOffset(scrollOffset, animated: false)
        updateCardVisual(at: selectedIndex, animated: false)
        if let hoveredIndex {
            updateCardVisual(at: hoveredIndex, animated: false)
        }
    }

    private func selectCard(at index: Int, animated: Bool = true) {
        guard cardLayers.indices.contains(index) else { return }
        let oldIndex = selectedIndex
        selectedIndex = index

        updateCardVisual(at: oldIndex, animated: animated)
        updateCardVisual(at: selectedIndex, animated: animated)
        scrollSelectedCardIntoView(animated: animated)
        positionSpotlight(animated: animated)
    }

    private func hoverCard(at index: Int, animated: Bool) {
        guard cardLayers.indices.contains(index) else { return }

        let oldIndex = hoveredIndex
        hoveredIndex = index

        if let oldIndex {
            updateCardVisual(at: oldIndex, animated: animated)
        }
        updateCardVisual(at: index, animated: animated)
    }

    private func updateCardVisual(at index: Int, animated: Bool) {
        guard cardLayers.indices.contains(index) else { return }
        let layer = cardLayers[index]
        let isSelected = index == selectedIndex
        let isHovered = index == hoveredIndex
        let scale: CGFloat
        let shadowOpacity: Float
        let shadowRadius: CGFloat
        let borderColor: CGColor

        if isSelected {
            scale = 1.045
            shadowOpacity = 0.32
            shadowRadius = 18
            borderColor = NSColor.keyboardFocusIndicatorColor.withAlphaComponent(0.72).cgColor
        } else if isHovered {
            scale = 1.025
            shadowOpacity = 0.22
            shadowRadius = 12
            borderColor = NSColor.controlAccentColor.withAlphaComponent(0.48).cgColor
        } else {
            scale = 1
            shadowOpacity = 0
            shadowRadius = 0
            borderColor = NSColor.separatorColor.withAlphaComponent(0.3).cgColor
        }

        CATransaction.begin()
        CATransaction.setAnimationDuration(animated ? 0.16 : 0)
        CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .easeOut))
        layer.transform = scale == 1 ? CATransform3DIdentity : CATransform3DMakeScale(scale, scale, 1)
        layer.shadowOpacity = shadowOpacity
        layer.shadowRadius = shadowRadius
        layer.borderColor = borderColor
        CATransaction.commit()
    }

    private func positionSpotlight(animated: Bool) {
        guard cardLayers.indices.contains(selectedIndex) else { return }
        let selectedFrame = cardLayers[selectedIndex].frame.insetBy(dx: -10, dy: -8)

        CATransaction.begin()
        CATransaction.setAnimationDuration(animated ? 0.16 : 0)
        CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .easeOut))
        spotlightLayer.frame = selectedFrame
        spotlightLayer.cornerRadius = 16
        spotlightLayer.opacity = 1
        CATransaction.commit()

        guard animated else { return }
        let animation = CABasicAnimation(keyPath: "locations")
        animation.fromValue = [-0.6, -0.2, 0.2]
        animation.toValue = [0.8, 1.2, 1.6]
        animation.duration = 0.8
        animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        spotlightLayer.add(animation, forKey: "aligner.round1.spotlight")
    }

    private func refillSimulatedScreenshots(in range: Range<Int>) {
        CATransaction.begin()
        CATransaction.setAnimationDuration(0.12)
        for index in range where thumbnailLayers.indices.contains(index) {
            thumbnailLayers[index].backgroundColor = simulatedScreenshotColor(index: index).cgColor
            thumbnailLayers[index].borderColor = NSColor.white.withAlphaComponent(0.36).cgColor
            simulatedScreenshotRefillCount += 1
        }
        CATransaction.commit()
    }

    private func scrollSelectedCardIntoView(animated: Bool) {
        guard cardLayers.indices.contains(selectedIndex) else { return }

        let visibleMinY: CGFloat = 24
        let visibleMaxY = max(visibleMinY, bounds.height - 128)
        let selectedFrame = cardLayers[selectedIndex].frame.offsetBy(dx: 0, dy: scrollOffset)
        var nextOffset = scrollOffset

        if selectedFrame.minY < visibleMinY {
            nextOffset += visibleMinY - selectedFrame.minY
        } else if selectedFrame.maxY > visibleMaxY {
            nextOffset -= selectedFrame.maxY - visibleMaxY
        }

        let clampedOffset = min(max(nextOffset, 0), maximumScrollOffset())
        if clampedOffset != scrollOffset {
            scrollAdjustmentCount += 1
            setScrollOffset(clampedOffset, animated: animated)
        }
    }

    private func setScrollOffset(_ offset: CGFloat, animated: Bool) {
        let clampedOffset = min(max(offset, 0), maximumScrollOffset())
        guard clampedOffset != scrollOffset || waterfallLayer.sublayerTransform.m42 != clampedOffset else { return }
        scrollOffset = clampedOffset
        applyScrollOffset(animated: animated)
        positionSpotlight(animated: animated)
    }

    private func applyScrollOffset(animated: Bool) {
        CATransaction.begin()
        CATransaction.setAnimationDuration(animated ? 0.12 : 0)
        CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .easeOut))
        waterfallLayer.sublayerTransform = CATransform3DMakeTranslation(0, scrollOffset, 0)
        CATransaction.commit()
    }

    private func maximumScrollOffset() -> CGFloat {
        guard let minY = cardLayers.map(\.frame.minY).min() else { return 0 }
        return max(0, 24 - minY)
    }

    private func appColor(index: Int) -> NSColor {
        let hue = CGFloat(index) / CGFloat(Self.appCount)
        return NSColor(calibratedHue: hue, saturation: 0.56, brightness: 0.88, alpha: 1)
    }

    private func cardColor(index: Int) -> NSColor {
        let hue = CGFloat(index % Self.appCount) / CGFloat(Self.appCount)
        return NSColor(calibratedHue: hue, saturation: 0.18, brightness: 0.96, alpha: 0.92)
    }

    private func simulatedScreenshotColor(index: Int) -> NSColor {
        let hue = CGFloat((index * 7) % Self.cardCount) / CGFloat(Self.cardCount)
        return NSColor(calibratedHue: hue, saturation: 0.36, brightness: 0.78, alpha: 0.9)
    }

    private func milliseconds(since start: CFTimeInterval) -> Double {
        (CACurrentMediaTime() - start) * 1_000
    }

    private func percentile95(_ values: [Double]) -> Double? {
        guard !values.isEmpty else { return nil }

        let sortedValues = values.sorted()
        let index = min(
            sortedValues.count - 1,
            Int(ceil(Double(sortedValues.count) * 0.95)) - 1
        )
        return sortedValues[index]
    }

    private func jsonNumber(_ value: Double?) -> Any {
        value ?? NSNull()
    }
}
