import APIClient
import ComponentLibrary
import ComposableArchitecture
import EventBusClient
import FeatureClipList
import FeatureHooksModels
import FeatureInAppNotifications
import FeaturePlayer
import FeaturePlaylistDetail
import FeatureShare
import FeatureSocial
import FeatureToasts
import InAppNotificationClient
import Localization
import NavigationRouterClient
import OrderedCollections
import PlayerClient
import StatsigClient
import SwiftData
import SwiftUI
import UserNotificationsClient
import Utilities

// swiftlint:disable file_length

@Reducer
public struct Notifications {
    @Reducer(state: .equatable)
    public enum Destination {
        case invite(Share)
        case creatorsToFollow(CreatorsToFollow)
        case profile(PublicProfileV1)
        case playlistDetail(PlaylistDetail)
    }

    @ObservableState
    public struct State: Equatable {
        @Presents public var destination: Destination.State?
        @ObservationStateIgnored @ObservedBox var inAppNotificationsList = InAppNotificationList.State()

        @Shared var me: Me
        var firstLoad = true

        @Shared(.inMemory(.promoCodeUrl)) var promoCodeUrl: String?
        @Shared(.inMemory(.promoCodeUrlRedemptionsLeft)) var promoCodeRedemptionsLeft: Int?

        var isContactSyncEnabled: Bool {
            FeatureFlag.legacy.contactSync
        }

        @Shared(.appStorage(.showInviteFriends)) var showInviteFriends: Bool = true

        @Shared(.inMemory(.inAppNotificationsState)) var inAppNotificationsState: NotificationLoadingState = .loading
        @Shared(.inMemory(.inAppNotificationMap)) var inAppNotificationMap: OrderedNotificationMap = .defaultValue
        @Shared(.inMemory(.hasUnreadNotifications)) var hasUnreadNotifications: Bool = false
        @Shared(.appStorage(.dismissedAnnouncements)) var dismissedAnnouncements: [String] = []
        @Shared(.appStorage(.hasDismissedPushNotificationBanner)) var hasDismissedPushNotificationBanner: Bool = false
        @Shared(.appStorage(.dismissedNotificationsBannerId)) var dismissedNotificationsBannerId: String = ""

        var loadingItemId: String?
        var followHandleInProgress: String?
        var isLastActionGoingToNotifications: Bool = false
        var notificationAllowStatus: PushNotificationAllowStatus?

        @ObservationStateIgnored @ObservedBox var inviteFriendsBanner = InviteFriendsBanner.State()

        /// The notification banner config fetched the Statsig Dynamic Config `notifications-banner-config`
        var notificationsBannerConfig: NotificationsBannerConfig?

        public enum Banner {
            case inviteFriends
            case announcement
            case turnOnPushNotifications
            case notificationsBanner
        }

        var banner: Banner? {
            if let notificationAllowStatus, notificationAllowStatus != .allowed, !hasDismissedPushNotificationBanner {
                return .turnOnPushNotifications
            } else {
                if lastAnnouncement != nil {
                    return .announcement
                } else if showInviteFriends {
                    return .inviteFriends
                } else {
                    return nil
                }
            }
        }

        var shouldShowNotificationsBanner: Bool {
            FeatureFlag.hooks.isFeedEnabled && notificationsBannerConfig != nil && dismissedNotificationsBannerId != notificationsBannerConfig?.id
        }

        var lastAnnouncement: NotificationItem?

        var shouldScrollToTop: Bool = false

        public init(me: Shared<Me>) {
            self._me = me
        }
    }

    public enum Action {
        case destination(PresentationAction<Destination.Action>)
        case task
        case onDisappear
        case back
        case refreshPushNotificationStatus
        case dismissPushNotificationBanner
        case attemptToAllowUserNotifications
        case getLocalNotifications
        case getRemoteNotifications
        case setNotificationsRead
        case clearLastSeenActionIsPushEnable
        case `internal`(Internal)
        case followTapped(_ handle: String, _ isFollowing: Bool)
        case userTapped(MiniProfile)
        case clipOrPlaylistTapped(MiniClipOrPlaylist, notificationId: String)
        case redirectV2(InAppNotificationItem.RedirectStyle, notificationId: String)
        case inviteFriendsBanner(InviteFriendsBanner.Action)
        case inAppNotificationsList(InAppNotificationList.Action)
        case dismissAnnouncement
        case creatorsToFollowTapped
        case scrollToTop
        case didScrollToTop
        case loadNotificationsBannerConfig
        case dismissNotificationsBannerTapped
        case delegate(Delegate)

        public enum Internal {
            case openNotificationSettings
            case notificationsResponse(Result<UserNotification, Error>)
            case followResult(String, Bool, Result<Void, Error>)
            case setNotificationsReadResponse(Result<Void, Error>)
            case clipResponse(Result<Clip, Error>, _ clipID: String, _ expandOmniPlayer: Bool, _ expandComments: Bool, _ replyToCommentID: String?, _ notificationId: String)
            case playlistResponse(Result<Playlist, Error>)
            case hookResponse(Result<Hook, Error>, _ hookID: String, _ navigationOptions: HookNavigationOptions?, _ notificationId: String)
            case userNotificationsClientStatusUpdate(UserNotificationClient.NotificationAllowStatus)
            case clearAppBadgeCount
        }

        public enum Delegate {
            case triggerPushNotificationRequest
        }
    }

    @Dependency(\.apiClientV2) private var api
    @Dependency(\.dismiss) private var dismiss
    @Dependency(APIClient.self) private var apiClient
    @Dependency(StatsigClient.self) private var statsigClient
    @Dependency(PlayerClient.self) var playerClient
    @Dependency(NavigationRouterClient.self) var navigationRouter
    @Dependency(\.inAppNotificationClient) var inAppNotificationClient
    @Dependency(UserNotificationClient.self) var userNotifications
    @Dependency(\.eventBus.getOmniplayerChannel) private var getOmniplayerChannel
    @Dependency(\.toastClient.show) var showToast

    public init() {}

    public var body: some ReducerOf<Self> {
        Scope(state: \.inviteFriendsBanner, action: \.inviteFriendsBanner) {
            InviteFriendsBanner()
        }
        Scope(state: \.inAppNotificationsList, action: \.inAppNotificationsList) {
            InAppNotificationList()
        }
        Reduce<State, Action> { state, action in
            struct UserNotificationsClientCancellable: Hashable {}

            state.isLastActionGoingToNotifications = false

            switch action {
            case .task:
                return .merge(
                    .stream(
                        userNotifications.notificationStatusChannel(),
                        send: { Action.internal(.userNotificationsClientStatusUpdate($0)) },
                        cancellableId: UserNotificationsClientCancellable()
                    ),
                    .send(.internal(.clearAppBadgeCount)),
                    .send(.getLocalNotifications),
                    .send(.loadNotificationsBannerConfig)
                )

            case .onDisappear:
                inAppNotificationClient.clearOrderedNotificationMap()
                return .none

            case .clearLastSeenActionIsPushEnable:
                /*
                    technically already handled above
                    state.isLastActionGoingToNotifications = false
                 */
                return .none

            case .attemptToAllowUserNotifications:
                switch state.notificationAllowStatus {
                case .none:
                    assertionFailure("we should have loaded this ")
                    return .none

                case .allowed:
                    return .none

                case .notAllowed:
                    state.isLastActionGoingToNotifications = true
                    userNotifications.openNotificationSettings()
                    return .none

                case .unknown:
                    state.isLastActionGoingToNotifications = true
                    return .send(.delegate(.triggerPushNotificationRequest))
                }

            case .refreshPushNotificationStatus:
                userNotifications.refreshPushNotificationAllowStatus()
                return .none

            case .internal(.userNotificationsClientStatusUpdate(let newStatus)):
                state.notificationAllowStatus = newStatus
                return .none

            case .dismissPushNotificationBanner:
                userNotifications.dismissPushNotificationBanner()
                return .none

            case .inviteFriendsBanner(.delegate(.bannerTapped)):
                state.destination = .invite(.init(.me(state.me.user), me: state.$me))
                return .none

            case .inviteFriendsBanner(.delegate(.dismissTapped)):
                state.$showInviteFriends.withLock { $0 = false }
                return .none

            case .getLocalNotifications:
                inAppNotificationClient.refreshOrderedNotificationMap()
                return .send(.getRemoteNotifications)

            case .getRemoteNotifications:
                inAppNotificationClient.enqueueGetNotifications(before: nil, shouldRefreshInMemoryMap: true)
                return .send(.setNotificationsRead)

            case .redirectV2(let redirect, let notificationId): /* Only handles tapping the notifcation content area */
                switch redirect {
                case .noRedirect:
                    return .none

                case .toClip(let clipID, let options):
                    state.loadingItemId = clipID
                    // Attempt to load as clip
                    let expandOmniplayer = true
                    let openComments = options.contains(.openComments) || options.contains {
                        if case .openCommentsForReplyToComment = $0 { return true }
                        return false
                    }
                    let replyToCommentID = options.compactMap { option in
                        if case .openCommentsForReplyToComment(let commentId) = option {
                            return commentId
                        }
                        return nil
                    }.first

                    return .run { send in
                        await send(.internal(.clipResponse(
                            Result(catching: { try await api.getClip(clipID) }),
                            clipID, expandOmniplayer, openComments, replyToCommentID, notificationId
                        )))
                    }

                case .toHook(let hookID, let options):
                    state.loadingItemId = hookID

                    let navigationOptions: HookNavigationOptions? = {
                        let shouldOpenComments = options.contains(.openComments)

                        let replyToCommentID = options.compactMap { option in
                            if case .openCommentsForReplyToComment(let commentId) = option {
                                return commentId
                            }
                            return nil
                        }.first

                        if shouldOpenComments || replyToCommentID != nil {
                            if let commentId = replyToCommentID {
                                return .openComments(.replyToComment(commentId))
                            } else {
                                return .openComments(nil)
                            }
                        } else {
                            return nil
                        }
                    }()

                    // Load hook and navigate to contextual feed
                    return .run { send in
                        await send(.internal(.hookResponse(
                            Result(catching: { try await api.getHookById(hookId: hookID) }),
                            hookID, navigationOptions, notificationId
                        )))
                    }

                case .toExternalURL:
                    return .none

                case .toPlaylist(let playlistID):
                    state.loadingItemId = playlistID
                    // Attempt to load as playlist
                    return .run { send in
                        await send(.internal(.clipResponse(
                            Result(catching: { try await api.getClip(playlistID) }),
                            playlistID, false, false, nil, notificationId
                        )))
                    }

                case .toProfile(let handle):
                    navigationRouter.sendIfNavV2(route: .profile(handle), else: {
                        state.destination = .profile(.init(me: state.$me, handle: handle))
                    })
                    return .none

                case .toProfileList:
                    return .none
                }

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

            case .setNotificationsRead:
                inAppNotificationClient.enqueueSetAllNotificationsRead()
                return .none

            case .internal(.clearAppBadgeCount):
                return .run { _ in
                    do {
                        try await UNUserNotificationCenter.current().setBadgeCount(0)

                        // Clear the backend app badge count to ensure Braze sends
                        // us the correct count for push notifications
                        try await api.clearAppBadgeCount()
                    } catch {
                        log.telemetry.error(error, message: "Failed to clear app badge count!")
                    }
                }

            case .internal(.clipResponse(.success(let clip), _, _, let openComments, let replyToCommentID, let notificationId)):
                state.loadingItemId = nil
                let context = SessionContext(
                    source: .notification(notificationId: notificationId),
                    navigationIntent: openComments ? .openComments(replyToCommentId: replyToCommentID) : nil
                )
                getOmniplayerChannel().queue(.present(clip, autoPlay: false, context: context))
                return .none

            case .internal(.clipResponse(.failure, let clipOrPlaylistId, _, _, _, _)):
                // Attempt to load as playlist
                return .run { send in
                    await send(.internal(.playlistResponse(
                        Result(catching: { try await api.getPlaylistById(0, clipOrPlaylistId, nil, nil) })
                    )))
                }

            case .internal(.playlistResponse(.success(let playlist))):
                state.loadingItemId = nil
                navigationRouter.sendIfNavV2(route: .playlist(playlist), else: {
                    state.destination = .playlistDetail(.init(me: state.$me, source: .playlist(playlist)))
                })
                return .none

            case .internal(.playlistResponse(.failure(let error))):
                state.loadingItemId = nil
                log.telemetry.error(error)
                let toast = ToastReducer.State.ToastType.warning(L10n.FeatureProfile.clipPlaylistLoadError, position: .bottom)
                showToast(toast)
                return .none

            case .internal(.hookResponse(.success(let hook), _, let navigationOptions, _)): // TODO (BA): Add notificationId
                state.loadingItemId = nil
                // Navigate to hooks contextual feed with the single hook
                navigationRouter.send(route: .hooksContextualFeed([hook], 0, navigationOptions, .notification))
                return .none

            case .internal(.hookResponse(.failure(let error), _, _, _)):
                state.loadingItemId = nil
                log.telemetry.error(error)
                let toast = ToastReducer.State.ToastType.warning(L10n.FeatureHooks.failedToLoadHook, position: .bottom)
                showToast(toast)
                return .none

            case .followTapped(let handle, let isFollowing):
                state.followHandleInProgress = handle
                return .run { [handle = handle, unfollow = isFollowing] send in
                    await send(.internal(.followResult(handle, isFollowing, Result(catching: { try await apiClient.profileFollow(handle, unfollow) }))))
                }

            case .internal(.followResult(let handle, let isFollowing, .success)):
                inAppNotificationClient.updateFollowOnNotification(handle: handle, isFollowing: !isFollowing)
                state.followHandleInProgress = nil
                return .none

            case let .internal(.followResult(_, _, .failure(error))):
                state.followHandleInProgress = nil
                log.telemetry.error(error)
                return .none

            case .back:
                return .run { _ in await self.dismiss() }

            case let .destination(.presented(.profile(.delegate(.didUpdateFollow(profile, isFollowing))))):
                inAppNotificationClient.updateFollowOnNotification(handle: profile.handle, isFollowing: isFollowing)
                return .none

            case .userTapped(let miniProfile):
                navigationRouter.sendIfNavV2(route: .profile(miniProfile.handle), else: {
                    state.destination = .profile(.init(me: state.$me, handle: miniProfile.handle))
                })
                return .none

            case .creatorsToFollowTapped:
                navigationRouter.sendIfNavV2(route: .creatorsToFollow, else: {
                    state.destination = .creatorsToFollow(.init(me: state.$me))
                })
                return .none

            case .clipOrPlaylistTapped(let miniClipOrPlaylist, let notificationId):
                state.loadingItemId = miniClipOrPlaylist.id
                // Attempt to load as clip
                return .run { send in
                    await send(.internal(.clipResponse(
                        Result(catching: { try await api.getClip(miniClipOrPlaylist.id) }),
                        miniClipOrPlaylist.id, true, false, nil, notificationId
                    )))
                }

            case .inAppNotificationsList(.delegate(let delegateAction)):
                switch delegateAction {
                case .followTapped(let handle, let isFollowing):
                    return .send(.followTapped(handle, isFollowing))
                case .userTapped(let profileV1):
                    return .send(.userTapped(profileV1))
                case .clipOrPlaylistTapped(let clipOrPlaylist, let notificationId):
                    return .send(.clipOrPlaylistTapped(clipOrPlaylist, notificationId: notificationId))
                case .redirectV2(let redirectStyleV2, let notificationId):
                    return .send(.redirectV2(redirectStyleV2, notificationId: notificationId))
                }

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

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

            case .loadNotificationsBannerConfig:
                state.notificationsBannerConfig = statsigClient.getDynamicConfig(
                    .notificationsBanner,
                    as: NotificationsBannerConfig.self
                )
                return .none

            case .dismissNotificationsBannerTapped:
                if let bannerId = state.notificationsBannerConfig?.id {
                    state.$dismissedNotificationsBannerId.withLock { $0 = bannerId }
                }
                return .none

            case .inAppNotificationsList,
                 .dismissAnnouncement,
                 .internal,
                 .destination,
                 .delegate:
                // Catch-all
                return .none
            }
        }
        .ifLet(\.$destination, action: \.destination)

        Analytics()
    }
}

public struct NotificationsScreen: View {
    @Bindable var store: StoreOf<Notifications>
    @Namespace var namespace
    @Namespace var scrollSpace

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

    public var body: some View {
        mainListArea
            .alert(L10n.FeatureNotifications.success, isPresented: alertBinding) {
                Button(L10n.FeatureNotifications.ok, role: .cancel) {}
            } message: {
                Text(L10n.FeatureNotifications.alertsAreNowEnabled)
            }
            .sheet(item: $store.scope(state: \.destination?.invite, action: \.destination.invite)) { store in
                ShareScreen(store: store)
            }
            .navigationDestination(item: $store.scope(state: \.destination?.creatorsToFollow, action: \.destination.creatorsToFollow)) { store in
                CreatorsToFollowScreen(store: store)
            }
            .fullScreenCover(item: $store.scope(state: \.destination?.profile, action: \.destination.profile)) { store in
                profileFullScreenCover(store: store)
            }
            .fullScreenCover(item: $store.scope(state: \.destination?.playlistDetail, action: \.destination.playlistDetail)) { store in
                playlistDetailFullScreenCover(store: store)
            }
            .background(Color.SemanticV1.backgroundPrimary)
            .navigationBarBackground(Color.SemanticV1.backgroundPrimary)
            .navigationBarBackButtonHidden()
            .navigationBarTitleDisplayMode(.inline)
            .task {
                store.send(.task)
            }
            .onDisappear {
                store.send(.onDisappear)
            }
            .toolbar {
                toolbarContent
            }
    }
}

private extension NotificationsScreen {
    private var alertBinding: Binding<Bool> {
        Binding<Bool>(
            get: {
                store.isLastActionGoingToNotifications && store.notificationAllowStatus == .allowed
            },
            set: { _ in
                store.send(.clearLastSeenActionIsPushEnable)
            }
        )
    }

    @ViewBuilder
    private func profileFullScreenCover(store: StoreOf<PublicProfileV1>) -> some View {
        NavigationStack {
            PublicProfileScreenV1(store: store)
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        ToolbarButton(.close, background: Color.SemanticV1.backgroundQuaternary) {
                            store.send(.dismiss)
                        }
                    }
                }
        }
    }

    @ViewBuilder
    private func playlistDetailFullScreenCover(store: StoreOf<PlaylistDetail>) -> some View {
        NavigationStack {
            PlaylistDetailScreen(store: store)
                .toolbar {
                    ToolbarItem(placement: .topBarLeading) {
                        ToolbarButton(.close, background: Material.ultraThin, colorScheme: store.averageColors.colorScheme) { store.send(.dismiss) }
                    }
                }
        }
    }
}

private extension NotificationsScreen {
    @ToolbarContentBuilder
    private var toolbarContent: some ToolbarContent {
        ToolbarItem(placement: .navigationBarTrailing) {
            ToolbarButton(.follow, background: Color.SemanticV1.backgroundQuaternary) {
                store.send(.creatorsToFollowTapped)
            }
        }

        if FeatureFlag.hooks.isFeedEnabled {
            GlasslessToolbarItem(placement: .principal) {
                ToolbarTitle(L10n.FeatureRoot.notifications)
            }
        } else {
            GlasslessToolbarItem(placement: .navigationBarLeading) {
                Text(L10n.FeatureRoot.notifications)
                    .typographyV1(.headline3)
                    .foregroundStyle(Color.SemanticV1.textPrimary)
                    .fixedSize()
                    .offset(x: -5, y: 2) // align with content
            }
        }
    }
}

private extension NotificationsScreen {
    @ViewBuilder
    var mainListArea: some View {
        ZStack {
            switch store.inAppNotificationsState {
            case .cleared:
                LoadingView()

            case .loading,
                 .loaded,
                 .failed:
                resultItemsView
                    .opacity(store.inAppNotificationsState.isLoading ? 0.5 : 1.0)
            }
        }
    }

    @ViewBuilder
    var resultItemsView: some View {
        let noteMap = store.inAppNotificationMap
        ZStack {
            if noteMap.isEmpty {
                NoResultsView(
                    title: L10n.FeatureProfile.emptyNotificationsTitle,
                    message: L10n.FeatureProfile.emptyNotificationsMessage,
                    style: .v2
                )
            } else {
                loadedView(noteMap)
            }
        }
    }

    func announcementBannerView(message: String) -> some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(L10n.FeatureProfile.newFeature)
                .padding(.vertical, 4)
                .padding(.horizontal, 8)
                .foregroundStyle(Color.SemanticV1.textPrimary)
                .typographyV1(.bodySmall)
                .environment(\.colorScheme, .dark)
                .background(
                    Image.Assets.newFeatureBackground
                        .resizable()
                        .scaledToFill()
                )
                .clipShape(.rect(cornerRadius: 4))

            let markdown = attributedString(
                for: message,
                typography: .body2,
                foregroundColor: .SemanticV1.textPrimary,
                emphasizedColor: .SemanticV1.textPrimary,
                emphasizedTypography: .body2.neueMontrealMedium()
            )

            Text(markdown)
                .foregroundStyle(Color.SemanticV1.textPrimary)
                .multilineTextAlignment(.leading)
                .frame(maxWidth: .infinity, alignment: .topLeading)
                .fixedSize(horizontal: false, vertical: true)
        }
        .padding(.leading, 16)
        .padding(.trailing, 44)
        .padding(.vertical, 16)
        .background(
            RoundedRectangle(cornerRadius: 12)
                .fill(Color.SemanticV1.backgroundTertiary)
        )
        .overlay(alignment: .topTrailing) {
            Button {
                store.send(.dismissAnnouncement)
            } label: {
                Image.Icon.close
                    .foregroundColor(Color.SemanticV1.textPrimary)
                    .frame(width: 40, height: 40)
                    .clipShape(Rectangle())
            }
            .buttonStyle(.plain)
        }
    }

    @MainActor
    func loadedView(_ notifications: OrderedNotificationMap) -> some View {
        ScrollViewReader { proxy in
            List {
                Group {
                    Section {
                        bannerSection
                    }
                    .listSectionSeparator(.hidden)

                    Section {
                        notificationsBannerSection
                    }
                    .listSectionSeparator(.hidden)
                    .id(scrollSpace)

                    notificationsListSection(notifications)
                }
                .listRowBackground(Color.clear)
                .listRowSeparator(.hidden)
                .listRowInsets(.init())
            }
            .onChange(of: store.shouldScrollToTop) { _, _ in
                withAnimation {
                    proxy.scrollTo(scrollSpace, anchor: .bottom)
                }
                store.send(.didScrollToTop)
            }
        }
        .scrollContentBackground(.hidden)
        .listSectionSpacing(32)
        .listRowSpacing(0)
        .listStyle(.grouped)
        .contentMargins(.horizontal, 12, for: .scrollContent)
        .stableRefreshable {
            Task { await store.send(.getLocalNotifications).finish() }
        }
    }

    @ViewBuilder
    private var bannerSection: some View {
        if store.banner == .inviteFriends {
            InviteFriendsBannerView(
                store: store.scope(state: \.inviteFriendsBanner, action: \.inviteFriendsBanner)
            )
        }

        if store.banner == .announcement,
           let announcement = store.lastAnnouncement, !store.dismissedAnnouncements.contains(announcement.id),
           let message = announcement.message, !message.isEmpty
        {
            announcementBannerView(message: message)
        }

        if store.banner == .turnOnPushNotifications {
            turnOnNotificationsBanner
        }
    }

    @ViewBuilder
    private var notificationsBannerSection: some View {
        if store.shouldShowNotificationsBanner,
           let bannerConfig = store.notificationsBannerConfig
        {
            NotificationsBannerView(
                config: bannerConfig,
                onDismiss: {
                    store.send(.dismissNotificationsBannerTapped)
                }
            )
        }
    }

    @ViewBuilder
    private func notificationsListSection(_ notifications: OrderedNotificationMap) -> some View {
        if !notifications.isEmpty {
            InAppNotificationListView(
                store: store.scope(
                    state: \.inAppNotificationsList,
                    action: \.inAppNotificationsList
                )
            )
        }
    }

    @ViewBuilder
    var turnOnNotificationsBanner: some View {
        VStack(alignment: .leading, spacing: .zero) {
            HStack {
                Text(L10n.FeatureNotifications.notifications)
                    .typographyV1(.caption4.neueMontrealMedium())
                    .foregroundStyle(.white)
                    .padding(.top, 2.0)
                    .padding(.bottom, 4.0)
                    .padding(.horizontal, 6.0)
                    .background {
                        Color.SemanticV1.textLink
                    }
                    .clipShape(.rect(cornerRadius: 8.0))

                Spacer()

                Image.Icon.close
                    .contentShape(.rect)
                    .onTapGesture {
                        store.send(.dismissPushNotificationBanner)
                    }
            }
            .padding(.bottom, 8.0)

            Text(L10n.FeatureNotifications.turnOnYourPushCopy)
                .typographyV1(.caption2.neueMontrealMedium())
                .padding(.bottom, 8.0)

            Text(L10n.FeatureNotifications.thingsMoveFastCopy)
                .typographyV1(.caption2.neueMontrealRegular())
                .padding(.bottom, 4.0)
        }
        .foregroundStyle(Color.SemanticV1.textPrimary)
        .padding(16.0)
        .background {
            Color.SemanticV1.backgroundTertiary
        }
        .clipShape(.rect(cornerRadius: 16.0))
        .padding(.trailing, 8.0)
        .onTapGesture {
            store.send(.attemptToAllowUserNotifications)
        }
    }
}
