import APIClient
import ComponentLibrary
import ComposableArchitecture
import EventBusClient
import FeatureBrandedAlert
import FeatureClipList
import FeatureHooksModels
import FeatureHooksPost
import FeaturePlaylistDetail
import FeatureToasts
import Localization
import NavigationRouterClient
import StatsigClient
import SwiftUI
import UIKit
import Utilities

// swiftlint:disable file_length

@Reducer
public struct Library {
    @Reducer(state: .equatable)
    public enum Destination {
        case brandedAlert(BrandedAlert)
    }

    @ObservableState
    public struct State: Equatable {
        public enum ListFilter: CaseIterable {
            case all
            case songs

            var title: String {
                switch self {
                case .all: L10n.FeatureCatalog.filterAllTitle
                case .songs: L10n.FeatureCatalog.filterSongsTitle
                }
            }
        }

        public enum MenuFilter {
            case none
            case `public`
            case `private`
            case liked

            var title: String {
                switch self {
                case .none: L10n.FeatureCatalog.filterTitle
                case .public: L10n.FeatureCatalog.filterPublicTitle
                case .private: L10n.FeatureCatalog.filterPrivateTitle
                case .liked: L10n.FeatureCatalog.filterLikedTitle
                }
            }

            var subtitle: String {
                switch self {
                case .none: L10n.FeatureCatalog.filterAllSongs
                case .public: L10n.FeatureCatalog.filterPublicOption
                case .private: L10n.FeatureCatalog.filterPrivateOption
                case .liked: L10n.FeatureCatalog.filterLikedOption
                }
            }

            var icon: Image {
                switch self {
                case .none: Image.Icon.musicNote
                case .public: Image.Icon.globe
                case .private: Image.Icon.lockV1
                case .liked: Image.Icon.thumbsUp
                }
            }

            static var allCases: [MenuFilter] {
                [.none, .public, .private, .liked]
            }
        }

        @Presents var destination: Destination.State?

        @Shared var me: Me
        @Shared(.inMemory(.readyNewGensCount)) var readyNewGensCount: Int = 0
        @ObservationStateIgnored @ObservedBox var clipList: ClipList.State
        var selectedListFilter: ListFilter = .all
        @ObservationStateIgnored @ObservedBox var brandedAlertState = BrandedAlert.State()
        @ObservationStateIgnored @ObservedBox var filterState = FilterOptions.State(options: State.MenuFilter.allCases,
                                                                                    selection: .none)
        var loadState: LoadState { clipList.loadState }
        var recentAction: Action.Undoable?
        var shouldShowNewClips: Bool = false
        var shouldScrollToTop: Bool = false
        var shouldShowDeprecateScenesBanner: Bool {
            FeatureFlag.legacy.showDeprecateScenesBanner &&
                !hasSeenDeprecateScenesBanner
        }

        var selectedMenuFilter: MenuFilter {
            filterState.selection
        }

        @Shared(.appStorage(.hasSeenDeprecateScenesBanner)) var hasSeenDeprecateScenesBanner: Bool = false

        @ObservationStateIgnored @ObservedBox
        var myHooksInitialState: HooksGridConfig.InitialState = .none
        @ObservationStateIgnored @ObservedBox
        var likedHooksInitialState: HooksGridConfig.InitialState = .none
        @ObservationStateIgnored @ObservedBox
        var myHooksPendingHooks: [HooksGridConfig.PendingHook] = []

        public init(me: Shared<Me>, onLoadedViewAppearAlert _: BrandedAlertStyle = .noAlert) {
            self._me = me
            self.clipList = .init(me: me, isLibraryScreen: true, context: SessionContext(source: .library()))
        }
    }

    public enum Action {
        case back
        case getFeed
        case onLoadedViewAppear
        case clipList(ClipList.Action)
        case searchTapped
        case hooksTapped
        case likedSongsTapped
        case playlistsTapped
        case brandedAlertAction(BrandedAlert.Action)
        case filterAction(FilterOptions.Action)
        case task
        case updateClip(Clip)
        case deleteClip(Clip)
        case toastEvent(ToastClient.ToastEvent)
        case hookEvent(EventBusClient.HookEvent)
        case hooksPostClientEvent(FeatureHooksPost.HooksPostClient.HooksPostClientEvent)
        case clipEvent(EventBusClient.ClipEvent)
        case undo(Undoable)
        case showTooltip(Tooltip)
        case silentRefresh
        case showNewClips
        case didShowNewClips
        case scrollToTop
        case didScrollToTop
        case didPullToRefresh
        case dismissDeprecateScenesBanner
        case destination(PresentationAction<Destination.Action>)
        case `internal`(Internal)

        public enum Undoable: Equatable {
            case deleteClip(Clip)
        }

        public enum Internal {
            case prefetchMyHooks
            case prefetchMyHooksResponse(Result<[Hook], Error>)
            case prefetchLikedHooks
            case prefetchLikedHooksResponse(Result<[Hook], Error>)
            case checkInFlightTasks
        }
    }

    @Dependency(\.apiClientV2) var api
    @Dependency(\.dismiss) var dismiss
    @Dependency(NavigationRouterClient.self) var navigationRouter
    @Dependency(\.eventBus.getOmniplayerChannel) private var getOmniplayerChannel
    @Dependency(\.eventBus.getHookPublisher) private var getHookPublisher
    @Dependency(\.eventBus.getClipPublisher) private var getClipPublisher
    @Dependency(\.toastClient.show) var showToast
    @Dependency(\.toastClient.stream) var toastStream
    @Dependency(\.hooksPostClient) var hooksPostClient

    public init() {}

    public var body: some ReducerOf<Self> {
        Scope(state: \.brandedAlertState, action: \.brandedAlertAction) {
            BrandedAlert()
        }
        Scope(state: \.filterState, action: \.filterAction) {
            FilterOptions()
        }
        Scope(state: \.clipList, action: \.clipList) {
            ClipList()
        }
        .withClipListClient { state in
            let menuFilters: (isPublic: Bool?, isLiked: Bool?) = {
                switch state.selectedMenuFilter {
                case .none: return (nil, nil)
                case .public: return (true, nil)
                case .private: return (false, nil)
                case .liked: return (nil, true)
                }
            }()

            let isScene: Bool? = {
                switch state.selectedListFilter {
                case .all: return nil
                case .songs: return false
                }
            }()

            return .init(getClipsV2: { page in
                // Params: page, isPublic, isLiked, isVideoToSong, isScene, isUploadedAudio, onlyFullSongs
                try await api.getFeedV2(page, menuFilters.isPublic, menuFilters.isLiked, nil, isScene, nil, true)
            })
        }

        Reduce<State, Action> { state, action in
            struct ToastCancellableId: Hashable {}
            struct HookEventCancellableId: Hashable {}
            switch action {
            case .back:
                return .run { _ in await self.dismiss() }

            case .onLoadedViewAppear:
                return .none

            case .getFeed:
                // Always make sure to track the added filters
                // for analytics tracking
                let filters: [ContextSource.LibraryFilter] = {
                    var filters: [ContextSource.LibraryFilter] = []
                    switch state.selectedMenuFilter {
                    case .none: break
                    case .public: filters.append(.public)
                    case .private: filters.append(.private)
                    case .liked: filters.append(.liked)
                    }
                    return filters
                }()
                state.clipList.context = SessionContext(source: .library(filters: filters))
                return .send(.clipList(.loadClips))

            case .searchTapped:
                navigationRouter.send(route: .search(.librarySong))
                return .none

            case .clipList(.delegate(.toastAfter(let toast))):
                showToast(toast)
                return .none

            case .clipList(.internal(.clipsLoadResult(_, .success))):
                return .none

            case let .clipList(.delegate(.clipDeleted(clip, succeed))):
                if succeed {
                    state.recentAction = .deleteClip(clip)
                    let toast = ToastReducer.State.ToastType.success(L10n.FeatureCatalog.songDeleted, position: .bottom, trailingView: .undo)
                    showToast(toast)
                    return .none
                } else {
                    state.recentAction = nil
                    let toast = ToastReducer.State.ToastType.warning(L10n.FeatureCatalog.actionFailed, position: .bottom)
                    showToast(toast)
                    return .none
                }

            case .updateClip(let clip):
                return .send(.clipList(.updateClip(clip)))

            case .deleteClip(let clip):
                return .send(.clipList(.deleteClip(clip)))

            case let .undo(action):
                switch action {
                case let .deleteClip(clip):
                    return .send(.clipList(.undoDeleteClip(clip)))
                }

            case .task:
                struct HooksPostClientEventObserver: Hashable {}

                var effects: [Effect<Action>] = [
                    .send(.getFeed),
                    .stream(toastStream(), send: Action.toastEvent, cancellableId: ToastCancellableId()),
                ]

                if FeatureFlag.hooks.isFeedEnabled {
                    effects.append(.send(.internal(.prefetchMyHooks)))
                    effects.append(.send(.internal(.prefetchLikedHooks)))
                    effects.append(.subscribe(getHookPublisher(), send: Action.hookEvent, cancellableId: HookEventCancellableId()))
                    effects.append(.subscribe(getClipPublisher(), send: Action.clipEvent))
                    effects.append(.stream(
                        hooksPostClient.events(),
                        send: Action.hooksPostClientEvent,
                        cancellableId: HooksPostClientEventObserver()
                    ))
                    effects.append(.send(.internal(.checkInFlightTasks)))
                }

                return .merge(effects)

            case let .toastEvent(event):
                switch event {
                case .undo:
                    guard case let .deleteClip(clip) = state.recentAction else { return .none }
                    state.recentAction = nil
                    return .send(.clipList(.undoDeleteClip(clip)))

                default: break
                }
                return .none

            case let .hookEvent(event):
                return handleHookEvent(event, state: &state)

            case let .clipEvent(event):
                return handleClipEvent(event, state: &state)

            case .filterAction(.delegate(.filterSelected)):
                return .send(.getFeed)

            case .hooksTapped:
                // Extract hooks and pending hooks from initial state, then reconstruct with current pending
                let config: HooksGridConfig = {
                    switch state.myHooksInitialState {
                    case .prefetched(let hooks, _, let hasMore, let pageSize):
                        return .init(
                            me: state.$me,
                            showBackButton: false,
                            initialState: .prefetched(
                                hooks: hooks,
                                pendingHooks: state.myHooksPendingHooks,
                                hasMore: hasMore,
                                pageSize: pageSize
                            ),
                            gridAppearance: .myHooks,
                            emptyState: .createFirstHook,
                            source: .libraryGrid
                        )

                    case .none, .loading:
                        return .myHooks(me: state.$me, initialState: .none)
                    }
                }()
                navigationRouter.send(route: .myHooks(config: config, likedHooksInitialState: state.likedHooksInitialState))
                return .none

            case .likedSongsTapped:
                navigationRouter.send(route: .likedSongs)
                return .none

            case .playlistsTapped:
                navigationRouter.send(route: .playlists)
                return .none

            case .showTooltip(let tooltip):
                state.selectedListFilter = .songs
                return .run { send in
                    try? await Task.sleep(for: .seconds(0.5))
                    await send(.clipList(.showTooltip(tooltip)))
                }

            case .clipList(.hideTooltip):
                return .none

            case .silentRefresh:
                // Refresh the current feed without resetting filters
                return .send(.getFeed)

            case .showNewClips:
                // Show all full clips after new clips are generated
                state.selectedListFilter = .all
//                state.selectedMenuFilter = .none
                state.shouldShowNewClips = true
                return .send(.getFeed)

            case .didShowNewClips:
                // Resets this property so we can always scroll to the top
                // after generating and showing new clips from EditClipCoordinator
                state.shouldShowNewClips = false
                return .none

            case .scrollToTop:
                state.shouldScrollToTop = true
                return .none

            case .didScrollToTop:
                state.shouldScrollToTop = false
                return .none

            case .didPullToRefresh:
                state.$readyNewGensCount.withLock { $0 = 0 }
                return .send(.getFeed)

            case .dismissDeprecateScenesBanner:
                state.$hasSeenDeprecateScenesBanner.withLock { $0 = true }
                return .none

            case let .hooksPostClientEvent(.statusChanged(_, status, thumbnailImage, videoUploadId)):
                return handleHooksPostClientEvent(status: status, thumbnailImage: thumbnailImage, videoUploadId: videoUploadId, state: &state)

            case .hooksPostClientEvent(.cancelled):
                // Clear pending hook when hook post is cancelled
                state.myHooksPendingHooks.removeAll(where: { $0.hook.id == "pending-hooks-post" })
                return .none

            case .hooksPostClientEvent:
                return .none

            case .internal(let internalAction):
                return handleInternalAction(internalAction, state: &state)

            case .clipList /* , .delegate */, .destination, .brandedAlertAction, .toastEvent, .filterAction:
                // Catch-all
                return .none
            }
        }
        .ifLet(\.$destination, action: \.destination)
    }
}

private extension Library {
    func handleInternalAction(
        _ action: Action.Internal,
        state: inout State
    ) -> Effect<Action> {
        let hooksPageSize = 20

        switch action {
        case .prefetchMyHooks:
            guard FeatureFlag.hooks.isFeedEnabled else { return .none }

            return .run { send in
                await send(.internal(.prefetchMyHooksResponse(
                    await Result(catching: {
                        let fetchedHooks = try await api.getUserCreatedHooks(0, hooksPageSize)
                        let hooksInGrid = fetchedHooks.userDisplayableHooks
                        return hooksInGrid
                    })
                )))
            }

        case .prefetchMyHooksResponse(let result):
            guard FeatureFlag.hooks.isFeedEnabled else { return .none }

            switch result {
            case .failure:
                // Silently fail prefetch - user will fetch if they navigate
                return .none

            case .success(let hooks):
                state.myHooksInitialState = .prefetched(
                    hooks: hooks,
                    pendingHooks: state.myHooksPendingHooks,
                    hasMore: hooks.count >= hooksPageSize,
                    pageSize: hooksPageSize
                )
                return .none
            }

        case .prefetchLikedHooks:
            guard FeatureFlag.hooks.isFeedEnabled else { return .none }

            return .run { send in
                await send(.internal(.prefetchLikedHooksResponse(
                    await Result(catching: {
                        try await api.getUserLikedHooks(0, hooksPageSize)
                    })
                )))
            }

        case .prefetchLikedHooksResponse(let result):
            guard FeatureFlag.hooks.isFeedEnabled else { return .none }

            switch result {
            case .failure:
                return .none

            case .success(let hooks):
                state.likedHooksInitialState = .prefetched(
                    hooks: hooks,
                    pendingHooks: [],
                    hasMore: hooks.count >= hooksPageSize,
                    pageSize: hooksPageSize
                )
                return .none
            }

        case .checkInFlightTasks:
            return .run { send in
                if let latest = await hooksPostClient.fetchMostRecentTask() {
                    await send(.hooksPostClientEvent(.statusChanged(
                        taskId: latest.id,
                        status: latest.status,
                        thumbnailImage: latest.thumbnailImage,
                        videoUploadId: latest.uploadId
                    )))
                }
            }
        }
    }

    func handleHookEvent(
        _ event: EventBusClient.HookEvent,
        state: inout State
    ) -> Effect<Action> {
        switch event {
        case .triggeredHookCreation(let hook):
            switch state.myHooksInitialState {
            case .none:
                state.myHooksInitialState = .prefetched(
                    hooks: [hook],
                    pendingHooks: state.myHooksPendingHooks,
                    hasMore: true,
                    pageSize: 20
                )

            case .prefetched(let existingHooks, _, let hasMore, let pageSize):
                var updatedHooks = [hook]
                updatedHooks.append(contentsOf: existingHooks.filter { $0.id != hook.id })
                state.myHooksInitialState = .prefetched(
                    hooks: updatedHooks,
                    pendingHooks: state.myHooksPendingHooks,
                    hasMore: hasMore,
                    pageSize: pageSize
                )

            case .loading:
                break
            }
            return .none

        case .hookCreated(let hook):
            state.myHooksPendingHooks.removeAll(where: { $0.hook.id == "pending-hooks-post" })

            switch state.myHooksInitialState {
            case .none:
                state.myHooksInitialState = .prefetched(
                    hooks: [hook],
                    pendingHooks: state.myHooksPendingHooks,
                    hasMore: true,
                    pageSize: 20
                )

            case .prefetched(let existingHooks, _, let hasMore, let pageSize):
                var updatedHooks = existingHooks.map { existingHook in
                    existingHook.id == hook.id ? hook : existingHook
                }
                if !existingHooks.contains(where: { $0.id == hook.id }) {
                    updatedHooks = [hook] + updatedHooks
                }
                state.myHooksInitialState = .prefetched(
                    hooks: updatedHooks,
                    pendingHooks: state.myHooksPendingHooks,
                    hasMore: hasMore,
                    pageSize: pageSize
                )

            case .loading:
                break
            }
            return .none

        case .hookDeleted(hookId: let id):
            state.myHooksPendingHooks.removeAll(where: { $0.hook.id == "pending-hooks-post" })
            switch state.myHooksInitialState {
            case .prefetched(let hooks, let pendingHooks, let hasMore, let pageSize):
                let updatedHooks = hooks.filter { $0.id != id }
                state.myHooksInitialState = .prefetched(
                    hooks: updatedHooks,
                    pendingHooks: pendingHooks,
                    hasMore: hasMore,
                    pageSize: pageSize
                )
            default:
                break
            }

            switch state.likedHooksInitialState {
            case .prefetched(let hooks, let pendingHooks, let hasMore, let pageSize):
                let updatedHooks = hooks.filter { $0.id != id }
                state.likedHooksInitialState = .prefetched(
                    hooks: updatedHooks,
                    pendingHooks: pendingHooks,
                    hasMore: hasMore,
                    pageSize: pageSize
                )
            default:
                break
            }

            return .none

        case .hookLiked(let hook):
            switch state.likedHooksInitialState {
            case .none:
                state.likedHooksInitialState = .prefetched(
                    hooks: [hook],
                    pendingHooks: [],
                    hasMore: true,
                    pageSize: 20
                )

            case .prefetched(let existingHooks, _, let hasMore, let pageSize):
                if !existingHooks.contains(where: { $0.id == hook.id }) {
                    state.likedHooksInitialState = .prefetched(
                        hooks: [hook] + existingHooks,
                        pendingHooks: [],
                        hasMore: hasMore,
                        pageSize: pageSize
                    )
                }

            case .loading:
                break
            }
            return .none

        case .hookUnliked(let hookId):
            switch state.likedHooksInitialState {
            case .prefetched(let existingHooks, _, let hasMore, let pageSize):
                let updatedHooks = existingHooks.filter { $0.id != hookId }
                state.likedHooksInitialState = .prefetched(
                    hooks: updatedHooks,
                    pendingHooks: [],
                    hasMore: hasMore,
                    pageSize: pageSize
                )

            default:
                break
            }
            return .none

        default:
            return .none
        }
    }

    func handleHooksPostClientEvent(
        status: FeatureHooksPost.HooksPostClientStatus,
        thumbnailImage: UIImage?,
        videoUploadId: String?,
        state: inout State
    ) -> Effect<Action> {
        switch status {
        case .uploadVideoFailed, .createHookFailed, .hookProcessingFailed, .hookFailedModeration:
            // Remove pending hook on failures
            state.myHooksPendingHooks.removeAll(where: { $0.hook.id == "pending-hooks-post" })
            return .none

        default:
            // Create or update pending hook with current status
            let pendingHook = status.toPendingHook(videoUploadId: videoUploadId)
            let pending = HooksGridConfig.PendingHook(hook: pendingHook, thumbnail: thumbnailImage)
            if let idx = state.myHooksPendingHooks.firstIndex(where: { $0.hook.id == pending.hook.id }) {
                state.myHooksPendingHooks[idx] = pending
            } else {
                state.myHooksPendingHooks.insert(pending, at: 0)
            }
            return .none
        }
    }

    func handleClipEvent(
        _ event: EventBusClient.ClipEvent,
        state: inout State
    ) -> Effect<Action> {
        switch event {
        case .updateClip(let updatedClip):
            switch state.myHooksInitialState {
            case .none:
                return .none

            case .prefetched(let existingHooks, _, let hasMore, let pageSize):
                let updatedHooks = existingHooks.map { hook -> Hook in
                    var updatedHook = hook
                    if updatedHook.clip?.id == updatedClip.id {
                        updatedHook.clip = updatedClip
                    }
                    return updatedHook
                }
                state.myHooksInitialState = .prefetched(
                    hooks: updatedHooks,
                    pendingHooks: [],
                    hasMore: hasMore,
                    pageSize: pageSize
                )
                return .none

            case .loading:
                return .none
            }

        case .removeClip(let clip):
            let allHooks: [Hook] = {
                var hooks: [Hook] = []

                hooks.append(contentsOf: state.myHooksPendingHooks.map(\.hook))

                if case .prefetched(let myHooks, _, _, _) = state.myHooksInitialState {
                    hooks.append(contentsOf: myHooks)
                }

                if case .prefetched(let likedHooks, _, _, _) = state.likedHooksInitialState {
                    hooks.append(contentsOf: likedHooks)
                }

                return hooks
            }()

            let effects = allHooks
                .filter { $0.clip?.id == clip.id }
                .map { hook in Effect<Action>.send(.hookEvent(.hookDeleted(hookId: hook.id))) }

            return effects.isEmpty ? .none : .concatenate(effects)

        default:
            return .none
        }
    }
}

public struct LibraryScreen: View {
    @Bindable var store: StoreOf<Library>
    @Namespace private var scrollSpace

    @Environment(\.colorScheme) var colorScheme

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

    public var body: some View {
        Group {
            switch store.loadState {
            case .loading:
                LoadingView()

            case .loaded:
                loadedView
                    .onAppear {
                        store.send(.onLoadedViewAppear)
                    }

            case .failed(let message):
                FailedView(
                    title: L10n.FeatureCatalog.errorTitle,
                    message: message,
                    buttonTitle: L10n.FeatureCatalog.retry,
                    action: { store.send(.getFeed) }
                )
            }
        }
        .background(Color.SemanticV1.backgroundPrimary)
        .stableRefreshable {
            await store.send(.didPullToRefresh).finish()
        }
        .overlay(alignment: .top) {
            if #available(iOS 26.0, *) {
                LinearGradient(colors: [Color.SemanticV1.backgroundPrimary,
                                        Color.SemanticV1.backgroundPrimary.opacity(0.75),
                                        Color.SemanticV1.backgroundPrimary.opacity(0)], startPoint: .top, endPoint: .bottom)
                    .frame(height: 140)
                    .ignoresSafeArea()
                    .allowsHitTesting(false)
            }
        }
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                ToolbarButton(.search, background: Color.SemanticV1.backgroundQuaternary) {
                    store.send(.searchTapped)
                }
            }
        }
        .toolbarColorScheme(colorScheme)
        .task { store.send(.task) }
        .overlay {
            if let store = store.scope(state: \.destination?.brandedAlert, action: \.destination.brandedAlert) {
                BrandedAlertView(store)
            }
        }
    }

    private var loadedView: some View {
        ScrollViewReader { proxy in
            List {
                Section(header: header) {
                    if store.clipList.clips.isEmpty {
                        emptyView
                            .padding(.top, UIScreen.height / 4.0)
                            .listRowBackground(Color.clear)
                    } else {
                        ClipListContent(store: store.scope(state: \.clipList, action: \.clipList)) { _, item in
                            item
                        }
                    }
                }
                .id(scrollSpace)
                .listRowSpacing(16.0)
                .listSectionSpacing(.zero)
                .listRowInsets(.init(top: 0, leading: 16, bottom: 0, trailing: 16))
            }
            .listStyle(.grouped)
            .scrollContentBackground(.hidden)
            .disablePressDelay()
            .onChange(of: store.shouldShowNewClips) { _, _ in
                withAnimation {
                    proxy.scrollTo(scrollSpace, anchor: .bottom)
                }
                store.send(.didShowNewClips)
            }
            .onChange(of: store.shouldScrollToTop) { _, _ in
                withAnimation {
                    proxy.scrollTo(scrollSpace, anchor: .bottom)
                }
                store.send(.didScrollToTop)
            }
            .overlay {
                if !store.clipList.clips.isEmpty && store.clipList.tooltipToShow != nil {
                    tooltipTapSafeArea
                }
            }
            .animation(.easeInOut(duration: 0.3), value: store.shouldShowDeprecateScenesBanner)
        }
    }

    /*
     This allows the user to interact with the tooltip
     without accidentally playing the song behind it.
     The offset is to allow tapping (...) to open Song Actions
     right away. This saves the user an extra click when trying
     to follow our instructions.

     Note that the user can't actually interact with the tooltip
     since it's not on the top-most layer in the view.
     */
    @ViewBuilder
    private var tooltipTapSafeArea: some View {
        Color.clear
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .zIndex(.infinity)
            .allowsHitTesting(true)
            .frame(width: UIScreen.width * 0.8)
            .offset(x: -UIScreen.width * 0.2)
            .contentShape(.rect)
            .onTapGesture {
                store.send(.clipList(.didDismissTooltip))
            }
    }

    private var emptyView: some View {
        VStack(spacing: 8) {
            Text(L10n.FeatureCatalog.emptyTitle)
                .typographyV1(.headline4)
                .multilineTextAlignment(.center)
                .foregroundColor(Color.SemanticV1.textPrimary)

            Text(L10n.FeatureCatalog.emptyMessage)
                .foregroundStyle(Color.SemanticV1.textBrand)
                .multilineTextAlignment(.center)
                .typographyV1(.body1)
        }
        .padding(.horizontal, 32)
        .listRowSeparator(.hidden)
        .frame(maxWidth: .infinity)
    }

    @ViewBuilder
    private var header: some View {
        if FeatureFlag.hooks.isFeedEnabled {
            LibraryScreenHeaderV2(store: store)
        } else {
            LibraryScreenHeader(store: store)
        }
    }
}
