import AnalyticsClient
import APIClient
import AVFoundation
import ComponentLibrary
import ComposableArchitecture
import Foundation
import SwiftUI

public struct VideoSongArtFullscreenPreviewPlaybar: View {
    let store: StoreOf<VideoSongArtFullscreenPreviewPlayer>
    let clip: Clip
    @Dependency(AnalyticsClient.self) var analyticsClient
    @State private var progressWidth: Double = 0.0
    @State private var playPauseButtonTransparency = 0.0
    let restartTapped: () -> Void
    let playTapped: () -> Void
    let pauseTapped: () -> Void
    let didStartScrubbing: (VideoSongArtFullscreenPreviewPlayer.State.ScrubState) -> Void
    let didEndScrubbing: (VideoSongArtFullscreenPreviewPlayer.State.ScrubState) -> Void

    public init(
        store: StoreOf<VideoSongArtFullscreenPreviewPlayer>,
        clip: Clip,
        restartTapped: @escaping () -> Void,
        playTapped: @escaping () -> Void,
        pauseTapped: @escaping () -> Void,
        didStartScrubbing: @escaping (VideoSongArtFullscreenPreviewPlayer.State.ScrubState) -> Void,
        didEndScrubbing: @escaping (VideoSongArtFullscreenPreviewPlayer.State.ScrubState) -> Void
    ) {
        self.store = store
        self.clip = clip
        self.restartTapped = restartTapped
        self.playTapped = playTapped
        self.pauseTapped = pauseTapped
        self.didStartScrubbing = didStartScrubbing
        self.didEndScrubbing = didEndScrubbing
    }

    public var body: some View {
        progress
            .padding(.vertical, 6)
            .opacity(clip.id != store.clip.id ? 0 : 1)
            .animation(.easeIn, value: clip.id == store.clip.id)
    }

    private var progress: some View {
        HStack(spacing: 0) {
            restartButton
                .padding(.leading, 4)
            playPauseButton
                .padding(.leading, 6)
                .padding(.trailing, 12)

            Group {
                let totalTime = store.totalTime.isInvalid ? store.fallbackTotalTimeSeconds : store.totalTime.seconds
                let progress = min(totalTime, max(0, store.displayTime.seconds))

                ProgressView(value: progress, total: totalTime)
                    .progressViewStyle(LinearProgressViewStyle(tint: Color.SemanticV1.iconInvert))
                    .background(Capsule().fill(.ultraThinMaterial))
                    .environment(\.colorScheme, .light)
                    .onGeometryChange(for: CGSize.self) { proxy in proxy.size } action: {
                        progressWidth = $0.width
                    }
                    .animation(nil, value: store.isScrubbing)
                    .scaleEffect(
                        x: store.isScrubbing ? 1.01 : 1,
                        y: store.isScrubbing ? 2 : 1
                    )
                    .animation(.default, value: store.isScrubbing)
                    .padding(.vertical, 10)
            }
            .contentShape(.rect)
            .gesture(drag)

            if !store.totalTime.isInvalid {
                Text("\(store.totalTime.seconds.positionalTime)")
                    .typographyV1(.monospace)
                    .foregroundStyle(Color.SemanticV1.textPrimary)
                    .padding(.leading, 16)
            }
        }
    }

    @ViewBuilder
    private var restartButton: some View {
        Button {
            restartTapped()
        } label: {
            Image.Icon.previousTrack
                .resizable()
                .frame(width: 24, height: 24)
                .foregroundColor(.SemanticV1.iconPrimary)
        }
        .frame(width: 30, height: 30)
        .contentShape(.rect)
        .buttonStyle(ScaleButtonStyle(scaleAmount: 0.9))
    }

    @ViewBuilder
    private var playPauseButton: some View {
        let isPlaying = store.timeControlStatus == .playing
        Button {
            switch store.timeControlStatus {
            case .paused:
                playTapped()
            case .waitingToPlayAtSpecifiedRate:
                break
            case .playing:
                pauseTapped()
            @unknown default:
                break
            }
            playPauseButtonTransparency = 0.4
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                playPauseButtonTransparency = 0.0
            }
            UIImpactFeedbackGenerator(style: .light).impactOccurred()
        } label: {
            ZStack {
                Circle()
                    .fill(.ultraThinMaterial)
                    .frame(width: 44, height: 44)
                    .opacity(playPauseButtonTransparency)
                    .environment(\.colorScheme, .light)
                    .animation(.easeInOut(duration: 0.2), value: playPauseButtonTransparency)

                if store.timeControlStatus != .waitingToPlayAtSpecifiedRate {
                    Image.Icon.playFilled
                        .foregroundColor(.SemanticV1.iconPrimary)
                        .scaleEffect(isPlaying ? 0 : 1)
                        .opacity(isPlaying ? 0 : 1)
                        .animation(.bouncy(extraBounce: 0.3), value: isPlaying)

                    Image.Icon.pause
                        .foregroundColor(.SemanticV1.iconPrimary)
                        .scaleEffect(isPlaying ? 1 : 0)
                        .opacity(isPlaying ? 1 : 0)
                        .animation(.bouncy(extraBounce: 0.3), value: isPlaying)
                }
            }
            .frame(width: 40, height: 40)
        }
        .foregroundColor(.primary)
        .disabled(store.timeControlStatus == .waitingToPlayAtSpecifiedRate)
        .overlay {
            ProgressView()
                .progressViewStyle(.circular)
                .opacity(store.timeControlStatus == .waitingToPlayAtSpecifiedRate ? 1 : 0)
        }
    }

    private var drag: some Gesture {
        DragGesture(minimumDistance: 0)
            .onChanged { value in
                let totalTime = store.totalTime.isInvalid ? store.fallbackTotalTimeSeconds : store.totalTime.seconds
                let scrubTime = value.time(totalTime: totalTime, progressWidth: progressWidth)
                didStartScrubbing(.scrubbing(scrubTime))
            }
            .onEnded { value in
                let totalTime = store.totalTime.isInvalid ? store.fallbackTotalTimeSeconds : store.totalTime.seconds
                let newTime = value.time(totalTime: totalTime, progressWidth: progressWidth)
                didEndScrubbing(.complete(newTime))
            }
    }
}

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