import SwiftUI

struct HooksEditorScrubBarView: View {
    let totalDuration: Double
    let currentTime: Double
    let isScrubbing: Bool
    let horizontalPadding: CGFloat
    let backgroundCornerRadius: CGFloat
    let onScrubStart: (Double) -> Void
    let onScrubChange: (Double) -> Void
    let onScrubEnd: (Double) -> Void

    @GestureState private var isDragging: Bool = false
    @State private var hasSentScrubStart: Bool = false

    // Smooth progress animation
    @State private var animatedProgress: Double = 0
    @State private var lastUpdateTime: Date = Date()

    private enum Constants {
        static let trackHeight: CGFloat = 3
        static let progressHeightNormal: CGFloat = 3
        static let progressHeightDragging: CGFloat = 6
        static let knobSizeNormal: CGFloat = 14
        static let knobSizeDragging: CGFloat = 18
        static let knobShadowOpacity: Double = 0.3
        static let knobShadowRadius: CGFloat = 2
    }

    private var activeProgressHeight: CGFloat {
        isDragging ? Constants.progressHeightDragging : Constants.progressHeightNormal
    }

    private var activeKnobSize: CGFloat {
        isDragging ? Constants.knobSizeDragging : Constants.knobSizeNormal
    }

    var body: some View {
        VStack(spacing: 24) {
            Spacer()

            if isDragging {
                timeIndicatorRow
                    .transition(.move(edge: .bottom).combined(with: .opacity))
            }

            scrubberContent
                .frame(height: Constants.trackHeight)
                .padding(.horizontal, horizontalPadding)
        }
        .padding(.bottom, isDragging ? (activeKnobSize / 2) - (Constants.trackHeight / 2) : 0)
        .background(dimmingGradientBackground)
        .animation(.easeInOut(duration: 0.15), value: isDragging)
    }

    private var timeIndicatorRow: some View {
        HStack(spacing: 4) {
            Text(currentTime.formatTimeConditional)
                .foregroundStyle(Color.SemanticV1.alwaysLighterBeige)

            Text("/")
                .foregroundStyle(Color.SemanticV1.alwaysLighterBeige.opacity(0.7))

            Text(totalDuration.formatTimeConditional)
                .foregroundStyle(Color.SemanticV1.alwaysLighterBeige.opacity(0.7))
        }
        .typographyV1(.caption2.inputSans())
    }

    private var scrubberContent: some View {
        GeometryReader { geo in
            let width = max(1, geo.size.width)
            let displayTime = isDragging ? currentTime : animatedProgress
            let ratio = progressRatio(displayTime)
            let progressWidth = CGFloat(ratio) * width

            progressTrack(
                totalWidth: width,
                progressBarWidth: progressWidth,
                progressBarHeight: activeProgressHeight,
                knobSize: activeKnobSize
            )
            /// HCI-compliant hit-area helper
            /// This `.overlay` approach allows us to increase the tap area without resizing the view.
            .overlay {
                    Rectangle()
                        .fill(Color.clear)
                        .frame(minWidth: 44, minHeight: 44)
                        .contentShape(.capsule)
                        .gesture(dragGesture(width: width))
                }
        }
        .onChange(of: currentTime) { _, newTime in
            updateAnimatedProgress(to: newTime)
        }
        .onAppear {
            animatedProgress = currentTime
        }
    }

    private func progressTrack(
        totalWidth: CGFloat,
        progressBarWidth: CGFloat,
        progressBarHeight: CGFloat,
        knobSize: CGFloat
    ) -> some View {
        ZStack(alignment: .leading) {
            Capsule()
                .fill(isDragging ? Color.SemanticV1.alwaysBlack1.opacity(0.2) : Color.white.opacity(0.3))
                .frame(height: Constants.trackHeight)

            Capsule()
                .fill(Color.SemanticV1.alwaysLighterBeige)
                .frame(width: progressBarWidth, height: progressBarHeight)

            if isDragging {
                Circle()
                    .fill(Color.SemanticV1.alwaysLighterBeige)
                    .frame(width: knobSize, height: knobSize)
                    .shadow(
                        color: .black.opacity(Constants.knobShadowOpacity),
                        radius: Constants.knobShadowRadius
                    )
                    .offset(x: max(0, min(progressBarWidth - (knobSize / 2), totalWidth - knobSize)))
            }
        }
    }

    private var dimmingGradientBackground: some View {
        LinearGradient(
            stops: [
                .init(color: .clear, location: 0),
                .init(color: isScrubbing ? .black.opacity(0.1) : .clear, location: 0.25),
                .init(color: isScrubbing ? .black.opacity(0.15) : .clear, location: 0.75),
                .init(color: isScrubbing ? .black.opacity(0.25) : .clear, location: 0.9),
                .init(color: isScrubbing ? .black.opacity(0.4) : .clear, location: 1.0),
            ],
            startPoint: UnitPoint(x: 0.5, y: 0.8),
            endPoint: UnitPoint(x: 0.5, y: 1.0)
        )
        .cornerRadius(backgroundCornerRadius)
    }

    private func dragGesture(width: CGFloat) -> some Gesture {
        DragGesture(minimumDistance: 0)
            .updating($isDragging) { _, state, _ in
                state = true
            }
            .onChanged { value in
                let x = min(max(0, value.location.x), width)
                let seconds = totalDuration * Double(x / width)
                if !hasSentScrubStart {
                    hasSentScrubStart = true
                    onScrubStart(seconds)
                }
                onScrubChange(seconds)
            }
            .onEnded { value in
                let x = min(max(0, value.location.x), width)
                let seconds = totalDuration * Double(x / width)
                onScrubEnd(seconds)
                hasSentScrubStart = false
            }
    }

    private func progressRatio(_ seconds: Double) -> Double {
        guard totalDuration > 0 else { return 0 }
        return min(max(0, seconds / totalDuration), 1)
    }

    private func updateAnimatedProgress(to newTime: Double) {
        guard !isDragging else {
            animatedProgress = newTime
            return
        }

        let currentDate = Date()
        let timeDelta = currentDate.timeIntervalSince(lastUpdateTime)
        lastUpdateTime = currentDate

        let timeDifference = abs(newTime - animatedProgress)
        if timeDifference > 0.5 {
            // Change immediately if the difference is too large to avoid
            animatedProgress = newTime
        } else {
            // The animation duration is based on the time delta, but capped at 0.3 seconds
            // This prevents the animation from being too slow or too fast
            withAnimation(.easeOut(duration: min(timeDelta * 2, 0.3))) {
                animatedProgress = newTime
            }
        }
    }
}
