//
//  NativeNotificationContentFetcher.swift
//  DailyNotificationPlugin
//
//  Swift SPI mirroring org.timesafari.dailynotification.NativeNotificationContentFetcher (Android).
//

import Foundation

/// Context for a native fetch (trigger, scheduled user notification time, metadata).
public struct FetchContext {
    public let trigger: String
    public let scheduledTimeMillis: Int64?
    public let fetchTimeMillis: Int64
    public let metadata: [String: Any]

    public init(
        trigger: String,
        scheduledTimeMillis: Int64?,
        fetchTimeMillis: Int64,
        metadata: [String: Any] = [:]
    ) {
        self.trigger = trigger
        self.scheduledTimeMillis = scheduledTimeMillis
        self.fetchTimeMillis = fetchTimeMillis
        self.metadata = metadata
    }
}

/// Host app implements this and registers via `DailyNotificationPlugin.registerNativeFetcher(_:)`.
public protocol NativeNotificationContentFetcher: AnyObject {
    /// Called when TypeScript invokes `configureNativeFetcher` (mirrors Android `NativeNotificationContentFetcher.configure`).
    func configure(apiBaseUrl: String, activeDid: String, jwtToken: String, jwtTokenPool: [String]?)

    /// Background-safe fetch; return empty array when there is nothing to show (not an error).
    func fetchContent(context: FetchContext) async throws -> [NotificationContent]
}

public extension NativeNotificationContentFetcher {
    func configure(apiBaseUrl: String, activeDid: String, jwtToken: String, jwtTokenPool: [String]?) {
        // Default: no-op
    }
}

/// Holds the registered host fetcher (thread-safe).
public final class NativeNotificationFetcherRegistry {
    public static let shared = NativeNotificationFetcherRegistry()
    private let lock = NSLock()
    private weak var weakFetcher: NativeNotificationContentFetcher?

    private init() {}

    public var fetcher: NativeNotificationContentFetcher? {
        lock.lock()
        defer { lock.unlock() }
        return weakFetcher
    }

    public func set(_ fetcher: NativeNotificationContentFetcher?) {
        lock.lock()
        weakFetcher = fetcher
        lock.unlock()
    }
}

enum NativeFetcherTimeoutError: Error {
    case timedOut(ms: Int)
}

func withTimeout<T>(milliseconds: Int, operation: @escaping () async throws -> T) async throws -> T {
    try await withThrowingTaskGroup(of: T.self) { group in
        group.addTask {
            try await operation()
        }
        group.addTask {
            try await Task.sleep(nanoseconds: UInt64(milliseconds) * 1_000_000)
            throw NativeFetcherTimeoutError.timedOut(ms: milliseconds)
        }
        let first = try await group.next()!
        group.cancelAll()
        return first
    }
}
