import APIClient
import AVFoundation
import ComponentLibrary
import ComposableArchitecture
import FeatureBrandedAlert
import Foundation
import Localization
import StatsigClient
import SunoModelClient
import SwiftUI
import Utilities

@Reducer
public struct ClipVideoReducer {
    @Dependency(VideoPreviewClient.self) var previewClient
    @Dependency(APIClient.self) private var apiClient
    @Dependency(\.continuousClock) private var clock

    @ObservableState
    public struct State: Equatable {
        public var video: URL
        public var player: AVPlayer?
        public var asset: AVAsset?
        public var caption: String = ""
        public var captionFocused: Bool = false
        @Shared(.appStorage(.hasSeenContextPrompt)) var hasSeenContextPrompt = false

        // Used to read modelMajorVersion in Analytics
        @Shared(.inMemory(.selectedSunoModel)) var selectedSunoModel: SunoModelMetaData = .modelDefault

        public var uploadId: String?
        public var audio: AudioMode = .existing
        public var isPlaying: Bool = false
        public var isSubmitting: Bool = false
        public var duration: CMTime?
        public var currentTime: CMTime = .zero
        public var formattedElapsedTime: String {
            let formatter = DateComponentsFormatter()
            formatter.unitsStyle = .positional
            formatter.allowedUnits = [.minute, .second]
            formatter.zeroFormattingBehavior = .pad
            return formatter.string(from: currentTime.seconds) ?? "00:00"
        }

        public var isVideoInputEnabled: Bool {
            FeatureFlag.legacy.videoInput
        }

        public let isMuted = true

        public init(reusePrompt: Prompt? = nil, video: URL) {
            self.video = video
        }
    }

    public enum AudioMode: Equatable {
        case existing
        case replace
    }

    public enum Action: BindableAction {
        case binding(BindingAction<State>)

        case togglePlayPause
        case didTapCreate
        case didTapDismiss
        case showAddContextAlert
        case hCaptchaTokenGenerationFailed

        case updateTime(CMTime)
        case loadVideo
        case loadVideoResponse(Result<(AVAsset, AVPlayer, CMTime), Error>)
        case videoReachedEnd

        case setAudioMode(AudioMode)

        case delegate(Delegate)

        public enum Delegate {
            case showAlert(BrandedAlertStyle)
        }
    }

    public var body: some ReducerOf<Self> {
        BindingReducer()
        Reduce { state, action in
            switch action {
            case .loadVideo:
                return .merge(
                    .run { [
                        // swiftformat:disable trailingCommas
                        video = state.video,
                        isMuted = state.isMuted,
                        cachedFileName = "video_upload_\(state.uploadId ?? "").mp4"
                    ] send in
                        do {
                            let (asset, player) = try await previewClient.loadVideo(video, isMuted, cachedFileName)
                            let duration = try await asset.load(.duration)
                            await send(.loadVideoResponse(.success((asset, player, duration))))
                        } catch {
                            await send(.loadVideoResponse(.failure(error)))
                        }
                    },
                )

            case .loadVideoResponse(.success((let asset, let player, let duration))):
                state.asset = asset
                state.player = player
                state.duration = duration
                return .publisher {
                    previewClient.periodicTime()
                        .map(Action.updateTime)
                }

            case .loadVideoResponse(.failure(let error)):
                log.telemetry.error(error)
                return .none

            case let .updateTime(time):
                state.currentTime = time
                if state.currentTime >= state.duration ?? .zero {
                    return .send(.videoReachedEnd)
                }
                return .none

            case .videoReachedEnd:
                state.isPlaying = false
                return .run { send in
                    previewClient.pause()
                    previewClient.seek(.zero)
                    await send(.updateTime(.zero))
                }

            case .togglePlayPause:
                state.isPlaying.toggle()
                if state.isPlaying {
                    previewClient.play()
                } else {
                    previewClient.pause()
                }
                return .none

            case .didTapDismiss:
                state.isPlaying = false
                previewClient.pause()
                previewClient.teardown()
                return .none

            case .didTapCreate: // Handled in parent
                state.isSubmitting = true
                return .none

            case .hCaptchaTokenGenerationFailed:
                state.isSubmitting = false
                return .none

            case .showAddContextAlert:
                state.$hasSeenContextPrompt.withLock { $0 = true }
                return .send(.delegate(.showAlert(.singleButtonAlert(.preset(.addContext)))))

            case let .setAudioMode(mode):
                state.audio = mode
                return .none

            case .binding, .delegate:
                return .none
            }
        }

        Analytics()
    }
}

struct CreateClipCameraVideoView: View {
    @FocusState private var focused: Bool
    @State private var animateAppear = false

    var namespace: Namespace.ID
    @Bindable var store: StoreOf<ClipVideoReducer>

    var body: some View {
        VStack(spacing: 0) {
            videoPreview
                .clipShape(.rect(cornerRadius: 16))
                .matchedGeometryEffect(id: media, in: namespace)
                .rotationEffect(.degrees(focused ? 11.33 : 0))
                .padding(.horizontal, focused ? 50 : 28)
                .padding(.bottom, focused ? -24 : 24)
                .padding(.top, focused ? 15 : 0)
                .zIndex(focused ? -1 : 0)

            VStack(spacing: 0) {
                TextField("", text: $store.caption, axis: .vertical)
                    .focused($focused)
                    .textFieldStyle(
                        CyclePlaceholderTextFieldStyle(
                            placeholders: [
                                L10n.FeatureCreateClip.mediaCaptionPlaceholder1,
                                L10n.FeatureCreateClip.mediaCaptionPlaceholder2,
                                L10n.FeatureCreateClip.mediaCaptionPlaceholder3,
                                L10n.FeatureCreateClip.mediaCaptionPlaceholder4
                            ],
                            typography: .body3,
                            maxWidth: nil,
                            maxHeight: nil
                        )
                    )
                    .lineLimit(2 ... 3)
                    .typographyV1(.body3)
                    .foregroundStyle(Color.SemanticV1.textPrimary)
                    .clearButton(value: $store.caption)
                    .padding(.horizontal, 16)
                    .padding(.vertical, focused ? 16 : 24)
                    .background(Color.SemanticV1.backgroundSecondary)
                    .cornerRadius(16)
                    .onTapGesture { focused = true }
                    .padding(.bottom, 12)
                    .zIndex(1)

                // Audio mode toggle is currently not being used
                if !store.captionFocused && store.isVideoInputEnabled {
                    HStack(spacing: 16) {
                        HStack(spacing: 8) {
                            RadioButton(isSelected: store.audio == .existing)
                            Text(L10n.FeatureCreateClip.audioModeExisting)
                        }
                        .onTapGesture { store.send(.setAudioMode(.existing)) }

                        HStack(spacing: 8) {
                            RadioButton(isSelected: store.audio == .replace)
                            Text(L10n.FeatureCreateClip.audioModeReplace)
                        }
                        .onTapGesture { store.send(.setAudioMode(.replace)) }
                    }
                    .padding(.horizontal, 16)
                    .padding(.vertical, 20)
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .background(Color.SemanticV1.backgroundSecondary)
                    .cornerRadius(16)
                    .typographyV1(.body2)
                    .padding(.bottom, 48)
                    .transition(.opacity)
                }

                AuraButton(
                    title: L10n.FeatureCreateClip.create,
                    isLoading: store.isSubmitting,
                    auraStyle: .orangeAura,
                    leadingView: { Image.Icon.create },
                    action: {
                        UIImpactFeedbackGenerator(style: .light).impactOccurred()
                        if store.caption.isEmpty, !store.hasSeenContextPrompt {
                            store.send(.showAddContextAlert)
                        } else {
                            focused = false
                            store.send(.didTapCreate)
                        }
                    }
                )
                .padding(.bottom, 12)
            }
            .opacity(animateAppear ? 1 : 0)
            .animation(.default, value: animateAppear)
        }
        .animation(.snappy(duration: 0.3), value: focused)
        .bind($store.captionFocused, to: $focused)
        .padding(12)
        .onAppear { animateAppear = true }
        .onTapGesture { focused = false }
    }

    var videoPreview: some View {
        ZStack {
            VideoPreviewView(player: store.player)
                .frame(maxHeight: .infinity)
                .onAppear {
                    store.send(.loadVideo)
                }
            VStack {
                Spacer()
                HStack {
                    playPauseButton
                    Spacer()
                    elapsedTime
                }
                .padding(.trailing, 16)
                .padding(.leading, 8)
                .padding(.bottom, 12)
            }
        }
    }

    var playPauseButton: some View {
        Button {
            store.send(.togglePlayPause)
        } label: {
            ZStack {
                Image(systemName: "play.fill")
                    .foregroundColor(.SemanticV1.iconPrimary)
                    .opacity(store.isPlaying ? 0 : 1)
                    .animation(.bouncy(extraBounce: 0.3), value: store.isPlaying)

                Image.Icon.pause
                    .foregroundColor(.SemanticV1.iconPrimary)
                    .opacity(store.isPlaying ? 1 : 0)
                    .animation(.bouncy(extraBounce: 0.3), value: store.isPlaying)
            }
            .frame(width: 40, height: 40)
        }
    }

    var elapsedTime: some View {
        Text(store.formattedElapsedTime)
            .foregroundColor(.white)
            .typographyV1(.body2thin.neueMontrealMedium())
    }
}
