import APIClient
import ComponentLibrary
import ComposableArchitecture
import FeatureClipDetail
import FeatureComments
import FeatureOmniPlayer
import FeatureProfile
import Foundation
import Localization
import StatsigClient
import SwiftUI
import Utilities

// Full-screen scrollable feed of Hooks
public struct HooksFeedView: View {
    @Bindable var store: StoreOf<HooksFeedReducer>
    @State private var dragOffset: CGFloat = 0
    @State private var didOmniPlayerAppear: Bool = false
    @State private var containerDragOffset: CGFloat = 0
    @State private var hasScrolledWhileFocused: Bool = false
    @Environment(\.safeAreaInsets) private var safeAreaInsets
    @Environment(\.scenePhase) private var scenePhase

    let maxDragOffset: CGFloat = UIScreen.main.bounds.height

    // Threshold for when to start showing and hiding the Profile screen
    // that comes from the right side of the screen
    let dragToShowProfileThreshold: CGFloat = 10
    let dragToHideProfileThreshold: CGFloat = 50

    // OmniPlayer drag offset when it's state is available
    private var omniPlayerOffset: CGFloat {
        if didOmniPlayerAppear == false {
            // Pushed to the bottom of the screen with a positive offset
            return maxDragOffset
        } else {
            return min(dragOffset, maxDragOffset)
        }
    }

    public init(store: StoreOf<HooksFeedReducer>) {
        self.store = store
    }

    private var disableTouchesOnChrome: Bool {
        store.isSwipingToShowProfile || store.isSwipingToHideProfile || store.isShowingOmniPlayer || !store.isFocused
    }

    // Full screen, minus the tab bar height and safe areas
    private var hooksFullHeight: CGFloat {
        UIScreen.height - CustomBottomBarConstants.tabBarHeight - safeAreaInsets.bottom
    }

    private var baseView: some View {
        HooksFeedViewControllerWrapper(store: store)
            .frame(height: hooksFullHeight)
            .background(Color.black)
    }

    private var baseViewWithSheets: some View {
        baseView
            .sheet(item: $store.scope(state: \.destination?.comments, action: \.destination.comments)) { comments in
                CommentsSheetView(store: comments)
                    .presentationCornerRadius(25, conditional: true)
                    .preferredColorScheme(.dark)
            }
            .sheet(item: $store.scope(state: \.destination?.addToPlaylist, action: \.destination.addToPlaylist)) { store in
                SelectPlaylistScreen(store: store)
                    .presentationCornerRadius(25, conditional: true)
                    .preferredColorScheme(.dark)
            }
    }

    private var configuredView: some View {
        baseViewWithSheets
            .background(Color.black.clipShape(.rect(cornerRadius: 30)))
            .allowsHitTesting(!disableTouchesOnChrome)
            .overlay {
                omniPlayerContainer
            }
            .environment(\.colorScheme, .dark)
    }

    public var body: some View {
        configuredView
            .onChange(of: store.isShowingOmniPlayer) { _, newValue in
                handleOmniPlayerStateChanged(newValue)
            }
            .onChange(of: store.isFocused) { _, newValue in
                if newValue {
                    hasScrolledWhileFocused = false
                }
            }
            .onChange(of: store.currentIndex) { _, newIndex in
                if store.isFocused && newIndex > 0 {
                    hasScrolledWhileFocused = true
                }
            }
            .onChange(of: store.didTapProfileInOmniPlayer) { _, newValue in
                handleOmniPlayerStateChanged(newValue, autoPlayHook: false)
            }
            .onChange(of: scenePhase) { _, newPhase in
                switch newPhase {
                case .background:
                    store.send(.appDidBackground)
                case .active:
                    store.send(.appDidForeground)
                case .inactive:
                    store.send(.appDidBecomeInactive)
                @unknown default:
                    break
                }
            }
    }

    let profileContainerAnimationDuration: TimeInterval = 0.2
    let profileContainerAnimation: Animation = .linear(duration: 0.2)

    private func dragToShowProfileGesture(store: StoreOf<HooksFeedReducer>) -> some Gesture {
        DragGesture()
            .onChanged { value in
                // Don't do anything if OmniPlayer is visible
                guard !store.isShowingOmniPlayer else { return }

                let horizontalDistance = abs(value.translation.width)
                let verticalDistance = abs(value.translation.height)

                // Only start if the first gesture has no vertical movement
                guard horizontalDistance > verticalDistance,
                      verticalDistance < 5 || store.isSwipingToShowProfile
                else {
                    // If they're already swiping to show the profile, cancel the gesture
                    if store.isSwipingToShowProfile {
                        store.send(.swipeToShowProfile(.cancel))
                    }
                    return
                }

                // Check if they drag over the threshold to the left
                guard value.translation.width < -dragToShowProfileThreshold else {
                    if store.isSwipingToShowProfile {
                        store.send(.swipeToShowProfile(.cancel))
                    }
                    return
                }

                if !store.isSwipingToShowProfile {
                    store.send(.swipeToShowProfile(.start))
                }
                containerDragOffset = max(value.translation.width, -UIScreen.width)
            }
            .onEnded { value in
                // Only complete if they dragged over the threshold to the left
                guard value.translation.width < -dragToShowProfileThreshold,
                      value.velocity.width < -dragToShowProfileThreshold,
                      store.isSwipingToShowProfile
                else { return }
                withAnimation(profileContainerAnimation) {
                    containerDragOffset = -UIScreen.width
                }
                store.send(.swipeToShowProfile(.finish))
            }
    }

    private func draggableProfileScreen(store: StoreOf<HooksFeedReducer>, profileStore: StoreOf<PublicProfileV1>) -> some View {
        PublicProfileScreenV1(store: profileStore)
            .navigationBarHidden(true)
            .offset(x: max(UIScreen.width + containerDragOffset, 0))
            .overlay(alignment: .topLeading) {
                ToolbarButton(.back, background: Material.ultraThin) {
                    withAnimation(profileContainerAnimation) {
                        containerDragOffset = 0
                    }
                    DispatchQueue.main.asyncAfter(deadline: .now() + profileContainerAnimationDuration) {
                        store.send(.dismissProfile)
                    }
                }
                .opacity(containerDragOffset == -UIScreen.width ? 1 : 0)
                .padding(.leading, 12)
            }
            .gesture(
                DragGesture()
                    .onChanged { value in
                        guard value.translation.width > dragToHideProfileThreshold else {
                            if store.isSwipingToHideProfile {
                                store.send(.swipeToHideProfile(.cancel))
                            }
                            return
                        }
                        if !store.isSwipingToHideProfile {
                            store.send(.swipeToHideProfile(.start))
                        }
                        containerDragOffset = -UIScreen.width + value.translation.width
                    }
                    .onEnded { value in
                        // If dragging to the right with velocity, dismiss
                        if value.translation.width > dragToHideProfileThreshold, value.velocity.width > dragToHideProfileThreshold {
                            withAnimation(profileContainerAnimation) {
                                containerDragOffset = 0
                            }
                            DispatchQueue.main.asyncAfter(deadline: .now() + profileContainerAnimationDuration) {
                                store.send(.swipeToHideProfile(.finish))
                            }
                        } else {
                            // Snap back to visible position
                            withAnimation(profileContainerAnimation) {
                                containerDragOffset = -UIScreen.width
                            }
                            DispatchQueue.main.asyncAfter(deadline: .now() + profileContainerAnimationDuration) {
                                store.send(.swipeToHideProfile(.cancel))
                            }
                        }
                    }
            )
    }

    private func handleDragChanged(_ translation: CGSize, velocity _: CGSize) {
        // When at the top, don't allow dragging up so the user can scroll down
        guard translation.height > 0 else { return }
        dragOffset = translation.height
    }

    private func handleDragEnded(_ translation: CGSize, velocity: CGSize) {
        guard translation.height > 0 else { return }

        let shouldDismiss = velocity.height > 100 || translation.height > UIScreen.height * 0.2

        if shouldDismiss {
            store.send(.didSwipeDownOmniPlayerToDismiss)
            handleOmniPlayerClosed(autoPlayHook: true)
        } else {
            withAnimation(.easeInOut(duration: store.omniPlayerAnimationDuration)) {
                dragOffset = 0
            }
            return
        }
    }

    private func handleOmniPlayerStateChanged(
        _ shouldAppear: Bool,
        autoPlayHook: Bool = true
    ) {
        // When opening for the second time
        if shouldAppear, didOmniPlayerAppear == false {
            handleOmniPlayerAppeared()
        } else if shouldAppear == false, didOmniPlayerAppear == true {
            // When tapping close to dismiss
            handleOmniPlayerClosed(autoPlayHook: autoPlayHook)
        }
    }

    private func handleOmniPlayerAppeared() {
        didOmniPlayerAppear = true
    }

    private func handleOmniPlayerClosed(autoPlayHook: Bool = true) {
        didOmniPlayerAppear = false
        DispatchQueue.main.asyncAfter(deadline: .now() + store.omniPlayerAnimationDuration) {
            store.send(.teardownOmniPlayer(autoPlayHook: autoPlayHook))
            dragOffset = 0
        }
    }

    @ViewBuilder
    private var omniPlayerContainer: some View {
        if let expandedState = store.scope(state: \.expandedState, action: \.omniPlayer) {
            ExpandedPlayerView(
                store: expandedState,
                isVisible: true,
                dragOffsetWhileAtTop: { _ in }
            )
            .clipShape(RoundedRectangle(cornerRadius: 16))
            .ignoresSafeArea()
            .offset(y: omniPlayerOffset)
            .opacity(didOmniPlayerAppear ? 1 : 0)
            .animation(.spring(duration: 0.35), value: omniPlayerOffset)
            .onAppear {
                handleOmniPlayerAppeared()
            }
            .gesture(
                DragGesture()
                    .onChanged { value in
                        handleDragChanged(value.translation, velocity: value.velocity)
                    }
                    .onEnded { value in
                        handleDragEnded(value.translation, velocity: value.velocity)
                    }
            )
        }
    }
}

extension TypographyV1 {
    static let createHookButton: TypographyV1 = .init(
        name: "Create Hook Button",
        size: 14,
        style: .body,
        weight: .ppNeueMontrealMedium, kerning: 0.28,
        lineHeight: 30
    )
}
