import APIClient
import ComponentLibrary
import ComposableArchitecture
import CoreMedia
import Localization
import PlayerClient
import SwiftUI
import Utilities

// swiftlint:disable file_length

@Reducer
public struct WaveformEditor {
    // Distinguish between different fixed handle positions and custom positions
    public enum HandlePosition: Equatable {
        case leftHandleAtLeftEdge
        case leftHandleAtRightEdge
        case leftHandleAtLeftBufferEdge
        case leftHandleAtRightBufferEdge
        case rightHandleAtLeftEdge
        case rightHandleAtRightEdge
        case rightHandleAtLeftBufferEdge
        case rightHandleAtRightBufferEdge
        case custom(Double)

        // Returns a clipped position if a buffer is present, otherwise returns the raw position
        func positionValue(_ totalWidth: CGFloat, _ bufferWidth: CGFloat) -> Double {
            switch self {
            case .leftHandleAtLeftEdge:
                return 0.0
            case .leftHandleAtRightEdge:
                return 1.0
            case .leftHandleAtLeftBufferEdge:
                return bufferWidth / totalWidth
            case .leftHandleAtRightBufferEdge:
                return 1.0 - bufferWidth / totalWidth
            case .rightHandleAtLeftEdge:
                return 0.0
            case .rightHandleAtRightEdge:
                return 1.0
            case .rightHandleAtLeftBufferEdge:
                return bufferWidth / totalWidth
            case .rightHandleAtRightBufferEdge:
                return 1.0 - bufferWidth / totalWidth
            case .custom(let value):
                return value
            }
        }
    }

    public enum Handle {
        case leftHandle
        case rightHandle
    }

    public enum Mode {
        case leftHandleFixed
        case rightHandleFixed
        case bothHandlesFixed
        case bothHandlesFree

        var leftHandleStartPosition: HandlePosition {
            switch self {
            case .leftHandleFixed:
                return .leftHandleAtLeftEdge
            case .rightHandleFixed:
                return .leftHandleAtRightBufferEdge
            case .bothHandlesFixed:
                return .leftHandleAtLeftEdge
            case .bothHandlesFree:
                return .leftHandleAtLeftEdge
            }
        }

        var rightHandleEndPosition: HandlePosition {
            switch self {
            case .leftHandleFixed:
                return .rightHandleAtLeftBufferEdge
            case .rightHandleFixed:
                return .rightHandleAtRightEdge
            case .bothHandlesFixed:
                return .rightHandleAtRightEdge
            case .bothHandlesFree:
                return .rightHandleAtRightEdge
            }
        }

        func leftHandleIsAtStartPosition(_ position: HandlePosition) -> Bool {
            return position == leftHandleStartPosition
        }

        func rightHandleIsAtEndPosition(_ position: HandlePosition) -> Bool {
            return position == rightHandleEndPosition
        }
    }

    // PlaybackState helps distinguish between playing/paused "active" states
    // where we need to zoom-in and offset the waveform to have the reference
    // start/end points be the center marker, and idle states where we see
    // the full waveform.
    public enum PlaybackState {
        case playing
        case paused
        case idle

        var isActive: Bool {
            self != .idle
        }
    }

    // The user can seek by fine-scrubbing and moving the
    // handle to either edge of the waveform strip when playback isn't active.
    // When playback is active, the user can seek by holding the seek buttons
    // or dragging the waveform.
    public enum SeekingState: Equatable {
        case notActive
        case manualWhilePlaying
        case manualWhilePaused
        case isFineScrubbingLeft
        case isFineScrubbingRight
        case isHoldingLeftSeekButton
        case isHoldingRightSeekButton

        var isActive: Bool {
            self != .notActive
        }

        var isLeft: Bool {
            self == .isHoldingLeftSeekButton || self == .isFineScrubbingLeft
        }

        var isRight: Bool {
            self == .isHoldingRightSeekButton || self == .isFineScrubbingRight
        }

        var isFineScrubbing: Bool {
            self == .isFineScrubbingLeft || self == .isFineScrubbingRight
        }

        var isHoldingSeekButton: Bool {
            self == .isHoldingLeftSeekButton || self == .isHoldingRightSeekButton
        }

        var isAutomatic: Bool {
            self == .isFineScrubbingLeft ||
                self == .isFineScrubbingRight ||
                self == .isHoldingLeftSeekButton ||
                self == .isHoldingRightSeekButton
        }

        var isManual: Bool {
            self == .manualWhilePlaying || self == .manualWhilePaused
        }
    }

    @ObservableState
    public struct State: Equatable {
        public let waveformEditorWidth: CGFloat = UIScreen.main.bounds.width - 20
        public let waveformStripHeight: CGFloat = 100

        // The buffer is the idle space either to the left or right of the waveform strip
        // when we're in a .rightHandleFixed or .leftHandleFixed mode, and are not currently
        // fine-scrubbing or scrolling. This helps the user see the starting point where they
        // can interact with the clip.
        public let bufferWidth: CGFloat = 32

        // Represents 10% of total duration
        // This is the size of time window displayed in the waveform strip
        // when fine-scrubbing or fine-scrubbing-to-scrolling
        public let windowSizeRatioWhenFineScrubbing: CGFloat = 0.1

        // Represents 50% of total duration
        // This is the size of time window displayed in the waveform strip
        // when playback is active (playing or paused)
        public let windowSizeRatioWhenPlaybackActive: CGFloat = 0.5

        public var mode: Mode = .rightHandleFixed
        public var isPlaying: Bool { playbackState == .playing }
        public var isFineScrubbing: Bool = false
        public var seekingState: SeekingState = .notActive
        public var playbackState: PlaybackState = .idle

        @ObservationStateIgnored @ObservedBox public var player: WaveformEditorPlayer.State

        public var clipDuration: Double = 0
        public var leftHandlePosition: HandlePosition
        public var rightHandlePosition: HandlePosition
        public var userIsInteractingWithLeftHandle: Bool = false
        public var userIsInteractingWithRightHandle: Bool = false
        public var startPointCaptionOverride: String?
        public var endPointCaptionOverride: String?
        public var fineScrubbingAnchorTime: Double?
        public var fineScrubbingStartPosition: Double?
        public var waveformData: [Float] = []

        // This is how we tell WaveformEditorControls that a parent view
        // dismissed the keyboard. If `shouldSaveTimestampsAndDismissKeyboard`,
        // we also save the current timestamp before dismissing the keyboard.
        public var shouldDismissKeyboard: Bool = false
        public var shouldSaveTimestampsAndDismissKeyboard: Bool = false

        public var leftHandleIsAtStartPosition: Bool {
            if mode == .rightHandleFixed {
                return leftHandlePosition == .leftHandleAtRightBufferEdge
            } else if mode == .bothHandlesFree {
                return leftHandlePosition == .leftHandleAtLeftEdge
            } else {
                return true
            }
        }

        public var rightHandleIsAtEndPosition: Bool {
            if mode == .leftHandleFixed {
                return rightHandlePosition == .rightHandleAtLeftBufferEdge
            } else if mode == .bothHandlesFree {
                return rightHandlePosition == .rightHandleAtRightEdge
            } else {
                return true
            }
        }

        // Only show reset controls if the handle is free-to-move and not at its start position
        // and we're not fine scrubbing, playing, or interacting with the handle
        public var showLeftHandleResetPositionControl: Bool {
            return !userIsInteractingWithLeftHandle &&
                !isFineScrubbing &&
                !leftHandleIsAtStartPosition &&
                playbackState == .idle
        }

        public var showRightHandleResetPositionControl: Bool {
            return !userIsInteractingWithRightHandle &&
                !isFineScrubbing &&
                !rightHandleIsAtEndPosition &&
                playbackState == .idle
        }

        // Don't show a handle if we can't move it or if we're fine scrubbing the other handle
        public var canMoveLeftHandle: Bool {
            return mode != .leftHandleFixed &&
                (seekingState.isFineScrubbing || !seekingState.isActive)
        }

        public var canMoveRightHandle: Bool {
            return mode != .rightHandleFixed &&
                (seekingState.isFineScrubbing || !seekingState.isActive)
        }

        public var isLeftHandleHidden: Bool {
            return !canMoveLeftHandle || (playbackState.isActive && seekingState.isManual && (leftHandlePosition == .custom(0.0) || leftHandlePosition == .custom(1.0)))
        }

        public var isRightHandleHidden: Bool {
            return !canMoveRightHandle || (playbackState.isActive && seekingState.isManual && (rightHandlePosition == .custom(0.0) || rightHandlePosition == .custom(1.0)))
        }

        public var waveformStripWidth: CGFloat {
            guard playbackState == .idle else {
                return waveformEditorWidth
            }

            if mode == .leftHandleFixed && !isFineScrubbing {
                return waveformEditorWidth - bufferWidth
            } else if mode == .rightHandleFixed && !isFineScrubbing {
                return waveformEditorWidth - bufferWidth
            } else {
                return waveformEditorWidth
            }
        }

        public var waveformStripOffset: CGFloat {
            guard playbackState == .idle else {
                return 0
            }

            if mode == .leftHandleFixed && !isFineScrubbing {
                return bufferWidth / 2
            } else if mode == .rightHandleFixed && !isFineScrubbing {
                return -bufferWidth / 2
            } else {
                return 0
            }
        }

        // Shows the current playing time, or,
        // the timestamp of the handle that is being
        // interacted with.
        public var markerTime: String {
            if userIsInteractingWithLeftHandle {
                return leftHandleTimeFormatted
            } else if userIsInteractingWithRightHandle {
                return rightHandleTimeFormatted
            } else if playbackState.isActive {
                /*
                 This checks that we're in a valid playing position.

                 As for elapsedTime > 0,
                 Sometimes when we seek and play right away,
                 the elapsedTime flashes to 0 for a little,
                 so we filter those positions out, just like we do
                 in waveformStripAnchorTime.
                 */
                guard player.timeControlStatus == .playing,
                      player.timeControlStatus == .playing,
                      player.elapsedTime.seconds > 0
                else {
                    return lastKnownPlayerTime.detailedPositionalTime
                }
                return player.elapsedTime.seconds.detailedPositionalTime
            } else {
                return ""
            }
        }

        public var userIsInteracting: Bool {
            return userIsInteractingWithLeftHandle || userIsInteractingWithRightHandle
        }

        public var leftHandleTimeFormatted: String {
            lastKnownLeftHandleTime.detailedPositionalTime
        }

        public var rightHandleTimeFormatted: String {
            rightHandleTime.detailedPositionalTime
        }

        public var fineScrubbingWindowSizeSeconds: Double {
            return clipDuration * windowSizeRatioWhenFineScrubbing
        }

        public var playbackActiveWindowSizeSeconds: Double {
            return clipDuration * windowSizeRatioWhenPlaybackActive
        }

        // These are used to keep track of the last known handle time
        // before the user plays the clip for the first time and shifts
        // the handle to a new position in a scaled window.
        public var lastKnownLeftHandleTimeBeforePlaying: Double = 0.0
        public var lastKnownRightHandleTimeBeforePlaying: Double = 0.0

        // If these are true, we stop showing `lastKnownLeftHandleTimeBeforePlaying`
        // and `lastKnownRightHandleTimeBeforePlaying` and instead show the
        // current handle time.
        public var didMoveLeftHandleWhilePlaybackPaused: Bool = false
        public var didMoveRightHandleWhilePlaybackPaused: Bool = false
        public var didMoveHandlesWhilePlaybackPaused: Bool {
            return didMoveLeftHandleWhilePlaybackPaused || didMoveRightHandleWhilePlaybackPaused
        }

        public var isInActivePlaybackWithoutMovingHandles: Bool {
            return playbackState.isActive && !didMoveHandlesWhilePlaybackPaused
        }

        public var lastKnownLeftHandleTime: Double {
            guard playbackState.isActive, !didMoveLeftHandleWhilePlaybackPaused else {
                return leftHandleTime
            }
            return lastKnownLeftHandleTimeBeforePlaying
        }

        public var lastKnownRightHandleTime: Double {
            guard playbackState.isActive, !didMoveRightHandleWhilePlaybackPaused else {
                return rightHandleTime
            }
            return lastKnownRightHandleTimeBeforePlaying
        }

        public var fineScrubbingWaveformStripStartTimeFormatted: String {
            guard isFineScrubbing,
                  let fineScrubbingAnchorTime,
                  let fineScrubbingStartPosition
            else { return "" }

            let offset = (fineScrubbingStartPosition - 0.5) * fineScrubbingWindowSizeSeconds
            let windowStart = fineScrubbingAnchorTime - fineScrubbingWindowSizeSeconds / 2 - offset
            let time = max(0, windowStart)
            return time.detailedPositionalTime
        }

        public var fineScrubbingWaveformStripEndTimeFormatted: String {
            guard isFineScrubbing,
                  let fineScrubbingAnchorTime,
                  let fineScrubbingStartPosition
            else { return "" }

            let offset = (fineScrubbingStartPosition - 0.5) * fineScrubbingWindowSizeSeconds
            let windowEnd = fineScrubbingAnchorTime + fineScrubbingWindowSizeSeconds / 2 - offset
            let time = min(clipDuration, windowEnd)
            return time.detailedPositionalTime
        }

        public var fineScrubbingAnchorTimeLeftEdgeMinimum: Double {
            return fineScrubbingWindowSizeSeconds / 2
        }

        public var fineScrubbingAnchorTimeRightEdgeMaximum: Double {
            return clipDuration - fineScrubbingWindowSizeSeconds / 2
        }

        // Fine scrubbing is disabled when playback is playing or paused
        public var fineScrubbingDisabled: Bool {
            return playbackState.isActive
        }

        // WaveformSelectorHandles are not showing when playing.
        // This is controlled by `isWaveformSelectorVisible`.
        // They can be seen in all the remaining states like fine-scrubbing,
        // fine-scrubbing to scrolling, paused, paused to scrolling, etc.
        public var leftHandleTime: Double {
            if leftHandlePosition == .leftHandleAtLeftEdge {
                return 0.0
            }

            let currentHandlePosition = leftHandlePosition.positionValue(waveformEditorWidth, bufferWidth)
            var time = 0.0
            if playbackState.isActive {
                // When playback is active, we add width/2 horizontal padding to the waveform so it looks like it
                // starts playing from the center mark. Since positions are normalized, we can just subtract 0.5.
                let anchorTime = waveformStripAnchorTime
                let delta = currentHandlePosition - 0.5
                time = anchorTime + (delta * playbackActiveWindowSizeSeconds)
                return max(0, min(clipDuration, time))
            } else if isFineScrubbing, let anchorTime = fineScrubbingAnchorTime, let anchorPosition = fineScrubbingStartPosition {
                // When fine-scrubbing, we need to calculate the handle's position change to determine
                // how much the handle has moved from where we started fine-scrubbing.
                // This delta first gets scaled to the smaller, zoomed-in fine-scrubbing window.
                let delta = currentHandlePosition - anchorPosition
                time = anchorTime + (delta * fineScrubbingWindowSizeSeconds)
            } else {
                if mode == .rightHandleFixed || mode == .leftHandleFixed {
                    let maxPosition = 1.0 - bufferWidth / waveformEditorWidth
                    let scaledDuration = clipDuration / maxPosition
                    let delta = currentHandlePosition - 0.0 // Just to show that the math is consistent
                    // When playback is idle, and the user is not fine scrubbing, we can just scale then
                    // offset the position to account for the buffer width to get the time corresponding
                    // to the handle position.
                    time = delta * scaledDuration
                } else {
                    // If both handles are free, and we're idle, no scaling is necessary
                    time = Double(currentHandlePosition) * clipDuration
                }
            }
            return time
        }

        public var rightHandleTime: Double {
            if rightHandlePosition == .rightHandleAtRightEdge || rightHandlePosition == .rightHandleAtRightBufferEdge {
                return clipDuration
            }

            let currentHandlePosition = rightHandlePosition.positionValue(waveformEditorWidth, bufferWidth)
            var time = 0.0
            if playbackState.isActive, didMoveRightHandleWhilePlaybackPaused {
                let anchorTime = waveformStripAnchorTime
                let delta = currentHandlePosition - 0.5
                time = anchorTime + (delta * playbackActiveWindowSizeSeconds)
                return max(0, min(clipDuration, time))
            } else if isFineScrubbing, let anchorTime = fineScrubbingAnchorTime, let anchorPosition = fineScrubbingStartPosition {
                let delta = currentHandlePosition - anchorPosition
                time = anchorTime + (delta * fineScrubbingWindowSizeSeconds)
            } else {
                if mode == .leftHandleFixed || mode == .rightHandleFixed {
                    let maxPosition = 1.0 - bufferWidth / waveformEditorWidth
                    let scaledDuration = clipDuration / maxPosition
                    let delta = currentHandlePosition - 0.0
                    time = delta * scaledDuration
                } else {
                    time = Double(currentHandlePosition) * clipDuration
                }
            }
            return time
        }

        public var leftHandleTimeMarkerCaption: String? {
            if let startPointCaptionOverride { return startPointCaptionOverride }
            switch mode {
            case .leftHandleFixed, .bothHandlesFixed:
                return nil
            case .rightHandleFixed, .bothHandlesFree:
                return L10n.FeatureEditClip.startFrom
            }
        }

        public var rightHandleTimeMarkerCaption: String? {
            if let endPointCaptionOverride { return endPointCaptionOverride }
            switch mode {
            case .rightHandleFixed, .bothHandlesFixed:
                return nil
            case .leftHandleFixed, .bothHandlesFree:
                return L10n.FeatureEditClip.endAt
            }
        }

        public var leftHandleMinPosition: Double? {
            if playbackState.isActive, waveformStripAnchorTime < playbackActiveWindowSizeSeconds / 2 {
                let max = abs(waveformStripAnchorTime) / playbackActiveWindowSizeSeconds
                return 0.5 - max
            }
            return nil
        }

        public var rightHandleMaxPosition: Double? {
            if playbackState.isActive, (clipDuration - waveformStripAnchorTime) < playbackActiveWindowSizeSeconds / 2 {
                let max = abs(clipDuration - waveformStripAnchorTime) / playbackActiveWindowSizeSeconds
                return 0.5 + max
            }
            return nil
        }

        // Defines the maximum position the left handle can be dragged to,
        // which has to account for the buffer when not idle and not fine-scrubbing.
        public var leftHandleMaxPosition: Double? {
            // If playback is active, and the anchor is at the right edge, stop letting the user move the handle past that point
            if playbackState.isActive, (clipDuration - waveformStripAnchorTime) < playbackActiveWindowSizeSeconds / 2 {
                let max = abs(clipDuration - waveformStripAnchorTime) / playbackActiveWindowSizeSeconds
                return 0.5 + max
            }

            if mode == .rightHandleFixed {
                if isFineScrubbing {
                    return HandlePosition.leftHandleAtRightEdge.positionValue(waveformEditorWidth, bufferWidth)
                } else {
                    return HandlePosition.leftHandleAtRightBufferEdge.positionValue(waveformEditorWidth, bufferWidth)
                }
            } else {
                return nil
            }
        }

        // Defines the minimum position the right handle can be dragged to,
        // which has to account for the buffer when not idle and not fine-scrubbing.
        public var rightHandleMinPosition: Double? {
            // If playback is active, and the anchor is at the left edge, stop letting the user move the handle past that point
            if playbackState.isActive, (waveformStripAnchorTime - 0.5) < playbackActiveWindowSizeSeconds / 2 {
                let max = abs(waveformStripAnchorTime - 0.5) / playbackActiveWindowSizeSeconds
                return 0.5 - max
            }
            if mode == .leftHandleFixed {
                if isFineScrubbing {
                    return HandlePosition.rightHandleAtLeftEdge.positionValue(waveformEditorWidth, bufferWidth)
                } else {
                    return HandlePosition.rightHandleAtLeftBufferEdge.positionValue(waveformEditorWidth, bufferWidth)
                }
            } else {
                return nil
            }
        }

        // When dragging either handle close to the edge,
        // we snap to the appropriate position
        public var snapThreshold: Double {
            return isFineScrubbing ? 0.01 : 0.03
        }

        public var shouldShowCenterMarker: Bool {
            return !userIsInteractingWithLeftHandle
                && !userIsInteractingWithRightHandle
                && playbackState.isActive
        }

        // Last known player time is used to maintain a consistent timestamp
        // when pausing and resuming playback
        public var lastKnownPlayerTime: Double = 0

        // Center reference time of the waveform strip, at all zoom levels
        public var waveformStripAnchorTime: Double {
            var time = lastKnownPlayerTime
            if seekingState.isManual {
                return lastKnownPlayerTime
            } else if playbackState == .playing,
                      player.timeControlStatus == .playing,
                      player.elapsedTime.seconds > 0
            {
                /*
                  This checks that we're in a valid playing position.

                  As for elapsedTime > 0,
                  Sometimes when we seek and play right away,
                  the elapsedTime flashes to 0 for a little,
                  so we filter those positions out, just like we do
                  in markerTime.
                 */
                time = player.elapsedTime.seconds
            } else if playbackState.isActive {
                time = lastKnownPlayerTime
            }

            if isFineScrubbing,
               let fineScrubbingAnchorTime,
               let fineScrubbingStartPosition
            {
                let offset = (fineScrubbingStartPosition - 0.5) * fineScrubbingWindowSizeSeconds
                return fineScrubbingAnchorTime - offset - fineScrubbingWindowSizeSeconds / 2
            }

            return time
        }

        // We still want to show the zoomed-in window when paused
        // so the user can still scrub and play from a new position
        public var showResetPlaybackStateButton: Bool {
            playbackState == .paused && !seekingState.isManual && !seekingState.isHoldingSeekButton
        }

        public var isWaveformSelectorVisible: Bool {
            return playbackState != .playing && !seekingState.isManual && !seekingState.isHoldingSeekButton
        }

        public var showWaveformStripControlsLeftSeekButton: Bool {
            return playbackState.isActive && waveformStripAnchorTime > 0
        }

        public var showWaveformStripControlsRightSeekButton: Bool {
            return playbackState.isActive && waveformStripAnchorTime < clipDuration
        }

        // Seek time when tapping on the waveform controls
        public var seekTimeWhenTappingOnWaveformControls: Double {
            return windowSizeRatioWhenPlaybackActive * clipDuration / 16
        }

        // Seek time when scrolling on the waveform strip is a little finer than
        // when using the left arrow / right arrow buttons
        public var seekTimeWhenScrollingOnWaveform: Double {
            return windowSizeRatioWhenPlaybackActive * clipDuration / 64
        }

        // MARK: - Waveform Strip Highlighted Portion

        public var waveformStripHighlightedPortionStartPosition: Double {
            if mode == .leftHandleFixed {
                return 0
            } else if playbackState == .idle, !isFineScrubbing {
                return leftHandleTime / clipDuration
            } else if isFineScrubbing,
                      fineScrubbingAnchorTime != nil,
                      fineScrubbingStartPosition != nil
            {
                let position = leftHandlePosition.positionValue(waveformEditorWidth, bufferWidth)
                return position
            } else if playbackState == .playing {
                let position = leftHandlePosition.positionValue(waveformEditorWidth, bufferWidth)
                return position
            } else {
                let position = leftHandlePosition.positionValue(waveformEditorWidth, bufferWidth)
                return position
            }
        }

        public var waveformStripHighlightedPortionEndPosition: Double {
            if mode == .rightHandleFixed {
                return 1.0
            } else if playbackState == .idle, !isFineScrubbing {
                return rightHandleTime / clipDuration
            } else if isFineScrubbing,
                      fineScrubbingAnchorTime != nil,
                      fineScrubbingStartPosition != nil
            {
                let position = rightHandlePosition.positionValue(waveformEditorWidth, bufferWidth)
                return position
            } else {
                let position = rightHandlePosition.positionValue(waveformEditorWidth, bufferWidth)
                return position
            }
        }

        public var waveformStripHighlightedPortionOffset: CGFloat {
            guard playbackState.isActive, !didMoveHandlesWhilePlaybackPaused else {
                return waveformStripWidth * waveformStripHighlightedPortionStartPosition
            }

            if mode == .rightHandleFixed, !didMoveLeftHandleWhilePlaybackPaused {
                let paddingTimeSeconds = playbackActiveWindowSizeSeconds / 2
                let timeOffset = lastKnownLeftHandleTimeBeforePlaying - waveformStripAnchorTime + paddingTimeSeconds
                return (waveformStripWidth * timeOffset / playbackActiveWindowSizeSeconds) + (waveformStripWidth * windowSizeRatioWhenPlaybackActive)
            } else if mode == .leftHandleFixed, !didMoveRightHandleWhilePlaybackPaused {
                let paddingTimeSeconds = playbackActiveWindowSizeSeconds / 2
                let timeOffset = lastKnownRightHandleTimeBeforePlaying - waveformStripAnchorTime + paddingTimeSeconds
                return (waveformStripWidth * timeOffset / playbackActiveWindowSizeSeconds) + (waveformStripWidth * windowSizeRatioWhenPlaybackActive)
            } else {
                return waveformStripWidth * waveformStripHighlightedPortionStartPosition
            }
        }

        public var waveformStripHighlightedPortionWidth: CGFloat {
            if playbackState.isActive, !didMoveHandlesWhilePlaybackPaused {
                return waveformStripWidth / windowSizeRatioWhenPlaybackActive
            } else {
                return waveformStripWidth
            }
        }

        public var waveformStripShapeStepSize: CGFloat {
            if playbackState.isActive {
                return waveformStripWidth / (CGFloat(waveformData.count) * windowSizeRatioWhenPlaybackActive)
            } else if isFineScrubbing {
                return waveformStripWidth / (CGFloat(waveformData.count) * windowSizeRatioWhenFineScrubbing)
            } else {
                return waveformStripWidth / CGFloat(waveformData.count)
            }
        }

        public var waveformStripShapeHorizontalOffset: CGFloat {
            if playbackState.isActive {
                return -waveformStripWidth * CGFloat(waveformStripAnchorTime / clipDuration) / windowSizeRatioWhenPlaybackActive
            } else if isFineScrubbing {
                // When fine-scrubbing, we don't add any padding, but we still need to
                // make sure we're scaling to the fine-scrubbing window size (10% of the waveform).
                return -waveformStripWidth * CGFloat(waveformStripAnchorTime / clipDuration) / windowSizeRatioWhenFineScrubbing
            } else {
                return 0
            }
        }

        public var waveformStripRulerOffset: CGFloat {
            guard playbackState.isActive else { return 0 }
            let timeOffset = -waveformStripAnchorTime
            let result = (waveformStripWidth * timeOffset / playbackActiveWindowSizeSeconds) + (waveformStripWidth * windowSizeRatioWhenPlaybackActive) + waveformStripWidth / 2
            return result
        }

        public var waveformStripRulerWidth: CGFloat {
            guard playbackState.isActive else { return waveformStripWidth }
            return waveformStripWidth / windowSizeRatioWhenPlaybackActive
        }

        public var numberOfRulerTicks: Int {
            return Int(waveformStripRulerWidth / 4)
        }

        // When playback is active, we add width/2 padding to the left and right
        // of the waveform so it starts playing / finishes playing at the center marker.
        // We also need to make sure the offset is to the playback window size (50% of the waveform).
        public var waveformStripShapeData: [Float] {
            guard playbackState.isActive else { return waveformData }
            let paddingCount = Int(ceil(Double(waveformData.count) * Double(windowSizeRatioWhenPlaybackActive / 2)))
            let paddingArray = Array(repeating: Float(0.0), count: paddingCount)
            return paddingArray + waveformData + paddingArray
        }

        public var isHighlightedPortionToTheRightOfVisibleWindow: Bool {
            guard playbackState.isActive, mode == .bothHandlesFree || mode == .rightHandleFixed, !didMoveHandlesWhilePlaybackPaused else { return false }
            return (lastKnownLeftHandleTimeBeforePlaying - waveformStripAnchorTime) > playbackActiveWindowSizeSeconds / 2
        }

        public var isHighlightedPortionToTheLeftOfVisibleWindow: Bool {
            guard playbackState.isActive, mode == .bothHandlesFree || mode == .leftHandleFixed, !didMoveHandlesWhilePlaybackPaused else { return false }
            return (waveformStripAnchorTime - lastKnownRightHandleTimeBeforePlaying) > playbackActiveWindowSizeSeconds / 2
        }

        // Add new state property to track the last drag location
        var lastDragLocation: Double?
        var lastDragTranslation: Double?

        public var didSetupPlayer: Bool = false
        public var didSeekWaveformWhilePlaybackActive: Bool = false

        public var isWaitingToPlay: Bool {
            guard playbackState.isActive else { return false }
            return player.timeControlStatus == .waitingToPlayAtSpecifiedRate
                || (player.timeControlStatus == .playing && player.elapsedTime.seconds < 1)
        }

        init(
            clip: Clip,
            me _: Shared<Me>,
            mode: Mode = .bothHandlesFree,
            clipDuration: Double,
            startPointCaptionOverride: String? = nil,
            endPointCaptionOverride: String? = nil
        ) {
            self.mode = mode
            self.clipDuration = clipDuration > 0.0 ? clipDuration : 1.0 // Set a minimum clip duration to avoid division by zero
            self.player = .init(clip: clip)
            self.leftHandlePosition = mode.leftHandleStartPosition
            self.rightHandlePosition = mode.rightHandleEndPosition
            self.startPointCaptionOverride = startPointCaptionOverride
            self.endPointCaptionOverride = endPointCaptionOverride
        }
    }

    @CasePathable
    @dynamicMemberLookup
    public enum Action {
        case togglePlayPause
        case didDragLeftHandleTo(Double)
        case didDragRightHandleTo(Double)
        case didEndDraggingLeftHandleTo(Double)
        case didEndDraggingRightHandleTo(Double)
        case didSetStartPoint(Double)
        case didSetEndPoint(Double)
        case didScrubTo(Double)
        case didEndScrubbing
        case didFineScrub
        case didEndFineScrubbing
        case didResetLeftHandle
        case didResetRightHandle
        case `internal`(Internal)
        case delegate(Delegate)
        case player(WaveformEditorPlayer.Action)
        case task
        case didStartScrollingLeft
        case didStartScrollingRight
        case didEndScrolling
        case didTapResetPlaybackStateButton
        case didStartDragWaveformStrip(location: Double)
        case didDragWaveformStripTo(translation: Double)
        case didEndDragWaveformStrip
        case didTapWaveformStripControlsMarkerTime
        case didTapWaveformStripControlsLeftSeekButton
        case didTapWaveformStripControlsRightSeekButton
        case didHoldWaveformStripControlsLeftSeekButton
        case didHoldWaveformStripControlsRightSeekButton
        case didTapRecenterToHighlightedPortion
        case didManuallyEditLeftHandleTime(String)
        case didManuallyEditRightHandleTime(String)
        case resetEditableTimestampFields
        case setWaveformData([Float])
        case resetFocusedField(Bool) // dismissKeyboard

        public enum Internal {
            case scrollingTick
        }

        public enum Delegate {
            case didChangeStartPoint(Double)
            case didChangeEndPoint(Double)
            case isDraggingLeftHandle(TimeInterval)
            case didResetLeftHandle
        }
    }

    @Dependency(PlayerClient.self) var playerClient
    @Dependency(APIClient.self) var apiClient

    public var body: some ReducerOf<Self> {
        Scope(state: \.player, action: \.player) {
            WaveformEditorPlayer()
        }
        Reduce { state, action in
            struct ScrollingTimerId: Hashable {}
            struct ElapsedTimePublisherCancellable: Hashable {}
            switch action {
            case .task:
                return playerAction(.setup, state: &state)

            case .setWaveformData(let waveform):
                state.waveformData = waveform
                return .none

            case .player(.delegate(.setupComplete)):
                state.didSetupPlayer = true
                return playerAction(.replaceCurrentItem(state.player.clip), state: &state)

            case .player(.internal(.replaceCurrentItemResponse)):
                guard state.player.timeControlStatus != .waitingToPlayAtSpecifiedRate,
                      state.didSetupPlayer
                else {
                    state.didSetupPlayer = true
                    return .none
                }
                state.lastKnownPlayerTime = state.player.elapsedTime.seconds
                return .none // Don't auto-play on appear, yet

            case .togglePlayPause:
                guard state.didSetupPlayer else { return .none }
                switch state.playbackState {
                case .idle:
                    state.lastKnownLeftHandleTimeBeforePlaying = state.lastKnownLeftHandleTime
                    let fiveSecondsBeforeLeftHandleTime = state.lastKnownLeftHandleTime - 5
                    state.lastKnownPlayerTime = fiveSecondsBeforeLeftHandleTime
                    state.playbackState = .playing
                    // Reset handle position, but not time, when entering playback mode
                    let position = (state.waveformStripHighlightedPortionOffset - (state.waveformStripWidth / 2)) / state.waveformStripWidth
                    state.leftHandlePosition = .custom(min(max(position, 0), 1))
                    return .run { send in
                        await send(.player(.seek(fiveSecondsBeforeLeftHandleTime, play: false)))
                        await send(.player(.play))
                    }

                case .paused:
                    state.lastKnownLeftHandleTimeBeforePlaying = state.lastKnownLeftHandleTime
                    state.playbackState = .playing
                    state.didMoveLeftHandleWhilePlaybackPaused = false
                    return playerAction(.play, state: &state)

                case .playing:
                    state.lastKnownPlayerTime = state.player.elapsedTime.seconds
                    state.playbackState = .paused
                    state.didMoveLeftHandleWhilePlaybackPaused = false
                    state.didMoveRightHandleWhilePlaybackPaused = false
                    let position = (state.waveformStripHighlightedPortionOffset - (state.waveformStripWidth / 2)) / state.waveformStripWidth
                    state.leftHandlePosition = .custom(min(max(position, 0), 1))
                    return .merge(
                        playerAction(.pause, state: &state),
                        .cancel(id: ScrollingTimerId())
                    )
                }

            case .didDragLeftHandleTo(let position):
                if state.playbackState == .paused, !state.didMoveLeftHandleWhilePlaybackPaused {
                    state.didMoveLeftHandleWhilePlaybackPaused = true
                }
                state.userIsInteractingWithLeftHandle = true
                state.leftHandlePosition = handleLeftHandleDrag(
                    to: position,
                    maxPosition: state.leftHandleMaxPosition,
                    snapThreshold: state.snapThreshold,
                    disableSnap: true,
                    waveformEditorWidth: state.waveformEditorWidth,
                    bufferWidth: state.bufferWidth
                )
                return .none

            case .didDragRightHandleTo(let position):
                if state.playbackState == .paused, !state.didMoveRightHandleWhilePlaybackPaused {
                    state.didMoveRightHandleWhilePlaybackPaused = true
                }
                state.userIsInteractingWithRightHandle = true
                state.rightHandlePosition = handleRightHandleDrag(
                    to: position,
                    minPosition: state.rightHandleMinPosition,
                    snapThreshold: state.snapThreshold,
                    disableSnap: true,
                    waveformEditorWidth: state.waveformEditorWidth,
                    bufferWidth: state.bufferWidth
                )
                return .none

            case .didEndDraggingLeftHandleTo(let position):
                if state.didMoveLeftHandleWhilePlaybackPaused {
                    state.lastKnownLeftHandleTimeBeforePlaying = state.leftHandleTime
                }
                state.userIsInteractingWithLeftHandle = false
                if state.isFineScrubbing {
                    // We have to fetch this before clearing the fine scrubbing state
                    let finalTime = state.leftHandleTime

                    // Clear fine scrubbing state
                    state.isFineScrubbing = false
                    state.fineScrubbingAnchorTime = nil
                    state.fineScrubbingStartPosition = nil
                    state.seekingState = .notActive

                    // Snap to the buffer edge if we're past the clip duration
                    if finalTime >= state.clipDuration {
                        state.leftHandlePosition = .leftHandleAtRightBufferEdge
                    } else {
                        let normalizedPosition = finalTime / state.clipDuration
                        let scaledPosition = normalizedPosition * (state.waveformStripWidth / state.waveformEditorWidth)
                        state.leftHandlePosition = .custom(scaledPosition)
                    }
                } else {
                    state.leftHandlePosition = handleLeftHandleDrag(
                        to: position,
                        maxPosition: state.leftHandleMaxPosition,
                        snapThreshold: state.snapThreshold,
                        disableSnap: state.playbackState.isActive,
                        waveformEditorWidth: state.waveformEditorWidth,
                        bufferWidth: state.bufferWidth
                    )
                }
                return .send(.delegate(.didChangeStartPoint(state.leftHandleTime)))

            case .didEndDraggingRightHandleTo(let position):
                if state.didMoveRightHandleWhilePlaybackPaused {
                    state.lastKnownLeftHandleTimeBeforePlaying = state.rightHandleTime
                }
                state.userIsInteractingWithRightHandle = false
                if state.isFineScrubbing {
                    // We have to fetch this before clearing the fine scrubbing state
                    let finalTime = state.rightHandleTime

                    // Clear fine scrubbing state
                    state.isFineScrubbing = false
                    state.fineScrubbingAnchorTime = nil
                    state.fineScrubbingStartPosition = nil
                    state.seekingState = .notActive

                    // Snap to the buffer edge if we're past the clip duration
                    if finalTime >= state.clipDuration {
                        state.rightHandlePosition = .rightHandleAtRightBufferEdge
                    } else {
                        let normalizedPosition = finalTime / state.clipDuration
                        let scaledPosition = normalizedPosition * (state.waveformStripWidth / state.waveformEditorWidth)
                        state.rightHandlePosition = .custom(scaledPosition)
                    }
                } else {
                    state.rightHandlePosition = handleRightHandleDrag(
                        to: position,
                        minPosition: state.rightHandleMinPosition,
                        snapThreshold: state.snapThreshold,
                        disableSnap: state.playbackState.isActive,
                        waveformEditorWidth: state.waveformEditorWidth,
                        bufferWidth: state.bufferWidth
                    )
                }
                return .send(.delegate(.didChangeEndPoint(state.rightHandleTime)))

            case .didSetStartPoint(let time):
                let position = time * (state.waveformEditorWidth + state.bufferWidth) / state.waveformEditorWidth
                state.leftHandlePosition = handleLeftHandleDrag(
                    to: position,
                    maxPosition: state.leftHandleMaxPosition,
                    snapThreshold: state.snapThreshold,
                    disableSnap: state.playbackState.isActive,
                    waveformEditorWidth: state.waveformEditorWidth,
                    bufferWidth: state.bufferWidth
                )
                return .none

            case .didSetEndPoint(let time):
                let position = time * (state.waveformEditorWidth + state.bufferWidth) / state.waveformEditorWidth
                state.rightHandlePosition = handleRightHandleDrag(
                    to: position,
                    minPosition: state.rightHandleMinPosition,
                    snapThreshold: state.snapThreshold,
                    disableSnap: state.playbackState.isActive,
                    waveformEditorWidth: state.waveformEditorWidth,
                    bufferWidth: state.bufferWidth
                )
                return .none

            case .didScrubTo:
                return .none

            case .didEndScrubbing:
                return .none

            case .didFineScrub:
                let currentTime = state.leftHandleTime
                let currentPosition = state.leftHandlePosition.positionValue(state.waveformEditorWidth, state.bufferWidth)
                state.isFineScrubbing = true
                state.fineScrubbingAnchorTime = currentTime
                state.fineScrubbingStartPosition = currentPosition
                return .none

            case .didEndFineScrubbing:
                state.isFineScrubbing = false
                state.fineScrubbingAnchorTime = nil
                state.fineScrubbingStartPosition = nil
                // Clean up scrolling state if we entered while fine-scrubbing
                if state.seekingState.isFineScrubbing {
                    state.seekingState = .notActive
                    return .cancel(id: ScrollingTimerId())
                }
                return .none

            case .didResetLeftHandle:
                state.leftHandlePosition = state.mode.leftHandleStartPosition
                return .send(.delegate(.didResetLeftHandle))

            case .didResetRightHandle:
                state.rightHandlePosition = state.mode.rightHandleEndPosition
                return .none

            case .didStartDragWaveformStrip(let location):
                state.didMoveLeftHandleWhilePlaybackPaused = false
                state.didMoveRightHandleWhilePlaybackPaused = false
                state.lastDragLocation = location
                state.lastDragTranslation = 0.0
                return .none

            case .didDragWaveformStripTo(let translation):
                let distance = translation - (state.lastDragTranslation ?? 0)
                state.lastDragTranslation = translation
                return handleWaveformStripDrag(state: &state, distance: distance)

            case .didEndDragWaveformStrip:
                state.lastDragLocation = nil
                guard state.playbackState.isActive else { return .none }
                guard state.seekingState == .manualWhilePlaying else {
                    state.seekingState = .notActive
                    let position = (state.waveformStripHighlightedPortionOffset - (state.waveformStripWidth / 2)) / state.waveformStripWidth
                    state.leftHandlePosition = .custom(min(max(position, 0), 1))
                    return .none
                }
                state.seekingState = .notActive
                return .send(.togglePlayPause)

            case .didStartScrollingLeft:
                state.seekingState = .isFineScrubbingLeft
                return .run { send in
                    for await _ in Timer.publish(every: 0.1, on: .main, in: .common).autoconnect().values {
                        await send(.internal(.scrollingTick))
                    }
                }
                .cancellable(id: ScrollingTimerId(), cancelInFlight: true)

            case .didStartScrollingRight:
                state.seekingState = .isFineScrubbingRight
                return .run { send in
                    for await _ in Timer.publish(every: 0.1, on: .main, in: .common).autoconnect().values {
                        await send(.internal(.scrollingTick))
                    }
                }
                .cancellable(id: ScrollingTimerId(), cancelInFlight: true)

            case .didEndScrolling:
                state.seekingState = .notActive
                return .cancel(id: ScrollingTimerId())

            case .internal(.scrollingTick):
                guard state.seekingState.isActive else { return .none }
                if state.seekingState.isFineScrubbing,
                   let currentAnchorTime = state.fineScrubbingAnchorTime,
                   let fineScrubbingStartPosition = state.fineScrubbingStartPosition
                {
                    let delta = state.clipDuration * 0.005
                    var newAnchorTime = currentAnchorTime

                    if state.seekingState == .isFineScrubbingLeft {
                        newAnchorTime = currentAnchorTime - delta
                    } else if state.seekingState == .isFineScrubbingRight {
                        newAnchorTime = currentAnchorTime + delta
                    }

                    let offset = (fineScrubbingStartPosition - 0.5) * state.fineScrubbingWindowSizeSeconds
                    let leftBound = state.fineScrubbingAnchorTimeLeftEdgeMinimum + offset
                    let rightBound = state.fineScrubbingAnchorTimeRightEdgeMaximum + offset

                    if newAnchorTime < leftBound {
                        state.fineScrubbingAnchorTime = leftBound
                    } else if newAnchorTime > rightBound {
                        state.fineScrubbingAnchorTime = rightBound
                    } else {
                        state.fineScrubbingAnchorTime = newAnchorTime
                    }
                } else if state.seekingState == .isHoldingLeftSeekButton {
                    return .send(.didTapWaveformStripControlsLeftSeekButton)
                } else if state.seekingState == .isHoldingRightSeekButton {
                    return .send(.didTapWaveformStripControlsRightSeekButton)
                }
                return .none

            case .player:
                return .none

            case .didTapResetPlaybackStateButton:
                let leftHandleTime = state.leftHandleTime // Store this before resetting playback state and changing handle position
                state.playbackState = .idle
                state.seekingState = .notActive
                state.didMoveLeftHandleWhilePlaybackPaused = false
                state.didMoveRightHandleWhilePlaybackPaused = false
                state.lastKnownPlayerTime = leftHandleTime
                let scaleRatio = state.waveformStripWidth / state.waveformEditorWidth
                let scaledPosition = (leftHandleTime / state.clipDuration) * scaleRatio
                if state.clipDuration - leftHandleTime < state.snapThreshold {
                    state.leftHandlePosition = .leftHandleAtRightBufferEdge
                } else {
                    state.leftHandlePosition = .custom(scaledPosition)
                }
                return playerAction(.seek(leftHandleTime, play: false), state: &state)

            case .didTapWaveformStripControlsMarkerTime:
                // Set to halfway point
                state.leftHandlePosition = .custom(0.5)
                state.didMoveLeftHandleWhilePlaybackPaused = true
                return .none

            case .didTapWaveformStripControlsLeftSeekButton:
                let newTime = max(0, state.lastKnownPlayerTime - state.seekTimeWhenTappingOnWaveformControls)
                state.lastKnownPlayerTime = newTime
                let position = (state.waveformStripHighlightedPortionOffset - (state.waveformStripWidth / 2)) / state.waveformStripWidth
                state.leftHandlePosition = .custom(min(max(position, 0), 1))
                return playerAction(.seek(newTime, play: false), state: &state)

            case .didTapWaveformStripControlsRightSeekButton:
                let newTime = min(state.clipDuration, state.lastKnownPlayerTime + state.seekTimeWhenTappingOnWaveformControls)
                state.lastKnownPlayerTime = newTime
                let position = (state.waveformStripHighlightedPortionOffset - (state.waveformStripWidth / 2)) / state.waveformStripWidth
                state.leftHandlePosition = .custom(min(max(position, 0), 1))
                return playerAction(.seek(newTime, play: false), state: &state)

            case .didHoldWaveformStripControlsLeftSeekButton:
                state.seekingState = .isHoldingLeftSeekButton
                return .run { send in
                    for await _ in Timer.publish(every: 0.1, on: .main, in: .common).autoconnect().values {
                        await send(.internal(.scrollingTick))
                    }
                }
                .cancellable(id: ScrollingTimerId(), cancelInFlight: true)

            case .didHoldWaveformStripControlsRightSeekButton:
                state.seekingState = .isHoldingRightSeekButton
                return .run { send in
                    for await _ in Timer.publish(every: 0.1, on: .main, in: .common).autoconnect().values {
                        await send(.internal(.scrollingTick))
                    }
                }
                .cancellable(id: ScrollingTimerId(), cancelInFlight: true)

            case .didTapRecenterToHighlightedPortion:
                let newTime = state.lastKnownLeftHandleTimeBeforePlaying
                state.lastKnownPlayerTime = newTime
                state.leftHandlePosition = .custom(0.5)
                return playerAction(.seek(newTime, play: false), state: &state)

            case .didManuallyEditLeftHandleTime(let time):
                return handleManuallyEditTimestamp(state: &state, time: time, handle: .leftHandle)

            case .didManuallyEditRightHandleTime(let time):
                return handleManuallyEditTimestamp(state: &state, time: time, handle: .rightHandle)

            case .resetFocusedField(let shouldDismissKeyboard):
                if shouldDismissKeyboard {
                    state.shouldSaveTimestampsAndDismissKeyboard = true
                } else {
                    state.shouldDismissKeyboard = true
                }
                return .none

            case .resetEditableTimestampFields:
                state.shouldDismissKeyboard = false
                state.shouldSaveTimestampsAndDismissKeyboard = false
                return .none

            case .delegate:
                return .none
            }
        }
    }

    func handleLeftHandleDrag(
        to position: Double,
        maxPosition: Double?,
        snapThreshold: Double,
        disableSnap: Bool = false,
        waveformEditorWidth _: CGFloat,
        bufferWidth _: CGFloat
    ) -> HandlePosition {
        // Scale the incoming position to match our new positionValue behavior
        let scaledPosition = position

        guard let maxPosition = maxPosition else {
            return .custom(scaledPosition)
        }

        if maxPosition - snapThreshold <= scaledPosition && !disableSnap {
            return .leftHandleAtRightBufferEdge
        } else if scaledPosition <= snapThreshold && !disableSnap {
            return .leftHandleAtLeftEdge
        } else {
            return .custom(scaledPosition)
        }
    }

    func handleRightHandleDrag(
        to position: Double,
        minPosition: Double?,
        snapThreshold: Double,
        disableSnap: Bool = false,
        waveformEditorWidth: CGFloat,
        bufferWidth: CGFloat
    ) -> HandlePosition {
        let scaledPosition = position * (waveformEditorWidth + bufferWidth) / waveformEditorWidth

        guard let minPosition = minPosition else {
            return .custom(scaledPosition)
        }

        if minPosition + snapThreshold >= scaledPosition && !disableSnap {
            return .rightHandleAtLeftBufferEdge
        } else if scaledPosition >= 1.0 - snapThreshold && !disableSnap {
            return .rightHandleAtRightEdge
        } else {
            return .custom(scaledPosition)
        }
    }

    func handleWaveformStripDrag(
        state: inout State,
        distance: Double
    ) -> Effect<Action> {
        guard state.playbackState.isActive else {
            return .none
        }

        if !state.seekingState.isManual {
            state.seekingState = state.playbackState == .playing ?
                .manualWhilePlaying :
                .manualWhilePaused
            state.didSeekWaveformWhilePlaybackActive = true
        }

        let normalizedDistance = distance / state.waveformStripWidth
        let newTime = max(0, min(state.clipDuration, state.lastKnownPlayerTime - normalizedDistance * state.playbackActiveWindowSizeSeconds))

        if abs(state.lastKnownPlayerTime - newTime) >= 0.01 {
            state.lastKnownPlayerTime = newTime
            let wasPlaying = state.playbackState == .playing

            return .concatenate(
                wasPlaying ? .send(.togglePlayPause) : .none,
                playerAction(.seek(newTime, play: false), state: &state)
            )
        }

        return .none
    }

    // Sets the timestamp and resets the playback state to make things
    // simpler. If the manual timestamp is greater than the clip duration,
    // we set it to the right buffer edge or the clip duration.
    func handleManuallyEditTimestamp(
        state: inout State,
        time: String,
        handle: Handle
    ) -> Effect<Action> {
        let timeComponents = time.split(separator: ":")
        let seconds = Int(timeComponents[1]) ?? 0
        let minutes = Int(timeComponents[0]) ?? 0
        let newTime = Double(minutes * 60 + seconds)
        state.playbackState = .idle
        state.seekingState = .notActive
        state.didMoveLeftHandleWhilePlaybackPaused = false
        state.didMoveRightHandleWhilePlaybackPaused = false
        if newTime > state.clipDuration {
            if handle == .leftHandle {
                state.leftHandlePosition = .leftHandleAtRightBufferEdge
            } else {
                state.rightHandlePosition = .rightHandleAtRightBufferEdge
            }
        } else {
            let normalizedPosition = newTime / state.clipDuration
            let scaledPosition = normalizedPosition * (state.waveformStripWidth / state.waveformEditorWidth)
            if handle == .leftHandle {
                state.leftHandlePosition = .custom(scaledPosition)
            } else {
                state.rightHandlePosition = .custom(scaledPosition)
            }
        }
        state.shouldDismissKeyboard = false
        state.shouldSaveTimestampsAndDismissKeyboard = false
        return .none
    }
}
