import APIClient
import AVFoundation
import ComponentLibrary
import ComposableArchitecture
import FeatureClipDetail
import Foundation
import SwiftUI

/*
 The progress bar that can be used in ExpandedPlayer.
 */
public struct ExpandedPlayerProgressViewV2: View {
    // Properties instead of store
    private let totalTime: CMTime
    private let fallbackTotalTimeSeconds: Double
    private let displayTime: CMTime
    private let timeControlStatus: AVPlayer.TimeControlStatus
    private let onScrubStart: (CMTime) -> Void
    private let onScrubComplete: (CMTime) -> Void

    @State private var progressWidth: Double = 0.0
    @State private var progressFullWidth: Double = 0.0
    @State private var floatingTimestampWidth: CGFloat = 32
    @State private var localScrubTime: CMTime?
    @GestureState private var isDragging: Bool = false

    public init(
        totalTime: CMTime,
        fallbackTotalTimeSeconds: Double,
        displayTime: CMTime,
        timeControlStatus: AVPlayer.TimeControlStatus,
        onScrubStart: @escaping (CMTime) -> Void,
        onScrubComplete: @escaping (CMTime) -> Void
    ) {
        self.totalTime = totalTime
        self.fallbackTotalTimeSeconds = fallbackTotalTimeSeconds
        self.displayTime = displayTime
        self.timeControlStatus = timeControlStatus
        self.onScrubStart = onScrubStart
        self.onScrubComplete = onScrubComplete
    }

    var currentDisplayTime: CMTime {
        return localScrubTime ?? displayTime
    }

    var scrubMarkerXOffset: CGFloat {
        guard progressFullWidth > 0 else { return 0 }

        let totalTimeSeconds = totalTime.isInvalid ? fallbackTotalTimeSeconds : totalTime.seconds
        guard totalTimeSeconds > 0 else { return -progressFullWidth / 2 }

        let progress = min(totalTimeSeconds, max(0, currentDisplayTime.seconds))
        return CGFloat(progress / totalTimeSeconds) * progressFullWidth - progressFullWidth / 2
    }

    var scrubMarkerSize: CGFloat {
        return isDragging ? 16 : 10
    }

    var progressBarHeight: CGFloat {
        return isDragging ? 8 : 4
    }

    var scrubFloatingMarkerXOffset: CGFloat {
        guard progressFullWidth > 0 else { return 0 }

        // Takes into account screen edge insets by not letting the
        // position go over the left or right edges of the progress bar. This accounts for
        // the timestamp container width to calculate the minimum and maximum values it can return.
        // Minimum = 12
        // Maximum = UIScreen.width - 12
        let minimum = 24 - progressFullWidth / 2
        let maximum = progressFullWidth / 2 - 24
        return max(minimum, min(maximum, scrubMarkerXOffset))
    }

    var scrubFloatingMarkerYOffset: CGFloat {
        return isDragging ? -42 : 0
    }

    var progress: Double {
        let totalTimeSeconds = totalTime.isInvalid ? fallbackTotalTimeSeconds : totalTime.seconds
        return min(totalTimeSeconds, max(0, currentDisplayTime.seconds)) / totalTimeSeconds
    }

    private let transitionCurve = Animation.timingCurve(0.65, 0, 0.35, 1, duration: 0.2)

    public var body: some View {
        ZStack {
            GeometryReader { geometry in
                ZStack(alignment: .leading) {
                    // Background track
                    Capsule()
                        .fill(Color.SemanticV2.backgroundFogThick)
                        .frame(height: progressBarHeight)
                        .shadow(color: Color.SemanticV2.backgroundGlassDense, radius: 2)

                    Color.SemanticV2.foregroundPrimary
                        .frame(width: CGFloat(progress) * geometry.size.width, height: progressBarHeight)
                        .clipShape(Capsule())
                }
                .frame(height: progressBarHeight)
                .scaleEffect(y: isDragging ? 1.1 : 1.0)
                .animation(transitionCurve, value: isDragging)
                .placeholderShimmering(
                    isVisible: timeControlStatus == .waitingToPlayAtSpecifiedRate,
                    opacity: 0.3,
                    color: Color.white,
                    duration: 1.5
                )
                .onAppear {
                    progressFullWidth = geometry.size.width
                }
                .onChange(of: geometry.size.width) { _, newWidth in
                    progressFullWidth = newWidth
                }
            }
            .frame(height: progressBarHeight)

            ZStack {
                Circle()
                    .fill(Color.SemanticV2.foregroundPrimary)
                    .frame(width: scrubMarkerSize, height: scrubMarkerSize)
                    .scaleEffect(isDragging ? 1.1 : 1.0)
                    .animation(transitionCurve, value: isDragging)
            }
            .contentShape(Rectangle().size(width: 40, height: 40))
            .offset(x: scrubMarkerXOffset)
            .highPriorityGesture(drag)
            .allowsHitTesting(true)

            if isDragging {
                Text(currentDisplayTime.seconds.positionalTime)
                    .contentTransition(.numericText())
                    .typographyV1(.monospace.lineHeight(12).inputSans())
                    .foregroundStyle(Color.SemanticV2.omniplayerWhite)
                    .padding(6)
                    .background(RoundedRectangle(cornerRadius: 4).fill(Color.SemanticV2.omniplayerBlack))
                    .onGeometryChange(for: CGSize.self) { proxy in proxy.size } action: { size in
                        floatingTimestampWidth = size.width
                    }
                    .offset(x: scrubFloatingMarkerXOffset, y: scrubFloatingMarkerYOffset)
                    .frame(width: floatingTimestampWidth + 12)
                    .animation(transitionCurve, value: isDragging)
            }
        }
        .padding(.vertical, 12)
        .onChange(of: displayTime) { _, _ in
            // Clear localScrubTime on the next displayTime change
            if localScrubTime != nil {
                localScrubTime = nil
            }
        }
    }

    private var drag: some Gesture {
        DragGesture()
            .updating($isDragging) { value, isDragging, _ in
                if !isDragging {
                    let totalTimeSeconds = totalTime.isInvalid ? fallbackTotalTimeSeconds : totalTime.seconds
                    let initialScrubTime = value.time(totalTime: totalTimeSeconds, progressWidth: progressFullWidth)
                    onScrubStart(initialScrubTime)
                }
                isDragging = true
            }
            .onChanged { value in
                // Only allow horizontal dragging and filter out scrolls
                guard value.translation.width != 0 else { return }

                let totalTimeSeconds = totalTime.isInvalid ? fallbackTotalTimeSeconds : totalTime.seconds
                let scrubTime = value.time(totalTime: totalTimeSeconds, progressWidth: progressFullWidth)
                localScrubTime = scrubTime
            }
            .onEnded { value in
                let totalTimeSeconds = totalTime.isInvalid ? fallbackTotalTimeSeconds : totalTime.seconds
                let newTime = value.time(totalTime: totalTimeSeconds, progressWidth: progressFullWidth)
                onScrubComplete(newTime)
            }
    }
}

private extension DragGesture.Value {
    func time(totalTime: Double, progressWidth: CGFloat) -> CMTime {
        let percentage = max(0, min(1, (location.x + progressWidth / 2) / progressWidth))
        return CMTime(seconds: totalTime * percentage, preferredTimescale: CMTimeScale(1000))
    }
}
