import APIClient
import ComponentLibrary
import ComposableArchitecture
import FeatureManagePlaylist
import FeaturePlaylistRow
import FeatureRatingTracker
import FeatureToasts
import Localization
import StatsigClient
import SwiftUI
import Utilities

@Reducer
public struct SelectPlaylist {
    @Reducer(state: .equatable)
    public enum Destination {
        case createPlaylist(CreatePlaylist)
    }

    @ObservableState
    public struct State: Equatable {
        @Presents public var destination: Destination.State?

        var clip: Clip
        var hook: Hook?
        var hooksFeedSource: HooksFeedSource?
        var rows: IdentifiedArrayOf<PlaylistRow.State> = []
        var loadState: LoadState = .loading
        @ObservationStateIgnored @ObservedBox var toastState = ToastReducer.State()
        @ObservationStateIgnored @ObservedBox var ratingTrackerState = RatingTracker.State()
        var isTogglingMembership = false
        var pages = PageState(firstPageIndex: 1)
        var searchBarQuery: String = ""
        var likedSongCount: Int = 0
        var isFirstLoad: Bool = true
        var originalPlaylistCount = 0
        var pendingPlaylistToggle: String?
        var addedPlaylistStack: [Playlist] = []
        var removedPlaylistStack: [Playlist] = []
        var shouldAddToNewPlaylist: Bool = false
        var originalAddedPlaylists: [Playlist] = []
        var originalLikedState: Bool = false
        var likedSongIsAdded: Bool {
            return clip.isLiked
        }

        var likedSongIsToggling = false

        public init(clip: Clip,
                    hook: Hook? = nil,
                    hooksFeedSource: HooksFeedSource? = nil)
        {
            self.clip = clip
            self.hook = hook
            self.hooksFeedSource = hooksFeedSource
        }
    }

    public enum Action: BindableAction {
        case binding(BindingAction<State>)
        case destination(PresentationAction<Destination.Action>)

        case rows(IdentifiedActionOf<PlaylistRow>)
        case onAppear
        case getPlaylists
        case getLikedSongCount
        case togglePlaylistTapped(Playlist)
        case createPlaylistTapped
        case toastAction(ToastReducer.Action)
        case ratingTrackerAction(RatingTracker.Action)
        case getNextPage
        case dismiss
        case `internal`(Internal)

        case toggleLike(Bool)

        public enum Internal {
            case playlistsResponse(_ page: Int, Result<PlaylistsResult, Error>)
            case updateResponse(Playlist, Result<Void, Error>)
            case likeResponse((Bool, Int), Result<Void, Error>)
            case likedSongCountResponse(Result<Int, Error>)
            case showToast(ToastReducer.State.ToastType)
        }
    }

    @Dependency(\.apiClientV2) var api
    @Dependency(APIClient.self) var apiClient
    @Dependency(\.telemetryClient) var telemetry
    @Dependency(\.dismiss) var dismiss
    @Dependency(\.eventBus.sendClipEvent) var sendClipEvent
    @Dependency(\.toastClient.show) var showToast

    public init() {}

    public var body: some ReducerOf<Self> {
        BindingReducer()
        Scope(state: \.toastState, action: \.toastAction) {
            ToastReducer()
        }
        Scope(state: \.ratingTrackerState, action: \.ratingTrackerAction) {
            RatingTracker()
        }
        Reduce { state, action in
            func togglePlaylistMembership(playlist: Playlist, clip: Clip) {
                let isAdded = playlist.contains(clip: clip)

                if isAdded {
                    var playlist = playlist
                    if let index = playlist.clips.firstIndex(where: { $0.id == clip.id }) {
                        playlist.totalResults = max(0, playlist.totalResults - 1)
                        playlist.clips.remove(at: index)
                        state.rows[id: playlist.id] = .init(playlist: playlist, image: .remote(url: playlist.imageUrl, fallbackId: playlist.id))
                    }

                } else {
                    var playlist = playlist
                    playlist.clips.append(state.clip)
                    playlist.totalResults += 1
                    state.rows[id: playlist.id] = .init(playlist: playlist, image: .remote(url: playlist.imageUrl, fallbackId: playlist.id))
                }
            }

            switch action {
            case .onAppear:
                state.originalLikedState = state.clip.isLiked
                return .merge(.send(.getPlaylists), .send(.getLikedSongCount))

            case .getLikedSongCount:
                return .run { send in
                    // getPlaylistById endpoint contains correct liked song count
                    await send(.internal(.likedSongCountResponse(Result(catching: { try await api.getPlaylistById(0, Playlist.liked.id, nil, nil).totalResults }))))
                }

            case .internal(.likedSongCountResponse(.success(let totalResults))):
                state.likedSongCount = totalResults
                return .none

            case .internal(.likedSongCountResponse(.failure(let error))):
                state.likedSongCount -= 1
                log.telemetry.error(error)
                return .none

            case .getPlaylists:
                state.pages.reset()
                state.originalAddedPlaylists = []
                return .send(.getNextPage)

            case .getNextPage:
                guard let nextPage = state.pages.nextIndex() else { return .none }
                let query = state.searchBarQuery.isEmpty ? nil : state.searchBarQuery

                return .run { [clip = state.clip] send in
                    await send(.internal(.playlistsResponse(nextPage, Result(catching: { try await api.getPlaylistsWithClipStatus(nextPage, clip, query, true) }))))
                }

            case .internal(.playlistsResponse(let page, .success(let playlistsResponse))):
                if state.isFirstLoad {
                    state.originalPlaylistCount = playlistsResponse.totalResults
                    state.isFirstLoad = false
                }
                if page == state.pages.firstPageIndex {
                    state.rows = []
                }
                state.originalAddedPlaylists.append(contentsOf: playlistsResponse.playlists.filter { $0.isAdded ?? false })

                state.rows.append(contentsOf: playlistsResponse.playlists.map {
                    PlaylistRow.State(
                        playlist: $0,
                        image: .remote(url: $0.imageUrl, fallbackId: $0.id)
                    )
                })
                // -1 to account for the liked playlist row
                state.pages.update(playlistsResponse.playlists, hasAllResults: playlistsResponse.totalResults == (state.rows.count - 1))
                state.loadState = .loaded
                if let pendingPlaylistId = state.pendingPlaylistToggle,
                   let playlist = playlistsResponse.playlists.first(where: { $0.id == pendingPlaylistId })
                {
                    if state.shouldAddToNewPlaylist {
                        togglePlaylistMembership(playlist: playlist, clip: state.clip)
                        state.shouldAddToNewPlaylist = false
                    }
                    state.pendingPlaylistToggle = nil
                    return .send(.togglePlaylistTapped(playlist))
                }
                return .none

            case .internal(.playlistsResponse(_, .failure(let error))):
                log.telemetry.error(error)
                state.pages.update(error)
                state.loadState = .failed(L10n.FeatureClipDetail.playlistsFailed)
                return .none

            case .togglePlaylistTapped(let playlist):
                let isAdded = playlist.contains(clip: state.clip)
                state.isTogglingMembership = true

                return .run { [clipId = state.clip.id] send in
                    await send(.internal(.updateResponse(playlist, Result(catching: { try await api.updatePlaylistClips(clipId, playlist.id, isAdded, nil) }))))
                }

            case .createPlaylistTapped:
                state.destination = .createPlaylist(.init(shouldAddToPlaylistAfterCreating: true))
                return .none

            case .destination(.presented(.createPlaylist(.internal(.updatePlaylistResponse(.success, _))))):
                return .send(.getPlaylists)

            case .internal(.updateResponse(let playlist, .success)):
                state.isTogglingMembership = false

                return .merge(
                    .send(!playlist.contains(clip: state.clip)
                        ? .toastAction(.show(.success(L10n.FeatureClipDetail.addedToPlaylist(playlist.name), .string(""), position: .bottom)))
                        : .toastAction(.show(.success(L10n.FeatureClipDetail.removedFromPlaylist(playlist.name), .string(""), position: .bottom)))),
                    !playlist.contains(clip: state.clip) ? .send(.ratingTrackerAction(.addToPlaylist)) : .none
                )

            case let .internal(.updateResponse(playlist, .failure(error))):
                togglePlaylistMembership(playlist: playlist, clip: state.clip)
                state.isTogglingMembership = false
                log.telemetry.error(error)
                return .none

            case .rows(.element(id: _, action: .delegate(.selectPlaylist(let playlist)))):
                let isAdded = playlist.contains(clip: state.clip)
                state.isTogglingMembership = true
                togglePlaylistMembership(playlist: playlist, clip: state.clip)
                if !isAdded {
                    if !state.originalAddedPlaylists.contains(playlist) {
                        state.addedPlaylistStack.append(playlist)
                        if let index = state.removedPlaylistStack.firstIndex(of: playlist) {
                            state.removedPlaylistStack.remove(at: index)
                        }
                    }
                } else {
                    if state.originalAddedPlaylists.contains(playlist) {
                        state.removedPlaylistStack.append(playlist)
                        if let index = state.addedPlaylistStack.firstIndex(of: playlist) {
                            state.addedPlaylistStack.remove(at: index)
                        }
                    }
                }

                return .run { [hook = state.hook, source = state.hooksFeedSource, clipId = state.clip.id] send in
                    var recommendationMetadata: HooksRecommendationMetadata?
                    if let hook = hook {
                        recommendationMetadata = HooksRecommendationMetadata(
                            contextType: source?.analyticsContext.contextType,
                            hookId: hook.id,
                            recommendationItemId: hook.recommendationItemId
                        )
                    }
                    await send(.internal(.updateResponse(playlist, Result(catching: { try await api.updatePlaylistClips(clipId, playlist.id, isAdded, recommendationMetadata) }))))
                }

            case .destination(.presented(.createPlaylist(.delegate(.savedPlaylist(let playlist))))):
                state.shouldAddToNewPlaylist = true
                state.pendingPlaylistToggle = playlist.id
                return .send(.getPlaylists)

            case .toggleLike(let liked):
                let originalIsLiked = state.clip.isLiked
                let originalLikedSongCount = state.likedSongCount

                state.likedSongIsToggling = true
                state.clip.isLiked = liked
                if liked {
                    state.likedSongCount += 1
                } else {
                    state.likedSongCount -= 1
                }
                sendClipEvent(.toggledLike(state.clip))
                return .merge(
                    .run { [hook = state.hook, source = state.hooksFeedSource, clip = state.clip] send in
                        var recommendationMetadata: HooksRecommendationMetadata?
                        if let hook = hook {
                            recommendationMetadata = HooksRecommendationMetadata(
                                contextType: source?.analyticsContext.contextType,
                                hookId: hook.id,
                                recommendationItemId: hook.recommendationItemId
                            )
                        }
                        await send(.internal(.likeResponse((originalIsLiked, originalLikedSongCount), .init(catching: { try await api.setReaction(clip, clip.isLiked, clip.isDisliked, recommendationMetadata) }))))
                    }
                )

            case .internal(.likeResponse(_, .success)):
                state.likedSongIsToggling = false
                return .none

            case let .internal(.likeResponse(originals, .failure(error))):
                // Revert to original values
                state.clip.isLiked = originals.0
                state.likedSongCount = originals.1
                state.likedSongIsToggling = false
                sendClipEvent(.toggledLike(state.clip))
                log.telemetry.error(error)
                return .none

            case .internal(.showToast(let toast)):
                showToast(toast)
                return .send(.dismiss)

            case .toastAction:
                // Catch-all
                return .none

            case .destination:
                // Catch-all
                return .none

            case .ratingTrackerAction:
                // Catch-all
                return .none

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

            case .rows:
                // Catch-all
                return .none

            case .binding(\.searchBarQuery):
                // debouce due to pagination (update cliplist with searchbar query)
                return .run { send in
                    await send(.getPlaylists)
                }
                .debounce(id: "playlist-searchbar-query", for: 0.5, scheduler: DispatchQueue.main)

            case .binding:
                return .none
            }
        }
        .ifLet(\.$destination, action: \.destination)
    }
}

public struct SelectPlaylistScreen: View {
    @Bindable var store: StoreOf<SelectPlaylist>
    @State private var isSearchFocused = false
    @State var scrollViewHeight = 0.0
    @Environment(\.colorScheme) var colorScheme
    var doneButtonHeight: CGFloat = 55.0

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

    public var body: some View {
        NavigationStack {
            contentView
                .sheetNavigationBar(
                    leading: { EmptyView() },
                    center: { ToolbarTitle(L10n.FeatureClipDetail.addToPlaylist) },
                    trailing: {
                        ToolbarButton(.close, background: Color.SemanticV1.backgroundQuaternary) {
                            store.send(.dismiss)
                        }
                    }
                )
                .background(Color.SemanticV1.backgroundPrimary, ignoresSafeAreaEdges: .all)
        }
        .sheet(item: $store.scope(state: \.destination?.createPlaylist, action: \.destination.createPlaylist)) { store in
            CreatePlaylistScreen(store: store)
                .presentationDetents([.medium])
                .preferredColorScheme(colorScheme)
        }
        .presentationCornerRadius(40, conditional: true)
        .presentationDragIndicator(.visible)
        .background(Color.SemanticV1.backgroundPrimary)
        .overlay(alignment: .bottom) {
            ToastView(store: store.scope(state: \.toastState, action: \.toastAction))
        }
        .overlay(alignment: .bottom) {
            ZStack(alignment: .bottom) {
                LinearGradient(
                    colors: Color.Gradient.clearToColorLinearGradient(baseColor: Color.SemanticV1.backgroundPrimary),
                    startPoint: .top,
                    endPoint: .bottom
                )
                .frame(height: 200)
                .offset(y: 38)
                .allowsHitTesting(false)

                Button {
                    doneTapped()
                } label: {
                    Text(L10n.FeatureClipDetail.done)
                        .foregroundStyle(Color.SemanticV1.backgroundPrimary)
                        .frame(width: 146, height: 46)
                        .background(Color.SemanticV1.textPrimary)
                        .clipShape(RoundedRectangle(cornerRadius: 50))
                        .padding(.bottom, isSearchFocused ? 16 : 0)
                }
                .buttonStyle(ScaleButtonStyle(scaleAmount: 0.98))
            }
            .ignoresSafeArea(.all, edges: .bottom)
        }

        .onAppear {
            store.send(.onAppear)
        }
    }

    private func doneTapped() {
        let likedChanged = store.clip.isLiked != store.originalLikedState
        let addedNotEmpty = !store.addedPlaylistStack.isEmpty
        let removedNotEmpty = !store.removedPlaylistStack.isEmpty

        let shouldShowGenericToast = (likedChanged && (addedNotEmpty || removedNotEmpty)) || (addedNotEmpty && removedNotEmpty)

        // show generic toast if multiple actions taken (liking, disliking, add/remove playlist)
        if shouldShowGenericToast {
            let successToast = ToastReducer.State.ToastType.success(
                L10n.FeatureClipDetail.changesSaved,
                position: .top
            )
            store.send(.internal(.showToast(successToast)))

        } else if let lastAdded = store.addedPlaylistStack.last {
            let count = store.addedPlaylistStack.count
            let message: String
            switch count {
            case 1:
                message = L10n.FeatureClipDetail.songAddedToPlaylist(lastAdded.name)
            default:
                message = L10n.FeatureClipDetail.songAddedToPlaylists(lastAdded.name, count - 1)
            }

            let successToast = ToastReducer.State.ToastType.successV2(
                message,
                image: RemoteImage(url: lastAdded.imageUrl, fallbackId: lastAdded.id),
                position: .top
            )
            store.send(.internal(.showToast(successToast)))

        } else if let lastRemoved = store.removedPlaylistStack.last {
            let count = store.removedPlaylistStack.count
            let message: String
            switch count {
            case 1:
                message = L10n.FeatureClipDetail.songRemovedFromPlaylist(lastRemoved.name)
            default:
                message = L10n.FeatureClipDetail.songRemovedFromPlaylists(lastRemoved.name, count - 1)
            }

            let successToast = ToastReducer.State.ToastType.successV2(
                message,
                image: RemoteImage(url: lastRemoved.imageUrl, fallbackId: lastRemoved.id),
                position: .top
            )
            store.send(.internal(.showToast(successToast)))

        } else if likedChanged {
            let message = store.originalLikedState
                ? L10n.FeatureClipDetail.songRemovedFromLikes
                : L10n.FeatureClipDetail.addedToLikedSongs

            let successToast = ToastReducer.State.ToastType.success(
                message,
                position: .top
            )
            store.send(.internal(.showToast(successToast)))

        } else {
            store.send(.dismiss)
        }
    }

    @ViewBuilder private var contentView: some View {
        switch store.loadState {
        case .loading:
            LoadingView()
        case .loaded:
            loadedView
        case .failed(let message):
            FailedView(title: L10n.FeatureClipDetail.errorTitle, message: message, buttonTitle: L10n.FeatureClipDetail.retry, action: { store.send(.getPlaylists) })
        }
    }

    @ViewBuilder
    private var loadedView: some View {
        ScrollView(showsIndicators: false) {
            LazyVStack(spacing: 10) {
                if store.originalPlaylistCount > 5 {
                    SearchBar(text: $store.searchBarQuery) { focused in
                        isSearchFocused = focused
                    }
                }

                if store.searchBarQuery.isEmpty {
                    createPlaylistRowV2

                    // likedRow
                }

                resultsStatusIndicator

                ForEach(store.scope(state: \.rows, action: \.rows)) { rowStore in
                    PlaylistRowItem(store: rowStore) {
                        let isAdded = rowStore.playlist.contains(clip: store.clip)
                        addRemoveButton(isAdded, isWorking: store.isTogglingMembership)
                    } onLikedPlaylistTapped: {
                        store.send(.toggleLike(!store.clip.isLiked))
                    }
                }

                if store.pages.hasMore {
                    ProgressView()
                        .progressViewStyle(.circular)
                        .padding()
                        .onAppear {
                            guard !store.pages.loadingNext else { return }
                            store.send(.getNextPage)
                        }
                }
            }
            .padding(.horizontal, 12)
        }
        .readSize {
            scrollViewHeight = $0.height
        }
        .scrollDismissesKeyboard(.interactively)
        .safeAreaInset(edge: .bottom) {
            Spacer()
                .frame(height: doneButtonHeight + (isSearchFocused ? 16 : 0))
        }
    }

    @ViewBuilder
    private var resultsStatusIndicator: some View {
        if store.rows.isEmpty && !store.searchBarQuery.isEmpty {
            Text(L10n.FeatureClipDetail.noResults(store.searchBarQuery))
                .typographyV1(.button.kerning(0.28))
                .foregroundStyle(Color.SemanticV2.foregroundTertiary)
                .multilineTextAlignment(.center)
                // subtract height of done button
                .frame(minHeight: scrollViewHeight - doneButtonHeight)
        } else if store.rows.isEmpty && store.originalPlaylistCount > 0 {
            // only show progress bar if there were any playlists before searching
            ProgressView()
                .progressViewStyle(.circular)
                .foregroundStyle(Color.SemanticV2.foregroundInactive)
                .frame(width: 27, height: 27)
                // subtract height of create+liked row & done button
                .frame(minHeight: scrollViewHeight - (100 + doneButtonHeight))
        }
    }

    struct SearchBar: View {
        @State private var isSearchDebouncing = false
        @Binding var text: String
        @FocusState private var isSearchBarFocused: Bool
        let onFocusChange: (Bool) -> Void

        var body: some View {
            HStack {
                magnifyingGlass

                searchBar

                searchBarTrailingView
            }
            .padding(.vertical, 8)
            .frame(height: 40)
            .background(Color.SemanticV1.backgroundSecondary)
            .cornerRadius(20)
            .onChange(of: text) { oldValue, newValue in
                handleSearchDebounce(oldValue, newValue)
            }
        }

        @ViewBuilder
        private var magnifyingGlass: some View {
            Image.Icon.search
                .renderingMode(.template)
                .frame(width: 16, height: 16)
                .foregroundColor(Color.SemanticV2.foregroundPrimary)
                .padding(.leading, 16)
                .padding(.trailing, 8)
        }

        @ViewBuilder
        private var searchBar: some View {
            TextField(L10n.FeatureClipDetail.searchForPlaylist, text: $text)
                .textFieldStyle(PlainTextFieldStyle())
                .typographyV1(.button.neueMontrealRegular().kerning(0.28))
                .focused($isSearchBarFocused)
                .autocorrectionDisabled(true)
                .onChange(of: isSearchBarFocused) { _, newValue in
                    onFocusChange(newValue)
                }
        }

        @ViewBuilder
        private var searchBarTrailingView: some View {
            if !text.isEmpty {
                Button {
                    text = ""
                } label: {
                    ZStack(alignment: .center) {
                        Circle()
                            .fill(Color.SemanticV1.backgroundSecondary)

                        if isSearchDebouncing {
                            ProgressView()
                                .progressViewStyle(.circular)
                                .foregroundStyle(Color.SemanticV2.foregroundInactive)
                                .frame(width: 27, height: 27)
                        } else {
                            Image.Icon.close
                                .resizable()
                                .renderingMode(.template)
                                .frame(width: 27, height: 27)
                                .foregroundStyle(Color.SemanticV2.foregroundInactive)
                        }
                    }
                    .frame(width: 40, height: 40)
                    .clipShape(Rectangle())
                }
                .buttonStyle(.plain)
                .font(.caption)
                .foregroundColor(.blue)
                .disabled(isSearchDebouncing)
            }
        }

        private func handleSearchDebounce(_ oldValue: String, _ newValue: String) {
            if oldValue != newValue, !newValue.isEmpty {
                isSearchDebouncing = true
                Task {
                    try await Task.sleep(for: .milliseconds(500))
                    isSearchDebouncing = false
                }
            } else if newValue.isEmpty {
                isSearchDebouncing = false
            }
        }
    }

    private var createPlaylistRow: some View {
        Button {
            UIImpactFeedbackGenerator(style: .light).impactOccurred()
            store.send(.createPlaylistTapped)
        } label: {
            HStack(spacing: 16) {
                Color.SemanticV1.backgroundSecondary
                    .clipShape(.rect(cornerRadius: 8))
                    .frame(width: 64, height: 64)
                    .overlay {
                        Image.Icon.plus
                            .foregroundStyle(Color.SemanticV1.iconPrimary)
                    }

                Text(L10n.FeatureClipDetail.newPlaylist)
                    .typographyV1(.body3)
                    .foregroundStyle(Color.SemanticV1.iconPrimary)

                Spacer()
            }
            .padding(.vertical, 16)
            .padding(.horizontal, 12)
        }
    }

    private var createPlaylistRowV2: some View {
        Button {
            UIImpactFeedbackGenerator(style: .light).impactOccurred()
            store.send(.createPlaylistTapped)
        } label: {
            HStack(spacing: 16) {
                Color.SemanticV1.backgroundSecondary
                    .clipShape(.rect(cornerRadius: 8))
                    .frame(width: 50, height: 50)
                    .overlay {
                        Image.Icon.plus
                            .foregroundStyle(Color.SemanticV1.iconPrimary)
                    }

                Text(L10n.FeatureClipDetail.newPlaylist)
                    .typographyV1(.caption2)
                    .foregroundStyle(Color.SemanticV1.textPrimary)

                Spacer()
            }
            .padding(.vertical, 4)
        }
    }

    @ViewBuilder
    public var likedRow: some View {
        HStack(spacing: 12) {
            likedRowAsset

            likedRowText

            Spacer()

            addRemoveButton(store.likedSongIsAdded, isWorking: store.likedSongIsToggling)
        }
        .contentShape(.rect)
        .padding(.vertical, 4)
        .onTapGesture {
            store.send(.toggleLike(!store.clip.isLiked))
        }

        .padding(.leading, 12)
    }

    private var likedRowAsset: some View {
        ZStack {
            Image.Icon.thumbsUpPlaylistAsset
                .frame(width: 24, height: 24)
            Image.Icon.thumbsUp
                .renderingMode(.template)
                .frame(width: 24, height: 24)
                .foregroundStyle(Color.white)
        }
    }

    private var likedRowText: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(L10n.FeatureClipDetail.likedSongs)
                .typographyV1(.caption.kerning(0.28))
                .foregroundStyle(Color.SemanticV2.accentPink)
                .lineLimit(1)

            Text(
                store.likedSongCount == 1
                    ? L10n.FeatureClipDetail.song(store.likedSongCount)
                    : L10n.FeatureClipDetail.songs(store.likedSongCount)
            )
            .typographyV1(.bodySmall)
            .foregroundStyle(Color.SemanticV2.foregroundInactive)
            .lineLimit(1)
            .contentTransition(.numericText())
            .animation(.easeInOut, value: store.likedSongCount)
        }
        .padding(.leading, 12)
    }

    private func addRemoveButton(_ isAdded: Bool, isWorking _: Bool) -> some View {
        Group {
            if isAdded {
                Image.Icon.success
                    .renderingMode(.template)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 24, height: 24)
                    .foregroundStyle(Color.SemanticV2.foregroundPrimary)
            } else {
                Circle()
                    .strokeBorder(
                        Color.SemanticV2.foregroundInactive,
                        lineWidth: 2
                    )
                    .frame(width: 20, height: 20)
                    .padding(.trailing, 2)
            }
        }
        .sensoryFeedbackIfEnabled(trigger: isAdded) { _, newValue in
            newValue == true ? .selection : nil
        }
    }
}
