#if os(iOS)
import BitmovinPlayer
import ExpoModulesCore
import Foundation
import React

internal class OfflineContentManagerBridge: NSObject, OfflineContentManagerListener {
    enum EventType: String {
        case onCompleted
        case onError
        case onProgress
        case onOptionsAvailable
        case onDrmLicenseUpdated
        case onSuspended
        case onResumed
        case onCanceled
        case onDrmLicenseExpired
    }

    let offlineContentManager: OfflineContentManager
    private weak var module: OfflineModule?
    let nativeId: NativeId
    let identifier: String
    var currentTrackSelection: OfflineTrackSelection?

    init(
        forManager offlineContentManager: OfflineContentManager,
        module: OfflineModule,
        nativeId: NativeId,
        identifier: String
    ) {
        self.offlineContentManager = offlineContentManager
        self.module = module
        self.nativeId = nativeId
        self.identifier = identifier
        super.init()

        offlineContentManager.add(listener: self)
    }

    func release() {
        offlineContentManager.remove(listener: self)
        currentTrackSelection = nil
    }

    func fetchAvailableTracks() {
        offlineContentManager.fetchAvailableTracks()

        sendOfflineEvent(eventType: .onOptionsAvailable)
    }

    func onOfflineError(
        _ event: OfflineErrorEvent,
        offlineContentManager: OfflineContentManager
    ) {
        sendOfflineEvent(eventType: .onError, body: [
            "code": event.errorCode,
            "message": event.message
        ])
    }

    func onAvailableTracksFetched(
        _ event: AvailableTracksFetchedEvent,
        offlineContentManager: OfflineContentManager
    ) {
        currentTrackSelection = event.tracks

        sendOfflineEvent(eventType: .onOptionsAvailable, body: [
            "options": RCTConvert.toJson(offlineTracks: event.tracks) ?? [:]
        ])
    }

    func onContentDownloadFinished(
        _ event: ContentDownloadFinishedEvent,
        offlineContentManager: OfflineContentManager
    ) {
        sendOfflineEvent(eventType: .onCompleted, body: [
            "options": RCTConvert.toJson(offlineTracks: currentTrackSelection) ?? [:]
        ])
    }

    func onContentDownloadProgressChanged(
        _ event: ContentDownloadProgressChangedEvent,
        offlineContentManager: OfflineContentManager
    ) {
        sendOfflineEvent(eventType: .onProgress, body: [
            "progress": event.progress
        ])
    }

    func onContentDownloadSuspended(
        _ event: ContentDownloadSuspendedEvent,
        offlineContentManager: OfflineContentManager
    ) {
        sendOfflineEvent(eventType: .onSuspended)
    }

    func onContentDownloadResumed(
        _ event: ContentDownloadResumedEvent,
        offlineContentManager: OfflineContentManager
    ) {
        sendOfflineEvent(eventType: .onResumed)
    }

    func onContentDownloadCanceled(
        _ event: ContentDownloadCanceledEvent,
        offlineContentManager: OfflineContentManager
    ) {
        sendOfflineEvent(eventType: .onCanceled)
    }

    func onOfflineContentLicenseRenewed(
        _ event: OfflineContentLicenseRenewedEvent,
        offlineContentManager: OfflineContentManager
    ) {
        sendOfflineEvent(eventType: .onDrmLicenseUpdated)
    }

    /**
     Called on every call to OfflineContentManager.createOfflineSourceConfig(restrictedToAssetCache:)
     if it is DRM protected and the offline DRM license has expired.
     */
    func onOfflineContentLicenseExpired(
        _ event: OfflineContentLicenseExpiredEvent,
        offlineContentManager: OfflineContentManager
    ) {
        sendOfflineEvent(eventType: .onDrmLicenseExpired)
    }

    private func sendOfflineEvent(eventType: EventType, body: [String: Any] = [:]) {
        let baseEvent: [String: Any] = [
            "nativeId": nativeId,
            "identifier": identifier,
            "eventType": eventType.rawValue,
            "state": RCTConvert.toJson(offlineState: offlineContentManager.offlineState)
        ]

        let eventBody = baseEvent.merging(body) { current, _ in current }

        module?.sendEvent("onBitmovinOfflineEvent", eventBody)
    }
}
#endif
