import AppKit
import NucleicCore
import SwiftUI

private func intelligenceShimmerPeriod(fraction: Double) -> Double {
    3.0 - 2.1 * fraction
}

extension Notification.Name {
    /// Emitted by `ChatInputField` for the in-session slider, whose large host view keeps its
    /// existing construction API. The source object is the field's window.
    static let composerTextDidChange = Notification.Name("nucleic.composerTextDidChange")
}

enum ComposerTextChangeNotification {
    static let textKey = "text"
}

/// The subagent constellation just beyond Max. It selects the existing Orchestra backend mode
/// while keeping the single-agent capability level at Max.
enum IntelligenceOrchestraTier: Int, CaseIterable, Identifiable {
    case orchestra

    var id: Int { rawValue }
    var displayName: String { "Orchestra" }
    var level: IntelligenceLevel { .max }
}

private enum IntelligenceSliderLane {
    case standard
    case orchestra
}

private enum IntelligenceOrchestraDirection: Int, CaseIterable, Identifiable {
    case top
    case upperOuter
    case upperInner
    case right
    case lowerInner
    case lowerOuter
    case bottom

    var id: Int { rawValue }

    var degrees: Double {
        [-90, -60, -30, 0, 30, 60, 90][rawValue]
    }

    var radians: Double { degrees * .pi / 180 }

    var unitOffset: CGSize {
        CGSize(
            width: CGFloat(cos(radians)),
            height: CGFloat(sin(radians)))
    }
}

/// A right-facing half-orbit ties the satellites into one multi-agent system without turning
/// the constellation into another button or stop on the rail.
private struct IntelligenceOrchestraArc: Shape {
    func path(in rect: CGRect) -> Path {
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let radius = min(rect.width, rect.height) / 2
        var path = Path()

        for step in 0...24 {
            let radians = -.pi / 2 + .pi * CGFloat(step) / 24
            let point = CGPoint(
                x: center.x + cos(radians) * radius,
                y: center.y + sin(radians) * radius)
            if step == 0 {
                path.move(to: point)
            } else {
                path.addLine(to: point)
            }
        }
        return path
    }
}

/// The composer's Intelligence control: five single-agent stops plus a compact Orchestra node.
/// Its fill briefly "energizes"
/// while its level changes — the fill behind the nub carries a traveling shimmer that speeds
/// up, and a glow that brightens, with each stop, cresting into the signature orchestra gold
/// at Max. The motion runs continuously while the composer is empty, pauses while the user is
/// actively editing text, and resumes after three quiet seconds.
/// Replaces the Model + Effort menus when Intelligence routing is on.
///
/// Design lineage (deliberate, so it feels native to the app):
/// - drag mechanics from `ResizeHandle` — the nub is positioned directly from the gesture
///   and never animated while dragging, so it can't lag the cursor;
/// - the intensity ramp from `StreakBadge` — the animation accelerates and brightens with
///   the level and "only ever adds light";
/// - the glow layering from `OrchestraGlow` — a color crest plus two stacked shadows, with
///   the same deferred-`settled` guard so restore-time layout can't sweep the effect into
///   place; Reduce Motion and the settled state leave only the level's base fill.
///
/// The level is never conveyed by hue alone (`ColorVisionMode` palettes vary): fill length,
/// detent dots, and the fixed-width level name all carry it too.
struct IntelligenceSlider: View {
    @Binding var level: IntelligenceLevel
    @Binding var orchestraTier: IntelligenceOrchestraTier?
    var enabled: Bool = true
    /// The constellation remains visible only where subagent routing can actually run.
    var orchestraAvailable: Bool = false
    /// Off until the mounting composer has settled its first layout
    /// (`NewChatComposer.layoutAnimationsEnabled`) — the shimmer stays absent before that.
    var animationsSettled: Bool = true
    /// Draft text from the mounting composer. Its changes pause the shimmer while the user is
    /// typing; an empty draft or three seconds without another edit lets it run continuously.
    var composerText: String = ""
    /// The in-session host's compatibility path: its `ChatInputField` publishes draft changes
    /// from the active window rather than passing the draft through its currently locked view.
    var stopsShimmerAfterRelease: Bool = false
    /// During a gesture, reports the same provisional Orchestra state used by the label.
    /// `nil` means the gesture ended and the committed binding is authoritative again.
    var onOrchestraPreviewChanged: ((Bool?) -> Void)? = nil

    @Environment(\.appPalette) private var palette
    @Environment(\.accessibilityReduceMotion) private var reduceMotion
    /// Non-nil while the nub is being dragged — the displayed position, magnetized toward
    /// detents. The binding only commits on release.
    @State private var dragFraction: Double?
    @State private var dragLane: IntelligenceSliderLane?
    @State private var dragHasMoved = false
    /// A pull that begins on Orchestra mirrors the elastic curve so the stretched knob
    /// contracts back toward Max instead of materializing there halfway through the gesture.
    @State private var dragStartedFromOrchestra = false
    /// The nub is an interaction affordance rather than persistent decoration. The fill,
    /// detents, and label retain the level while it is hidden.
    @State private var hoveringTrack = false
    /// Empty composers shimmer continuously. Non-empty text edits pause this until the
    /// composer's three-second inactivity debounce completes.
    @State private var energyAnimating = true
    @State private var energyRestartGeneration = 0
    @State private var observedComposerText = ""
    /// The stretch rumble is spaced by actual elastic progress, not pointer-update cadence,
    /// so holding still is silent and fast hardware cannot turn it into a buzz.
    @State private var orchestraRumbleAnchor: Double?
    /// One-runloop-deferred animation gate (the `OrchestraGlow` idiom): without it, the
    /// shimmer's first phase and any level animation can sweep up the control's initial
    /// layout and visibly slide it into place on window restore.
    @State private var settled = false
    @FocusState private var focused: Bool

    private let trackHeight: CGFloat = 8
    private let nubSize: CGFloat = 20
    /// Elastic easing keeps the short Max → Orchestra half-step from feeling like an ordinary
    /// sixth intelligence detent.
    private let orchestraResistanceExponent = 2.0
    /// Keeps the right-hand satellite inside the slider's footprint when the constellation blooms.
    private let orchestraTrailingInset: CGFloat = 4
    /// Orchestra activates only near its dot, protecting Max despite the intentionally compact gap.
    private let orchestraActivationProgress = 0.90
    private let orchestraRumbleSpacing = 0.16
    private let clickSlop: CGFloat = 3
    /// The nub center's travel inset from the track ends (half the nub, so it never
    /// overhangs the capsule).
    private var inset: CGFloat { nubSize / 2 }
    /// Fixed footprint in the composer row (label + track); geometry inside adapts.
    static let preferredWidth: CGFloat = 240
    private static let labelWidth: CGFloat = 64
    private let controlHeight: CGFloat = 28

    /// Preview the active lane immediately during a drag; bindings still commit on release.
    private var displayedOrchestraTier: IntelligenceOrchestraTier? {
        if dragLane == .standard { return nil }
        if dragLane == .orchestra {
            return (dragFraction ?? 0) >= orchestraActivationProgress
                ? .orchestra : nil
        }
        return orchestraTier
    }

    private var displayedLevel: IntelligenceLevel {
        guard dragLane != .orchestra else {
            return displayedOrchestraTier?.level ?? .max
        }
        return dragFraction.map(IntelligenceSliderMath.nearestLevel(fraction:)) ?? level
    }

    private var displayedName: String {
        displayedOrchestraTier?.displayName ?? displayedLevel.displayName
    }
    private var effectiveComposerText: String {
        stopsShimmerAfterRelease ? observedComposerText : composerText
    }
    private var isInteractive: Bool { enabled }
    private var isBright: Bool { enabled || orchestraTier != nil }

    var body: some View {
        HStack(spacing: 8) {
            // The level name carries the value for color-vision users and Reduce Motion —
            // fixed width so Quick → Balanced can't reflow the composer row (StreakBadge's
            // pinned-frame rule).
            Text(displayedName)
                .font(.caption)
                .foregroundStyle(isBright ? AnyShapeStyle(.primary) : AnyShapeStyle(.tertiary))
                .monospacedDigit()
                .frame(width: Self.labelWidth, alignment: .leading)
            track
        }
        .frame(width: Self.preferredWidth, height: controlHeight)
        .opacity(isBright ? 1 : 0.5)
        .focusable(isInteractive)
        // Keep arrow-key adjustment without macOS drawing its persistent blue focus
        // rectangle around this custom-shaped control after a pointer click.
        .focusEffectDisabled()
        .focused($focused)
        .onMoveCommand { direction in
            guard isInteractive else { return }
            switch direction {
            case .left: step(-1)
            case .right: step(+1)
            default: break
            }
        }
        .accessibilityRepresentation {
            Slider(
                value: Binding(
                    get: { Double(level.rawValue) },
                    set: {
                        guard let next = IntelligenceLevel(rawValue: Int($0.rounded())) else {
                            return
                        }
                        setLevel(next)
                    }
                ),
                in: 1...Double(IntelligenceLevel.allCases.count), step: 1
            ) {
                Text("Intelligence")
            }
            .disabled(!isInteractive)
        }
        .accessibilityValue(Text(displayedName))
        .help(
            "Intelligence: \(displayedName) — drag the main rail for Quick through Max, "
                + "then pull across the short gap to the Orchestra constellation.")
        .onAppear { DispatchQueue.main.async { settled = true } }
        .onDisappear { onOrchestraPreviewChanged?(nil) }
        .onReceive(NotificationCenter.default.publisher(for: .composerTextDidChange)) {
            notification in
            guard stopsShimmerAfterRelease,
                  let sourceWindow = notification.object as? NSWindow,
                  sourceWindow === NSApp.keyWindow,
                  let text = notification.userInfo?[ComposerTextChangeNotification.textKey]
                    as? String
            else { return }
            observedComposerText = text
        }
        .task(id: effectiveComposerText) {
            guard !effectiveComposerText.isEmpty else {
                energyAnimating = true
                return
            }
            energyAnimating = false
            do {
                try await Task.sleep(for: .seconds(3))
            } catch {
                return
            }
            guard !Task.isCancelled else { return }
            energyAnimating = true
        }
    }

    private var track: some View {
        GeometryReader { geo in
            let width = geo.size.width
            let trackY = geo.size.height / 2
            let maxX = maxNodeX(width: width)
            let orchestraX = orchestraNodeX(width: width)
            let orchestraGap = orchestraAvailable ? max(orchestraX - maxX, 0) : 0
            let railLeading = inset - trackHeight / 2
            let mainRailWidth = maxX + trackHeight / 2 - railLeading
            let orchestraGuideWidth = orchestraX + trackHeight / 2 - railLeading
            let activeTier = displayedOrchestraTier
            let standardFraction = dragLane == .orchestra || activeTier != nil
                ? IntelligenceLevel.max.fraction
                : (dragFraction ?? level.fraction)
            let standardNubX = inset + (maxX - inset) * standardFraction
            let orchestraApproach: CGFloat = dragLane == .orchestra
                ? CGFloat(min((dragFraction ?? 0) / orchestraActivationProgress, 1))
                : 0
            let nubStretch = orchestraGap
                * orchestraApproach * orchestraApproach
            let nubX = standardNubX + nubStretch / 2
            let orchestraFillProgress: CGFloat = activeTier != nil
                ? 1 : orchestraApproach * orchestraApproach
            let fillEndX = standardNubX + orchestraGap * orchestraFillProgress
            let fillWidth = fillEndX + trackHeight / 2 - railLeading
            let deepGlowProgress = max(0, (standardFraction - 0.5) * 2)
            // Lift Deep a little without increasing Max's peak: the small ease-out gives
            // the middle of this ramp more presence while still converging to 1 at Max.
            let deepGlowStrength = deepGlowProgress * (1.25 - 0.25 * deepGlowProgress)
            let animationEligible = settled && animationsSettled && isInteractive
            let reversingFromOrchestra = dragStartedFromOrchestra && dragLane == .orchestra
            let nubVisible = (dragLane != nil || hoveringTrack)
                && (activeTier == nil || reversingFromOrchestra)

            ZStack(alignment: .topLeading) {
                // A faint capsule underneath hints that the control continues past Max.
                // The ordinary rail is layered above it and keeps its own rounded Max cap,
                // so Orchestra reads as an extension rather than a sixth point on the rail.
                if orchestraAvailable {
                    Capsule()
                        .fill(Color.secondary.opacity(0.15))
                        .frame(width: orchestraGuideWidth, height: trackHeight)
                        .position(
                            x: railLeading + orchestraGuideWidth / 2,
                            y: trackY)
                }

                Capsule()
                    .fill(Color.secondary.opacity(0.18))
                    .frame(width: mainRailWidth, height: trackHeight)
                    .position(x: railLeading + mainRailWidth / 2, y: trackY)

                ForEach(IntelligenceLevel.allCases) { stop in
                    let dotSize: CGFloat = stop == .max && orchestraAvailable ? 4 : 2
                    Circle()
                        .fill(Color.secondary.opacity(
                            stop.fraction <= standardFraction ? 0 : 0.4))
                        .frame(width: dotSize, height: dotSize)
                        .position(
                            x: inset + (maxX - inset) * stop.fraction,
                            y: trackY)
                        .animation(
                            dragLane == nil && settled
                                ? PanelMotion.indicator(reduceMotion) : nil,
                            value: standardFraction)
                }

                IntelligenceEnergyFill(
                    fraction: standardFraction, accent: palette.accent,
                    animating: animationEligible && !reduceMotion,
                    pauseAfterCurrentSweep: !energyAnimating,
                    restartToken: energyRestartGeneration)
                    .frame(width: fillWidth, height: trackHeight)
                    .clipShape(Capsule())
                    .position(x: railLeading + fillWidth / 2, y: trackY)
                    .shadow(
                        color: AppTheme.orchestra.opacity(
                            0.10 + 0.35 * standardFraction * standardFraction),
                        radius: 6 * standardFraction)
                    .shadow(
                        color: AppTheme.orchestra.opacity(
                            deepGlowStrength * 0.35),
                        radius: 14 * standardFraction)
                    .animation(
                        dragLane == nil && settled
                            ? PanelMotion.indicator(reduceMotion) : nil,
                        value: fillWidth)
                    .allowsHitTesting(false)

                Capsule()
                    .fill(nubColor(fraction: standardFraction))
                    .overlay(Capsule().strokeBorder(.white.opacity(0.85), lineWidth: 1))
                    .shadow(color: .black.opacity(0.25), radius: 1.5, y: 0.5)
                    .frame(width: nubSize + nubStretch, height: nubSize)
                    // Between Max and Orchestra the left cap remains rooted at Max while the
                    // right cap stretches toward the separate node across the empty half-step.
                    .position(x: nubX, y: trackY)
                    .opacity(nubVisible ? 1 : 0)
                    .animation(
                        dragLane == nil && settled
                            ? PanelMotion.indicator(reduceMotion) : nil,
                        value: nubX)
                    .animation(
                        dragLane == nil && settled
                            ? PanelMotion.indicator(reduceMotion) : nil,
                        value: nubStretch)
                    .animation(PanelMotion.hover(reduceMotion), value: nubVisible)
                    .animation(PanelMotion.hover(reduceMotion), value: hoveringTrack)
                    .animation(PanelMotion.hover(reduceMotion), value: orchestraTier)
                    .animation(
                        dragLane == nil && settled
                            ? PanelMotion.indicator(reduceMotion) : nil,
                        value: level)
                    .animation(
                        dragLane == nil && settled
                            ? PanelMotion.indicator(reduceMotion) : nil,
                        value: orchestraTier)

                if orchestraAvailable {
                    ForEach(IntelligenceOrchestraTier.allCases, id: \.id) { tier in
                        let selected = activeTier == tier
                        let orbitRadius: CGFloat = selected ? 11 : 7
                        let satelliteSize: CGFloat = selected ? 3.5 : 2.25

                        ZStack {
                            IntelligenceOrchestraArc()
                                .stroke(
                                    selected
                                        ? AppTheme.orchestra.opacity(0.34)
                                        : Color.secondary.opacity(0.10),
                                    style: StrokeStyle(
                                        lineWidth: selected ? 1 : 0.75,
                                        lineCap: .round))
                                .frame(
                                    width: orbitRadius * 2,
                                    height: orbitRadius * 2)

                            ForEach(
                                IntelligenceOrchestraDirection.allCases,
                                id: \.rawValue
                            ) { direction in
                                Circle()
                                    .fill(selected
                                        ? AnyShapeStyle(AppTheme.orchestra)
                                        : AnyShapeStyle(Color.secondary.opacity(0.30)))
                                    .frame(
                                        width: satelliteSize,
                                        height: satelliteSize)
                                    .offset(CGSize(
                                        width: direction.unitOffset.width * orbitRadius,
                                        height: direction.unitOffset.height * orbitRadius))
                            }

                            // Unlike the satellites, this center dot is a persistent detent:
                            // it makes Orchestra legible as a destination before selection.
                            Circle()
                                .fill(selected
                                    ? AnyShapeStyle(AppTheme.orchestra)
                                    : AnyShapeStyle(Color.secondary.opacity(0.52)))
                                .overlay(Circle().strokeBorder(
                                    .white.opacity(selected ? 0.75 : 0.18),
                                    lineWidth: 0.5))
                                .frame(
                                    width: selected ? 5.5 : 2,
                                    height: selected ? 5.5 : 2)
                        }
                        .frame(width: 28, height: 28)
                        .position(x: orchestraX, y: trackY)
                        .shadow(
                            color: selected
                                ? AppTheme.orchestra.opacity(0.34)
                                : .clear,
                            radius: selected ? 7 : 0)
                        .animation(
                            PanelMotion.indicator(reduceMotion),
                            value: selected)
                        .allowsHitTesting(false)
                    }
                }
            }
            .contentShape(Rectangle())
            .gesture(
                dragGesture(width: width, height: geo.size.height),
                isEnabled: isInteractive)
            .onContinuousHover { phase in
                switch phase {
                case .active:
                    hoveringTrack = true
                case .ended:
                    hoveringTrack = false
                }
            }
        }
    }

    private func maxNodeX(width: CGFloat) -> CGFloat {
        guard orchestraAvailable else { return width - inset }
        let orchestraX = orchestraNodeX(width: width)
        // With four ordinary intervals, putting Orchestra half an interval beyond Max means
        // Max occupies 8/9 of the usable Quick → Orchestra span.
        return inset + (orchestraX - inset) * 8 / 9
    }

    private func orchestraNodeX(width: CGFloat) -> CGFloat {
        width - inset - orchestraTrailingInset
    }

    /// Click-to-jump and drag in one gesture: minimumDistance 0 means a plain click lands
    /// on `onChanged` once and snaps on release. Local coordinates are safe here (unlike
    /// `ResizeHandle`) because the control itself never moves while dragging.
    private func dragGesture(width: CGFloat, height: CGFloat) -> some Gesture {
        DragGesture(minimumDistance: 0)
            .onChanged { value in
                let distanceSquared = value.translation.width * value.translation.width
                    + value.translation.height * value.translation.height
                guard dragHasMoved || distanceSquared >= clickSlop * clickSlop else {
                    return
                }
                if !dragHasMoved {
                    dragHasMoved = true
                    dragStartedFromOrchestra = orchestraTier != nil
                    energyRestartGeneration += 1
                }
                let previousName = displayedName
                let previousOrchestra = displayedOrchestraTier != nil
                let selection = dragSelection(
                    at: value.location,
                    width: width,
                    height: height,
                    isClick: false,
                    reversesOrchestraStretch: dragStartedFromOrchestra)
                dragLane = selection.lane
                dragFraction = selection.fraction
                updateOrchestraRumble(for: selection)

                let nextName: String
                let nextOrchestra: Bool
                if selection.lane == .orchestra {
                    nextOrchestra = selection.fraction >= orchestraActivationProgress
                    nextName = nextOrchestra
                        ? IntelligenceOrchestraTier.orchestra.displayName
                        : IntelligenceLevel.max.displayName
                } else {
                    nextOrchestra = false
                    nextName = IntelligenceSliderMath.nearestLevel(
                        fraction: selection.fraction).displayName
                }
                if nextOrchestra != previousOrchestra {
                    onOrchestraPreviewChanged?(nextOrchestra)
                }
                if nextName != previousName { performLevelHaptic() }
            }
            .onEnded { value in
                let clicked = !dragHasMoved
                let selection = dragSelection(
                    at: value.location,
                    width: width,
                    height: height,
                    isClick: clicked,
                    reversesOrchestraStretch: !clicked && dragStartedFromOrchestra)
                if clicked {
                    let nextName = selection.lane == .orchestra
                        && selection.fraction >= orchestraActivationProgress
                        ? IntelligenceOrchestraTier.orchestra.displayName
                        : (selection.lane == .orchestra
                            ? IntelligenceLevel.max.displayName
                            : IntelligenceSliderMath.nearestLevel(
                                fraction: selection.fraction).displayName)
                    if nextName != displayedName { performLevelHaptic() }
                }
                if selection.lane == .orchestra,
                   selection.fraction >= orchestraActivationProgress
                {
                    let tier = IntelligenceOrchestraTier.orchestra
                    orchestraTier = tier
                } else {
                    let landed = selection.lane == .orchestra
                        ? IntelligenceLevel.max
                        : IntelligenceSliderMath.nearestLevel(fraction: selection.fraction)
                    orchestraTier = nil
                    level = landed
                }
                dragFraction = nil
                dragLane = nil
                dragHasMoved = false
                dragStartedFromOrchestra = false
                orchestraRumbleAnchor = nil
                onOrchestraPreviewChanged?(nil)
                energyRestartGeneration += 1
            }
    }

    private func dragSelection(
        at point: CGPoint,
        width: CGFloat,
        height: CGFloat,
        isClick: Bool,
        reversesOrchestraStretch: Bool
    ) -> (
        lane: IntelligenceSliderLane,
        fraction: Double
    ) {
        let maxX = maxNodeX(width: width)

        if orchestraAvailable,
           let orchestra = orchestraDragSelection(
               at: point,
               maxX: maxX,
               orchestraX: orchestraNodeX(width: width),
               trackY: height / 2,
               isClick: isClick,
               reversesStretch: reversesOrchestraStretch)
        {
            return (.orchestra, orchestra)
        }

        let raw = IntelligenceSliderMath.fraction(
            atX: point.x, trackWidth: maxX + inset, inset: inset)
        return (.standard, IntelligenceSliderMath.magnetized(fraction: raw))
    }

    private func orchestraDragSelection(
        at point: CGPoint,
        maxX: CGFloat,
        orchestraX: CGFloat,
        trackY: CGFloat,
        isClick: Bool,
        reversesStretch: Bool
    ) -> Double? {
        let dx = point.x - maxX
        let dy = point.y - trackY
        let gap = orchestraX - maxX
        let dotDX = point.x - orchestraX
        if isClick, dotDX * dotDX + dy * dy <= 64 {
            return 1
        }
        guard gap > 0,
              dx > 0,
              abs(dy) <= max(controlHeight / 2, gap)
        else { return nil }

        let raw = min(max(dx / gap, 0), 1)
        if reversesStretch {
            // Mirror the spring when leaving Orchestra: it starts fully extended at the dot
            // and gives way toward Max, rather than reusing the forward lag in reverse.
            return 1 - pow(1 - Double(raw), orchestraResistanceExponent)
        }
        // Forward ease-in deliberately yields less than the pointer, making the short
        // half-step feel elastic rather than like an ordinary sixth detent.
        return pow(Double(raw), orchestraResistanceExponent)
    }

    private func step(_ delta: Int) {
        if orchestraTier != nil {
            if delta < 0 { setLevel(.max) }
            return
        }
        guard let next = IntelligenceLevel(rawValue: level.rawValue + delta) else {
            if delta > 0, orchestraAvailable, level == .max {
                setOrchestraTier(.orchestra)
            }
            return
        }
        setLevel(next)
    }

    private func setLevel(_ next: IntelligenceLevel) {
        guard next != level || orchestraTier != nil else { return }
        orchestraTier = nil
        level = next
        performLevelHaptic()
    }

    private func setOrchestraTier(_ next: IntelligenceOrchestraTier) {
        guard orchestraTier != next else { return }
        orchestraTier = next
        performLevelHaptic()
    }

    /// A light pulse at even intervals along the resisted Max → Orchestra stretch. The
    /// stronger level haptic still owns the actual Orchestra detent at the end.
    private func updateOrchestraRumble(
        for selection: (
            lane: IntelligenceSliderLane,
            fraction: Double
        )
    ) {
        guard selection.lane == .orchestra,
              selection.fraction > 0,
              selection.fraction < orchestraActivationProgress
        else {
            orchestraRumbleAnchor = nil
            return
        }

        let progress = selection.fraction / orchestraActivationProgress
        if let anchor = orchestraRumbleAnchor,
           abs(progress - anchor) < orchestraRumbleSpacing
        {
            return
        }
        orchestraRumbleAnchor = progress
        performStretchHaptic()
    }

    /// Use AppKit's firmer generic pulse so the compact control has a more legible detent.
    /// Unsupported hardware remains silent and the user's system haptics preference is respected.
    private func performLevelHaptic() {
        NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .now)
    }

    private func performStretchHaptic() {
        NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .now)
    }

    /// Accent → orchestra gold crest for the nub (a stacked-opacity blend, so it holds up
    /// across the dynamic light/dark palette colors without color-space math).
    private func nubColor(fraction: Double) -> AnyShapeStyle {
        AnyShapeStyle(
            palette.accent.blendedTowardOrchestra(fraction * fraction))
    }
}

/// The animated fill: a base accent→gold gradient whose gold share grows with the level,
/// plus a traveling specular band whose period shortens from a lazy 3 s at Quick to a brisk
/// 0.9 s at Max (StreakBadge's accelerate-with-intensity ramp). Reduce Motion, initial layout,
/// and disabled controls remove the band immediately. A composer typing pause is gentler: the
/// current band first travels fully beyond the trailing edge, then the base color remains.
private struct IntelligenceEnergyFill: View {
    var fraction: Double
    var accent: Color
    var animating: Bool
    var pauseAfterCurrentSweep: Bool
    var restartToken: Int
    /// A phase/date anchor lets the animation change speed without changing position.
    /// Computing `time % period` directly makes every level use a different clock; moving the
    /// slider then jumps to an unrelated point in the shimmer cycle.
    @State private var phaseAnchor = 0.0
    @State private var phaseAnchorDate = Date()
    /// Unlike the global animation gates, typing never cuts a visible sweep in half. This flips
    /// only after the band has cleared the trailing edge.
    @State private var pausedForComposer: Bool

    init(
        fraction: Double,
        accent: Color,
        animating: Bool,
        pauseAfterCurrentSweep: Bool,
        restartToken: Int
    ) {
        self.fraction = fraction
        self.accent = accent
        self.animating = animating
        self.pauseAfterCurrentSweep = pauseAfterCurrentSweep
        self.restartToken = restartToken
        _pausedForComposer = State(initialValue: pauseAfterCurrentSweep)
    }

    private var sweepAnimating: Bool {
        animating && !pausedForComposer
    }

    private var pauseTaskID: IntelligenceShimmerPauseTaskID {
        IntelligenceShimmerPauseTaskID(
            requested: pauseAfterCurrentSweep,
            eligible: animating,
            fraction: fraction,
            restartToken: restartToken)
    }

    var body: some View {
        TimelineView(.animation(minimumInterval: 1 / 30, paused: !sweepAnimating)) { context in
            Canvas { graphics, size in
                let gold = AppTheme.orchestra
                // Base fill: accent shading into gold; the gold share scales with level so
                // Quick reads as the project accent and Max unmistakably as orchestra gold.
                let base = Gradient(stops: [
                    .init(color: accent.opacity(0.55 + 0.25 * fraction), location: 0),
                    .init(color: gold.opacity(0.25 + 0.75 * fraction), location: 1),
                ])
                let rect = CGRect(origin: .zero, size: size)
                graphics.fill(
                    Path(rect),
                    with: .linearGradient(
                        base, startPoint: .zero, endPoint: CGPoint(x: size.width, y: 0)))

                // Preserve the accent as the low-intelligence base, then gently borrow the
                // destination gold as the level rises. Concentrating this wash at the leading
                // edge removes the green-to-yellow clash at Max without flattening the gradient.
                let leadingWarmth = 0.18 * pow(fraction, 2.2)
                if leadingWarmth > 0 {
                    let warmth = Gradient(stops: [
                        .init(color: gold.opacity(leadingWarmth), location: 0),
                        .init(color: gold.opacity(leadingWarmth * 0.45), location: 0.42),
                        .init(color: gold.opacity(0), location: 0.78),
                    ])
                    graphics.fill(
                        Path(rect),
                        with: .linearGradient(
                            warmth, startPoint: .zero,
                            endPoint: CGPoint(x: size.width, y: 0)))
                }

                if sweepAnimating {
                    // Sweep from fully off the left edge to fully off the right, so the band
                    // enters and exits cleanly instead of popping.
                    let phase = phase(at: context.date, fraction: fraction)
                    let bandWidth = max(14, size.width * 0.25)
                    let bandCenter = (size.width + bandWidth * 2) * phase - bandWidth
                    let band = Gradient(stops: [
                        .init(color: .clear, location: 0),
                        .init(color: .white.opacity(0.18 + 0.30 * fraction), location: 0.5),
                        .init(color: .clear, location: 1),
                    ])
                    graphics.fill(
                        Path(rect),
                        with: .linearGradient(
                            band,
                            startPoint: CGPoint(x: bandCenter - bandWidth, y: 0),
                            endPoint: CGPoint(x: bandCenter + bandWidth, y: 0)))
                }
            }
        }
        .onChange(of: fraction) { oldFraction, _ in
            guard sweepAnimating else { return }
            // Re-anchor at the current visual position using the old rate. The next frame
            // continues from exactly here using the new rate instead of phase-jumping.
            let now = Date()
            phaseAnchor = phase(at: now, fraction: oldFraction)
            phaseAnchorDate = now
        }
        .onChange(of: animating) { _, isEligible in
            guard isEligible else { return }
            // Re-enter from the leading edge after a lifecycle or accessibility gate reopens.
            phaseAnchor = 0
            phaseAnchorDate = Date()
        }
        .onChange(of: restartToken) { _, _ in
            // Release starts a clean sweep run; a new drag also cancels and restarts it.
            // The mounting slider decides whether that run is finite or continuous.
            phaseAnchor = 0
            phaseAnchorDate = Date()
        }
        .task(id: pauseTaskID) {
            guard pauseAfterCurrentSweep else {
                pausedForComposer = false
                phaseAnchor = 0
                phaseAnchorDate = Date()
                return
            }
            guard animating, !pausedForComposer else { return }

            // Keep the current visual phase and wait only for the remainder of this sweep.
            // Including fraction and restartToken in the task ID keeps the deadline aligned
            // when a concurrent slider gesture changes the speed or deliberately restarts it.
            let now = Date()
            let currentPhase = phase(at: now, fraction: fraction)
            let remaining = max(
                (1 - currentPhase) * intelligenceShimmerPeriod(fraction: fraction),
                1 / 30)
            do {
                try await Task.sleep(for: .seconds(remaining))
            } catch {
                return
            }
            guard !Task.isCancelled, pauseAfterCurrentSweep, animating else { return }

            // At phase 1 the gradient band is wholly past the trailing edge, so hiding it here
            // is visually identical to the natural end of the sweep.
            phaseAnchor = 0
            phaseAnchorDate = Date()
            pausedForComposer = true
        }
    }

    private func phase(at date: Date, fraction: Double) -> Double {
        let period = intelligenceShimmerPeriod(fraction: fraction)
        let elapsed = max(0, date.timeIntervalSince(phaseAnchorDate))
        return (phaseAnchor + elapsed / period).truncatingRemainder(dividingBy: 1)
    }
}

private struct IntelligenceShimmerPauseTaskID: Hashable {
    var requested: Bool
    var eligible: Bool
    var fraction: Double
    var restartToken: Int
}

extension Color {
    /// Blend this color toward the orchestra gold by `amount` (0…1) — done with an overlay
    /// blend rather than component math so the dynamic (appearance-resolving) colors keep
    /// resolving per appearance.
    fileprivate func blendedTowardOrchestra(_ amount: Double) -> some ShapeStyle {
        let clamped = min(max(amount, 0), 1)
        return LinearGradient(
            colors: [
                self.opacity(1 - clamped * 0.85),
                AppTheme.orchestra.opacity(0.15 + clamped * 0.85),
            ],
            startPoint: .top, endPoint: .bottom)
    }
}
