import APIClient
import ComposableArchitecture
import FeatureMadlibsOnboarding
import FeatureOnboardingModels
import FeatureOnboardingScreens
import Foundation
import OnboardingAnalyticsClient
import StatsigClient

public enum OnboardingNavigation {
    // MARK: - FlowConfiguration

    fileprivate struct FlowConfiguration {
        let steps: [OnboardingStepType]

        func nextStep(after currentStep: OnboardingStepType) -> OnboardingStepType? {
            guard let currentIndex = steps.firstIndex(of: currentStep) else { return nil }
            let nextIndex = currentIndex + 1
            return nextIndex < steps.count ? steps[nextIndex] : nil
        }

        var firstStep: OnboardingStepType? {
            steps.first
        }
    }

    // MARK: - NavigationResult

    public enum NavigationResult {
        case pushStep(OnboardingReducer.Path.State)
        case completeOnboarding(Me)
        case invariantViolation
    }

    // MARK: - Public Navigation Methods

    public static func navigateAfterAuthentication(
        state: inout OnboardingReducer.State
    ) -> NavigationResult {
        let me = state.currentMe ?? .empty

        let flowConfig = FlowConfiguration(
            variant: state.flowVariant,
            user: me.user
        )

        guard let firstStep = flowConfig.firstStep else {
            return .completeOnboarding(me)
        }

        switch firstStep {
        case .madlibsFlow:
            // Only send an exposure event if the user is eligible
            if let experimentFlags = state.experimentFlags, experimentFlags.madlibs.isEnabled {
                ParameterStores.SunoIos.markExposed(flag: \.$madlibsOnboardingExperiment)
            }

            let madlibsContainerState = MadlibsContainerReducer.State(me: me)
            state.madlibsContainerState = madlibsContainerState

            let pathState = OnboardingReducer.Path.State.madlibsFlow
            state.path.append(pathState)
            return .pushStep(pathState)

        default:
            guard let pathState = createPathState(for: firstStep, me: me, state: state) else {
                return .invariantViolation
            }

            state.path.append(pathState)
            return .pushStep(pathState)
        }
    }

    public static func navigateToNextStep(
        after currentStep: OnboardingStepType,
        state: inout OnboardingReducer.State,
        me: Me
    ) -> NavigationResult {
        let flowConfig = FlowConfiguration(
            variant: state.flowVariant,
            user: me.user
        )

        guard let nextStep = flowConfig.nextStep(after: currentStep) else {
            return .completeOnboarding(me)
        }

        guard let pathState = createPathState(for: nextStep, me: me, state: state) else {
            return .invariantViolation
        }

        state.path.append(pathState)

        return .pushStep(pathState)
    }

    private static func createPathState(
        for step: OnboardingStepType,
        me: Me,
        state: OnboardingReducer.State
    ) -> OnboardingReducer.Path.State? {
        switch step {
        case .displayNameSelection:
            return .displayName(
                NameEntry.State(
                    me: me,
                    initialValue: state.fullNameFromAuth,
                    errorRepresentation: .systemAlert
                )
            )

        case .usernameSelection:
            return .username(
                UsernameEntry.State(
                    me: me
                )
            )

        case .birthdayEntry:
            return .birthday(BirthdayEntry.State(me: me))

        case .notificationPermissions:
            return .notifications(OnboardingNotificationsQuestion.State(me: me))

        case .contactPermissions:
            return .contactsAuth(ContactsAuthorization.State(me: me))

        case .contactSync:
            return .contactSync(ContactSync.State(me: me))

        case .welcome,
             .phoneAuth,
             .signUp,
             .codeVerification,
             .madlibsFlow:
            // Our onboarding navigation is still tightly coupled to authentication
            // Future improvment - move steps that come before profile setup to a separate authentication flow
            // In the case of madlibsFlow - we handle this profile setup flow navigation in a child reducer
            return nil
        }
    }
}

// MARK: - NavigationResult+toEffect

extension OnboardingNavigation.NavigationResult {
    func toEffect(
        state: OnboardingReducer.State,
        onboardingAnalyticsClient: OnboardingAnalyticsClient
    ) -> Effect<OnboardingReducer.Action> {
        switch self {
        case .pushStep:
            return .none

        case .completeOnboarding(let me):
            onboardingAnalyticsClient.trackFlowCompleted(
                state.flowVariant,
                state.currentMe?.user.id,
                state.isFTUX
            )
            let result = OnboardingCompletedResult(
                me: me,
                isFirstTimeUser: state.isFTUX,
                onboardingFlowVariant: state.flowVariant
            )
            return .send(.delegate(.onboardingCompleted(result)))

        case .invariantViolation:
            // Should never happen
            return .none
        }
    }
}

// MARK: - FlowConfiguration+init

private extension OnboardingNavigation.FlowConfiguration {
    init(
        variant: OnboardingFlowVariant,
        user: User
    ) {
        switch variant {
        case .madlibs:
            self = Self(steps: [.madlibsFlow])

        case .default:
            var steps: [OnboardingStepType] = []

            let shouldShowDisplayName = if let displayName = user.displayName, displayName.isEmpty {
                true
            } else {
                user.displayName == nil
            }

            if shouldShowDisplayName {
                steps.append(.displayNameSelection)
            }

            if user.username.isEmpty || !user.isHandleUpdated {
                steps.append(.usernameSelection)
            }

            // Always show birthday and contacts sync steps
            steps.append(.birthdayEntry)
            steps.append(.contactPermissions)
            steps.append(.contactSync)

            self = Self(steps: steps)
        }
    }
}

// MARK: - Me+empty

private extension Me {
    static var empty: Self {
        Me(
            models: [],
            roles: [:],
            flags: [:],
            user: .empty()
        )
    }
}
