import Foundation

/// Pure state transition behind the transcript's bottom-follow gate.
///
/// Scroll geometry changes for two very different reasons: the user moves the viewport, or
/// streaming/layout changes move the content around it. Only the first may disengage following.
/// Keeping this decision free of SwiftUI state makes the anti-jitter contract directly testable.
enum TranscriptScrollFollowPolicy {
    /// Close enough to resume (or confirm) bottom following.
    static let followThreshold: CGFloat = 24
    /// Far enough that an active user scroll deliberately leaves the live tail.
    static let unfollowThreshold: CGFloat = 64

    /// Coarsens continuously-changing scroll geometry into the only three regions that can
    /// affect follow state. `onScrollGeometryChange` calls its action only when this Equatable
    /// value changes, so a thousand-point swipe costs two threshold callbacks rather than a
    /// main-actor callback for every rounded pixel.
    enum Region: Equatable {
        case atBottom
        case hysteresis
        case away
    }

    static func region(distanceToBottom: CGFloat) -> Region {
        if distanceToBottom <= followThreshold { return .atBottom }
        if distanceToBottom > unfollowThreshold { return .away }
        return .hysteresis
    }

    static func following(
        current: Bool,
        distanceToBottom: CGFloat,
        isSettling: Bool,
        isUserScrolling: Bool,
        isJumpingToBottom: Bool
    ) -> Bool {
        following(
            current: current,
            region: region(distanceToBottom: distanceToBottom),
            isSettling: isSettling,
            isUserScrolling: isUserScrolling,
            isJumpingToBottom: isJumpingToBottom)
    }

    static func following(
        current: Bool,
        region: Region,
        isSettling: Bool,
        isUserScrolling: Bool,
        isJumpingToBottom: Bool
    ) -> Bool {
        if region == .atBottom { return true }
        if !isSettling,
           region == .away,
           isUserScrolling,
           !isJumpingToBottom {
            return false
        }
        return current
    }
}
