import ComposableArchitecture

public protocol AnalyticsReducer {
    associatedtype State
    associatedtype Action

    func analytics(before: State, after: State, action: Action) -> Effect<Action>
    /// Name of the source of the event. Used to identify the event in code i.e. `root_coordinator` or `create_clip`
    /// Allows for source to be set once on instances of `AnalyticsReducer` and enabling of the syntactic sugar in the extension below
    var source: String { get }
}

public extension AnalyticsReducer {
    var source: String {
        return ""
    }

    /// Syntactic sugar for `trackV2-with-source` for conformances of `AnalyticsReducer`
    func track(_ event: Event, sourceOverride: String? = nil, destination: Set<AnalyticsClient.Destination> = .standard) {
        @Dependency(\.analyticsClient.trackV2) var trackV2
        trackV2(event, sourceOverride ?? source, destination)
    }

    func getGlobalContext(options: [AnalyticsGlobalContextOption]) -> String {
        return AnalyticsUtility.getGlobalContextJSON(options: options)
    }
}

public struct _AnalyticsReducer<Base: Reducer, Analytics: AnalyticsReducer>: Reducer where Analytics.State == Base.State, Analytics.Action == Base.Action {
    @usableFromInline
    let base: Base

    @usableFromInline
    let analytics: Analytics

    @usableFromInline
    init(
        base: Base,
        analytics: Analytics
    ) {
        self.base = base
        self.analytics = analytics
    }

    @inlinable
    public func reduce(into state: inout Base.State, action: Base.Action) -> Effect<Base.Action> {
        let before = state
        let baseEffects = self.base.reduce(into: &state, action: action)
        let after = state
        return .merge(
            baseEffects,
            analytics.analytics(before: before, after: after, action: action)
        )
    }
}

public extension ReducerBuilder {
    @inlinable
    static func buildPartialBlock<R0: Reducer, R1: AnalyticsReducer>(accumulated: R0, next: R1) -> _AnalyticsReducer<R0, R1>
    where R0.State == State, R0.Action == Action {
        return .init(base: accumulated, analytics: next)
    }

    @inlinable
    static func buildExpression<R: AnalyticsReducer>(_ expression: R) -> R
    where R.State == State, R.Action == Action {
        return expression
    }
}
