import APIClient
import ComponentLibrary
import ComposableArchitecture
import FeatureCatalog
import FeatureDiscover
import FeatureDiscoverSectionDetail
import FeatureHooksFeed
import FeatureHooksGrid
import FeatureMyHooks
import FeaturePaywall
import FeaturePlaylistDetail
import FeatureProfile
import FeatureRemix
import FeatureSearch
import FeatureSettingsV2
import FeatureSocial
import FeatureTopUp
import Foundation
import SwiftUI
import TabBarUtilities
import Utilities

@Reducer
public struct NavigationStackCoordinator {
    @ObservableState
    public struct State: Equatable {
        @Reducer(state: .equatable)
        public enum Screen {
            // General + deeplinks
            case profile(PublicProfileV1)
            case playlistDetail(PlaylistDetail)
            // Discover sections
            case trendingPlaylistSection(Trending)
            case playlistListSection(FeatureDiscover.Playlists)
            // Song library
            case likedSongs(LikedSongs)
            case likedPlaylists(LikedPlaylists)
            case playlists(FeatureCatalog.Playlists)
            // Hooks
            case myHooks(MyHooksReducer)
            case userHooks(UserHooksGridReducer)
            case clipHooks(ClipHooksGridReducer)
            case hooksContextualFeed(HooksContextualFeedReducer)
            // Social
            case creatorsToFollow(CreatorsToFollow)
            case followers(FollowList)
            case following(FollowList)
            // Account
            case settings(SettingsV2)
            case account(Account)
            case subscriptions(PaywallV1)
            case subscriptionsV2(PaywallV2)
            case topUp(TopUp)
            case webView(WebViewReducer)
            case appearance(Appearance)
            // Search
            case search(Search)
            // Remix
            case remixesList(RemixesList)
            // Notifications
            case notifications(Notifications)
        }

        /*
         Helpers to determine navigation direction and how and when to resume playback
         - didPush: When a new screen is pushed onto the stack (push)
         - didPop: When a screen is popped from the stack (back button or popToRoot)
         - willPop: When a screen is about to be popped from the stack (swipe to dismiss)
         */
        public enum NavigationDirectionType {
            case didPush
            case didPop
            case willPop
        }

        public var path: StackState<Screen.State> = .init()

        @Shared(.inMemory(.selectedTab)) var selectedTab: TabBarTab = .defaultSelection
        @Shared(.inMemory(.isOnHooksFeed)) var isOnHooksFeed: Bool = false
        @Shared(.inMemory(.isHooksFeedFocused)) var isHooksFeedFocused: Bool = false

        public init() {}
    }

    public enum Action {
        case path(StackActionOf<State.Screen>)
        case backButtonTapped
        case push(screen: State.Screen.State)
        case popToRoot
        case delegate(Delegate)

        public enum Delegate {
            case showHooksCreate
        }
    }

    public init() {}

    public var body: some ReducerOf<Self> {
        Reduce<State, Action> { state, action in
            switch action {
            case .backButtonTapped:
                guard !state.path.isEmpty else {
                    return .none
                }
                state.path.removeLast()
                return updateHooksFeedStateOnNavigationChange(state: &state, action: action, navigationType: .didPop)

            case .push(let screen):
                state.path.append(screen)
                return updateHooksFeedStateOnNavigationChange(state: &state, action: action, navigationType: .didPush)

            case .popToRoot:
                state.path = .init()
                return .none

            // If we're leaving a Hooks Contextual Feed,
            // send the `exitDetachedFeed` action to detach from HooksPlayerClient
            // without relying on HookContextualFeed's lifecycle
            case .path(.popFrom(id: let id)):
                guard state.path.ids.contains(id) else {
                    return .none
                }
                if case .hooksContextualFeed = state.path[id: id] {
                    // We only need to recycle the players if we're leaving the contextual feed
                    // and not just popping back to the Hooks tab
                    state.$isOnHooksFeed.withLock { $0 = false }
                    return recycleHooksFeedPlayers()
                } else {
                    // For any other screen being popped, check if we're returning to root hooks tab
                    return updateHooksFeedStateOnNavigationChange(state: &state, action: action, navigationType: .willPop)
                }

            case .path:
                return .none

            case .delegate:
                return .none
            }
        }
        .forEach(\.path, action: \.path)
    }

    private func recycleHooksFeedPlayers() -> Effect<Action> {
        return .run { _ in
            @Dependency(\.hooksPlayerClient) var playerClient
            playerClient.exitContextualFeed()
        }
    }

    private func updateHooksFeedStateOnNavigationChange(
        state: inout State,
        action _: Action,
        navigationType: NavigationStackCoordinator.State.NavigationDirectionType
    ) -> Effect<Action> {
        let isContextualFeed = { () -> Bool in
            if case .hooksContextualFeed = state.path.last { return true }
            return false
        }()

        // If we call this method before dismissing,
        // like when receiving a `popFrom` action from `NavigationStack`,
        // we need to take that into account when updating the tab bar state.
        // This is because `StackReducer` only gives us the `popFrom` action
        // before updating the path but `backButtonTapped` manually pops the last item.
        var pathCount = state.path.ids.count
        if navigationType == .willPop {
            pathCount -= 1
        }

        // Set `isOnHooksFeed` if we're on the Hooks tab without
        // any nested feeds or in a contextual feed, on any tab
        let isAtHooksTabRoot = pathCount == 0 && state.selectedTab == .hooks
        let isShowingHooksFeed = isAtHooksTabRoot || isContextualFeed

        // Pause music playback when navigating to a focused hooks feed, if we haven't done so already
        let shouldPauseMusic = isShowingHooksFeed && !state.isOnHooksFeed && state.isHooksFeedFocused
        if shouldPauseMusic {
            @Dependency(\.omniplayerClient) var omniplayerClient
            omniplayerClient.pauseCurrentClip()
        }

        state.$isOnHooksFeed.withLock { $0 = isShowingHooksFeed }

        guard isShowingHooksFeed, navigationType != .didPush else { return .none }

        @Dependency(\.hooksPlayerClient) var hooksPlayerClient
        hooksPlayerClient.playCurrentHook(.resume(.feed))

        return .none
    }
}

public struct NavigationStackView<RootView: View>: View {
    @Bindable var store: StoreOf<NavigationStackCoordinator>
    let root: () -> RootView
    private let transitionNamespace: Namespace.ID

    public init(
        store: StoreOf<NavigationStackCoordinator>,
        @ViewBuilder root: @escaping () -> RootView,
        transitionNamespace: Namespace.ID
    ) {
        self.store = store
        self.root = root
        self.transitionNamespace = transitionNamespace
    }

    public var body: some View {
        NavigationStack(
            path: $store.scope(state: \.path, action: \.path),
            root: root,
            destination: { screen in
                Group {
                    switch screen.case {
                    case .profile(let profileStore):
                        PublicProfileScreenV1(store: profileStore)

                    case .playlistDetail(let playlistDetailV1Store):
                        PlaylistDetailScreen(store: playlistDetailV1Store)

                    case .trendingPlaylistSection(let trendingStore):
                        TrendingScreen(store: trendingStore)

                    case .playlistListSection(let playlistsStore):
                        FeatureDiscover.PlaylistsScreen(store: playlistsStore)

                    case .myHooks(let hooksStore):
                        MyHooksScreen(store: hooksStore)

                    case .userHooks(let userHooksStore):
                        UserHooksGridScreen(store: userHooksStore)

                    case .clipHooks(let clipHooksStore):
                        ClipHooksGridScreen(store: clipHooksStore)

                    case .hooksContextualFeed(let hooksFeedStore):
                        HooksContextualFeed(store: hooksFeedStore)

                    case .likedSongs(let likedSongsStore):
                        LikedSongsScreen(store: likedSongsStore)

                    case .likedPlaylists(let likedPlaylistsStore):
                        LikedPlaylistsScreen(store: likedPlaylistsStore)

                    case .playlists(let playlistsStore):
                        FeatureCatalog.PlaylistsScreen(store: playlistsStore)

                    case .creatorsToFollow(let creatorsToFollowStore):
                        CreatorsToFollowScreen(store: creatorsToFollowStore)

                    case .followers(let followersStore):
                        FollowListScreen(store: followersStore)

                    case .following(let followingStore):
                        FollowListScreen(store: followingStore)

                    case .settings(let settingsV2Store):
                        SettingsV2Screen(store: settingsV2Store)

                    case .account(let accountStore):
                        AccountScreen(store: accountStore)

                    case .subscriptions(let paywallStore):
                        PaywallScreenV1(store: paywallStore)
                        
                    case .subscriptionsV2(let paywallStore):
                        PaywallScreenV2(store: paywallStore)

                    case .topUp(let topUpStore):
                        TopUpScreen(store: topUpStore)

                    case .webView(let webViewStore):
                        WebViewScreen(store: webViewStore)

                    case .search(let searchStore):
                        SearchScreen(store: searchStore)

                    case .remixesList(let remixStore):
                        RemixesListScreen(store: remixStore)

                    case .appearance(let appearanceStore):
                        AppearanceScreen(store: appearanceStore)

                    case .notifications(let notificationsStore):
                        NotificationsScreen(store: notificationsStore)
                    }
                }
                .navigationBarBackButtonHidden(true)
                .toolbar {
                    if case .search = screen.case {
                        ToolbarItem {}
                    } else {
                        ToolbarItem(placement: .topBarLeading) {
                            if case let .playlistDetail(playlistDetailV1Store) = screen.case {
                                ToolbarButton(.back, background: Material.ultraThin, colorScheme: playlistDetailV1Store.averageColors.colorScheme) {
                                    playlistDetailV1Store.send(.dismiss)
                                }
                            } else {
                                ToolbarButton(.back, background: Material.ultraThin) {
                                    store.send(.backButtonTapped)
                                }
                            }
                        }
                    }
                }
            }
        )
    }
}
