import AVFoundation
import Combine
import ComposableArchitecture
import SwiftUI

public struct WaveformSelectorHandle: View {
    let position: Double
    let otherPosition: Double
    let isLeft: Bool
    let totalWidth: CGFloat
    let height: CGFloat
    let duration: Double
    let minimumDuration: Double?
    let maximumDuration: Double?
    let onDrag: (Double) -> Void
    let onDragEnd: (Double) -> Void
    let canMove: Bool
    let isHidden: Bool
    let onFineScrub: () -> Void
    let onFineScrubEnd: () -> Void
    let fineScrubbingBufferTime: Double = 0.5
    @State private var currentPosition: Double
    @State private var fineScrubTimer: Timer?
    @State private var isHandling: Bool = false
    @State private var isFineScrubbing: Bool = false
    @State private var fineScrubAndDragTimer: Timer?
    @State private var isFineScrubAndDragging: Bool = false
    let minPosition: Double?
    let maxPosition: Double?
    let fineScrubbingDisabled: Bool
    let onFineScrubAndDragStartLeft: () -> Void
    let onFineScrubAndDragStartRight: () -> Void
    let onFineScrubAndDragEnd: () -> Void
    private let fineScrubThreshold: CGFloat = 0.2 // 20% from either edge

    @State private var didStartDraggingToTheLeft: Bool = false
    @State private var didStartDraggingToTheRight: Bool = false
    @State private var hapticProxy: Int = .zero
    @State private var didTriggerBoundaryHaptic: Bool = false

    var offset: CGFloat {
        // Offset to make the rectangle appear on th edge of the handle
        if isLeft {
            return -2
        } else {
            return 2
        }
    }

    public init(
        position: Double,
        otherPosition: Double,
        isLeft: Bool,
        totalWidth: CGFloat,
        height: CGFloat,
        duration: Double,
        minimumDuration: Double?,
        maximumDuration: Double?,
        onDrag: @escaping (Double) -> Void,
        onDragEnd: @escaping (Double) -> Void,
        canMove: Bool,
        isHidden: Bool,
        onFineScrub: @escaping () -> Void,
        onFineScrubEnd: @escaping () -> Void,
        minPosition: Double? = nil,
        maxPosition: Double? = nil,
        fineScrubbingDisabled: Bool = false,
        onFineScrubAndDragStartLeft: @escaping () -> Void,
        onFineScrubAndDragStartRight: @escaping () -> Void,
        onFineScrubAndDragEnd: @escaping () -> Void
    ) {
        self.position = position
        self.otherPosition = otherPosition
        self.isLeft = isLeft
        self.totalWidth = totalWidth
        self.height = height
        self.duration = duration
        self.minimumDuration = minimumDuration
        self.maximumDuration = maximumDuration
        self.onDrag = onDrag
        self.onDragEnd = onDragEnd
        self.canMove = canMove
        self.isHidden = isHidden
        self.onFineScrub = onFineScrub
        self.onFineScrubEnd = onFineScrubEnd
        self._currentPosition = State(initialValue: position)
        self.minPosition = minPosition
        self.maxPosition = maxPosition
        self.fineScrubbingDisabled = fineScrubbingDisabled
        self.onFineScrubAndDragStartLeft = onFineScrubAndDragStartLeft
        self.onFineScrubAndDragStartRight = onFineScrubAndDragStartRight
        self.onFineScrubAndDragEnd = onFineScrubAndDragEnd
    }

    private func startFineScrubTimer() {
        guard !isFineScrubbing else { return }

        fineScrubTimer?.invalidate()
        fineScrubTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
            isFineScrubbing = true
            onFineScrub()
            hapticProxy += 1
        }
    }

    private func startFineScrubAndDragTimer() {
        guard !isFineScrubAndDragging else { return }

        fineScrubAndDragTimer?.invalidate()
        fineScrubAndDragTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
            if didStartDraggingToTheLeft {
                onFineScrubAndDragStartLeft()
            } else {
                onFineScrubAndDragStartRight()
            }
        }
    }

    private func cancelFineScrubAndDragTimer() {
        fineScrubAndDragTimer?.invalidate()
        fineScrubAndDragTimer = nil
        isFineScrubAndDragging = false
    }

    private func cancelFineScrubTimer() {
        fineScrubTimer?.invalidate()
        fineScrubTimer = nil
    }

    private func checkForFineScrubAndDrag(_ position: CGFloat) {
        guard isFineScrubbing else { return }

        let positionInPoints = position
        let leftThreshold = totalWidth * fineScrubThreshold
        let rightThreshold = totalWidth * (1 - fineScrubThreshold)

        if positionInPoints <= leftThreshold {
            didStartDraggingToTheLeft = true
            didStartDraggingToTheRight = false
            startFineScrubAndDragTimer()
        } else if positionInPoints >= rightThreshold {
            didStartDraggingToTheLeft = false
            didStartDraggingToTheRight = true
            startFineScrubAndDragTimer()
        } else {
            cancelFineScrubAndDragTimer()
            onFineScrubAndDragEnd()
        }
    }

    private func constrainPosition(_ newPosition: Double) -> Double {
        if isLeft {
            let limit = maxPosition ?? otherPosition
            if newPosition >= limit && !didTriggerBoundaryHaptic {
                hapticProxy += 1
                didTriggerBoundaryHaptic = true
            } else if newPosition < limit {
                didTriggerBoundaryHaptic = false
            }
            return max(minPosition ?? 0, min(newPosition, limit))
        } else {
            let limit = minPosition ?? otherPosition
            if newPosition <= limit && !didTriggerBoundaryHaptic {
                hapticProxy += 1
                didTriggerBoundaryHaptic = true
            } else if newPosition > limit {
                didTriggerBoundaryHaptic = false
            }
            return min(1, max(newPosition, limit))
        }
    }

    public var body: some View {
        handle
            .opacity(isHidden ? 0 : 1)
            .disabled(!canMove)
            .position(x: (currentPosition * totalWidth) + offset, y: 50)
            .gesture(
                DragGesture()
                    .onChanged { value in
                        if !isHandling {
                            isHandling = true
                            hapticProxy += 1
                        }
                        let newPosition = value.location.x / totalWidth

                        // If slowly dragging or moving down while dragging, start fine scrubbing
                        if !fineScrubbingDisabled, value.velocity.width.magnitude < 100 || value.translation.height > 50 {
                            startFineScrubTimer()
                            checkForFineScrubAndDrag(value.location.x)
                        } else {
                            cancelFineScrubTimer()
                            cancelFineScrubAndDragTimer()
                        }

                        currentPosition = constrainPosition(newPosition)
                        onDrag(currentPosition)
                    }
                    .onEnded { value in
                        isHandling = false
                        hapticProxy += 1
                        cancelFineScrubTimer()
                        cancelFineScrubAndDragTimer()
                        let wasFineScrubbing = isFineScrubbing

                        let newPosition = value.location.x / totalWidth
                        currentPosition = constrainPosition(newPosition)
                        onDragEnd(currentPosition)

                        if wasFineScrubbing {
                            isFineScrubbing = false
                            hapticProxy += 1
                            onFineScrubEnd()
                        }
                    }
            )
            .onChange(of: position) { _, newPosition in
                currentPosition = newPosition
            }
            .sensoryFeedbackIfEnabled(.success, trigger: hapticProxy)
    }

    @ViewBuilder
    private var handle: some View {
        ZStack {
            Rectangle()
                .frame(width: 14, height: height)
                .cornerRadius(20, corners: [.topLeft, .bottomLeft])
                .foregroundColor(Color.SemanticV1.iconLink)
                .offset(x: 2)
            Rectangle()
                .frame(width: 12, height: height)
                .cornerRadius(20, corners: [.topLeft, .bottomLeft])
                .offset(x: 10)
                .foregroundColor(Color.SemanticV1.iconLink)
                .blendMode(.destinationOut)
        }
        .compositingGroup()
        .overlay(
            RoundedRectangle(cornerRadius: 20)
                .fill(Color.white)
                .frame(width: 3, height: 28)
        )
    }
}
