import AnalyticsClient
import APIClient
import ComposableArchitecture
import Foundation
import Utilities

extension HooksFeedReducer {
    struct Analytics: AnalyticsReducer {
        var source: String = HooksFeedSource.hooksFeed.analyticsContext.contextType ?? "hooks_feed"

        // swiftlint:disable:next cyclomatic_complexity
        func analytics(before: State, after _: State, action: Action) -> Effect<Action> {
            switch action {
            case .commentsTapped(let hook):
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .commentTapped,
                        actionType: .tap,
                        elementType: .hook,
                        elementId: hook.id,
                        context: createHookAnalyticsContext(hook: hook, source: before.source).toJsonString()
                    )
                )

            case .shareTapped(let hook):
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .shareTapped,
                        actionType: .tap,
                        elementType: .hook,
                        elementId: hook.id,
                        context: createHookAnalyticsContext(hook: hook, source: before.source).toJsonString()
                    )
                )

            case .remixTapped(let hook):
                var context = createHookAnalyticsContext(hook: hook, source: before.source)
                context.clipId = hook.clip?.id.remoteId
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .remixTapped,
                        actionType: .tap,
                        elementType: .hook,
                        elementId: hook.id,
                        context: context.toJsonString()
                    )
                )

            case .internal(.showAddToPlaylist(let clip)):
                guard let currentHook = getCurrentHook(from: before) else {
                    return .none
                }

                var context = createHookAnalyticsContext(hook: currentHook, source: before.source)
                context.clipId = clip.id.remoteId
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .addToPlaylistTapped,
                        actionType: .tap,
                        elementType: .hook,
                        elementId: currentHook.id,
                        context: context.toJsonString()
                    )
                )

            case .swipeToShowProfile(.finish):
                guard let currentHook = getCurrentHook(from: before) else {
                    return .none
                }

                var context = createHookAnalyticsContext(hook: currentHook, source: before.source)
                context.authorHandle = currentHook.user?.handle
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .swipeToProfile,
                        actionType: .swipe,
                        elementType: .hook,
                        elementId: currentHook.id,
                        context: context.toJsonString()
                    )
                )

            case .moreTapped(let hook):
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .moreTapped,
                        actionType: .tap,
                        elementType: .hook,
                        elementId: hook.id,
                        context: createHookAnalyticsContext(hook: hook, source: before.source).toJsonString()
                    )
                )

            case .authorTapped(let handle):
                guard let currentHook = getCurrentHook(from: before) else {
                    return .none
                }

                var context = createHookAnalyticsContext(hook: currentHook, source: before.source)
                context.authorHandle = handle
                context.profileUserId = handle
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .profileTapped,
                        actionType: .tap,
                        elementType: .hook,
                        elementId: currentHook.id,
                        context: context.toJsonString()
                    )
                )

            case .songTapped(let clip):
                guard let currentHook = getCurrentHook(from: before) else {
                    return .none
                }

                var context = createHookAnalyticsContext(hook: currentHook, source: before.source)
                context.clipId = clip.id.remoteId
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .songPillTapped,
                        actionType: .tap,
                        elementType: .hook,
                        elementId: currentHook.id,
                        context: context.toJsonString()
                    )
                )

            case .omniPlayer(.delegate(.closeTapped)):
                guard let currentHook = getCurrentHook(from: before) else {
                    return .none
                }

                var context = createHookAnalyticsContext(hook: currentHook, source: before.source)
                context.clipId = currentHook.clip?.id.remoteId
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .omniPlayerCloseTapped,
                        actionType: .tap,
                        elementType: .hook,
                        elementId: currentHook.id,
                        context: context.toJsonString()
                    )
                )

            case .didSwipeDownOmniPlayerToDismiss:
                guard let currentHook = getCurrentHook(from: before) else {
                    return .none
                }

                var context = createHookAnalyticsContext(hook: currentHook, source: before.source)
                context.clipId = currentHook.clip?.id.remoteId
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .omniPlayerSwipeDown,
                        actionType: .swipe,
                        elementType: .hook,
                        elementId: currentHook.id,
                        context: context.toJsonString()
                    )
                )

            /*
              For most actions performed on Hooks, we use `elementId`
              and `elementType` "hook" to attribute the action to a Hook ID.
             
              Some actions aren't directly performed on a Hook, but have a Hook
              in the relevant context, such as tapping "Create Hook"
              in the top-right or tapping the Hooks tab icon to reload the feed.
             
              For those, we send Hook ID in the context instead.
             */
            case .createHookTapped:
                guard let currentHook = getCurrentHook(from: before) else {
                    return .none
                }

                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .createHookTapped,
                        actionType: .tap,
                        elementType: .button,
                        context: createHookAnalyticsContext(hook: currentHook, source: before.source).toJsonString()
                    )
                )

            case .reloadFeed:
                guard let currentHook = getCurrentHook(from: before) else {
                    return .none
                }
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .refreshFeedTapped,
                        actionType: .tap,
                        elementType: .hook,
                        elementId: currentHook.id,
                        context: createHookAnalyticsContext(hook: currentHook, source: before.source).toJsonString()
                    )
                )

            case .tapToUnmute:
                guard let currentHook = getCurrentHook(from: before) else {
                    return .none
                }
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .tapToUnmuteTapped,
                        actionType: .tap,
                        elementType: .hook,
                        elementId: currentHook.id,
                        context: createHookAnalyticsContext(hook: currentHook, source: before.source).toJsonString()
                    )
                )

            default:
                break
            }
            return .none
        }

        private func createHookAnalyticsContext(hook: Hook, source: HooksFeedSource) -> HookAnalyticsContext {
            let analytics = source.analyticsContext
            return hook.analyticsContext(
                contextType: analytics.contextType,
                contextId: analytics.contextId,
                sourceUrl: analytics.sourceUrl
            )
        }

        private func getCurrentHook(from state: State) -> Hook? {
            guard !state.hooks.isEmpty,
                  state.currentIndex >= 0,
                  state.currentIndex < state.hooks.count,
                  state.currentIndex < state.hooks.ids.count
            else {
                return nil
            }
            return state.hooks[id: state.hooks.ids[state.currentIndex]]
        }
    }
}

extension HooksFeedScreenReducer {
    struct Analytics: AnalyticsReducer {
        var source: String = HooksFeedSource.hooksFeed.analyticsContext.contextType ?? "hooks_feed"

        // swiftlint:disable:next cyclomatic_complexity
        func analytics(before: State, after _: State, action: Action) -> Effect<Action> {
            switch action {
            case .notificationsTapped:
                track(
                    Event(
                        category: .social,
                        actionName: .notificationsTapped,
                        actionType: .tap,
                        elementType: .toolbarButton
                    )
                )
                return .none

            case .setFeedInFocus(let inFocus):
                guard inFocus, let currentHook = getCurrentHook(from: before) else {
                    return .none
                }
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .feedExpandTapped,
                        actionType: .tap,
                        elementType: .hook,
                        elementId: currentHook.id,
                        context: createHookAnalyticsContext(hook: currentHook, source: before.hooksFeed.source).toJsonString()
                    )
                )

            case .toggleMute:
                guard let currentHook = getCurrentHook(from: before) else {
                    return .none
                }
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: before.isMuted ? .unmuteTapped : .muteTapped,
                        actionType: .tap,
                        elementType: .hook,
                        elementId: currentHook.id,
                        context: createHookAnalyticsContext(hook: currentHook, source: before.hooksFeed.source).toJsonString()
                    )
                )

            case .shortcutTapped(let shortcut):
                guard let currentHook = getCurrentHook(from: before) else {
                    return .none
                }
                let mapping = mapElement(for: shortcut)
                let index = before.shortcuts.firstIndex(where: { $0.id == shortcut.id })
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .carouselItemTapped,
                        actionType: .tap,
                        elementType: mapping.elementType,
                        elementId: mapping.elementId,
                        xIndex: index,
                        context: createHookAnalyticsContext(hook: currentHook, source: before.hooksFeed.source).toJsonString()
                    )
                )

            case .refreshFeedTapped:
                guard let currentHook = getCurrentHook(from: before) else {
                    return .none
                }
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .refreshFeedTapped,
                        actionType: .tap,
                        elementType: .button,
                        context: createHookAnalyticsContext(hook: currentHook, source: before.hooksFeed.source).toJsonString()
                    )
                )

            case .carouselSwiped(let direction):
                guard let currentHook = getCurrentHook(from: before) else {
                    return .none
                }
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .carouselSwiped,
                        actionType: .swipe,
                        elementType: .carousel,
                        context: currentHook.id,
                        direction: createHookAnalyticsContext(hook: currentHook, source: before.hooksFeed.source).toJsonString()
                    )
                )

            case .dismissHooksOnboarding:
                track(
                    Event(
                        category: .hooksPlayer,
                        actionName: .hookOnboardingModalDismissTapped,
                        actionType: .tap,
                        elementType: .button
                    )
                )

            default:
                break
            }
            return .none
        }

        private func createHookAnalyticsContext(hook: Hook, source: HooksFeedSource) -> HookAnalyticsContext {
            let analytics = source.analyticsContext
            return hook.analyticsContext(
                contextType: analytics.contextType,
                contextId: analytics.contextId,
                sourceUrl: analytics.sourceUrl
            )
        }

        private func getCurrentHook(from state: State) -> Hook? {
            guard !state.hooksFeed.hooks.isEmpty,
                  state.hooksFeed.currentIndex >= 0,
                  state.hooksFeed.currentIndex < state.hooksFeed.hooks.count,
                  state.hooksFeed.currentIndex < state.hooksFeed.hooks.ids.count
            else {
                return nil
            }
            return state.hooksFeed.hooks[id: state.hooksFeed.hooks.ids[state.hooksFeed.currentIndex]]
        }

        private func mapElement(for shortcut: Shortcut) -> (elementType: Event.ElementType, elementId: String?) {
            switch shortcut.destination {
            case .playlistWithId(let playlistId):
                return (.playlist, playlistId)
            case .followingPlaylist:
                return (.playlist, shortcut.id)
            case .continueListeningPlaylist:
                return (.playlist, shortcut.id)
            case .weeklyHitsPlaylist:
                return (.playlist, shortcut.id)
            case .library, .likedSongs, .discoverTab:
                return (.tab, shortcut.id)
            }
        }
    }
}
