import AVFoundation
import NitroModules

/// Queue surface of ``HybridPlaybackEngine``, split out of the main bridge
/// file. All queue mutations delegate to the C coordinator; the Swift-side
/// `_queueItemRefs` mirror exists only so JS can read back the item objects.
/// `_currentItem` is never assigned here — it is updated on the main thread in
/// `handleDomainEvent(CURRENT_ITEM_CHANGED)` to avoid a cross-thread data race.
extension HybridPlaybackEngine {
    func setQueue(items: [any HybridMediaItemSpec]) throws {
        guard let coord = coordinator else { return }

        // Keep Swift-side refs for JS access
        _queueItemRefs = items

        // Build C items array
        var cItems = items.map { buildCoreItem(from: $0) }
        aviation_coordinator_queue_set(coord, &cItems, Int32(cItems.count))
    }

    func addToQueue(item: any HybridMediaItemSpec) throws {
        guard let coord = coordinator else { return }
        _queueItemRefs.append(item)
        var cItem = buildCoreItem(from: item)
        aviation_coordinator_queue_add(coord, &cItem)
    }

    func insertInQueue(item: any HybridMediaItemSpec, afterIndex: Double) throws {
        guard let coord = coordinator else { return }
        let idx = Int(afterIndex)
        guard idx >= 0, idx < _queueItemRefs.count else { return }
        _queueItemRefs.insert(item, at: idx + 1)
        var cItem = buildCoreItem(from: item)
        aviation_coordinator_queue_insert_after(coord, &cItem, Int32(idx))
    }

    func removeFromQueue(index: Double) throws {
        guard let coord = coordinator else { return }
        let idx = Int(index)
        guard idx >= 0, idx < _queueItemRefs.count else { return }
        _queueItemRefs.remove(at: idx)
        aviation_coordinator_queue_remove(coord, Int32(idx))
    }

    func moveInQueue(fromIndex: Double, toIndex: Double) throws {
        guard let coord = coordinator else { return }
        let from = Int(fromIndex)
        let to = Int(toIndex)
        guard from >= 0, from < _queueItemRefs.count else { return }
        guard to >= 0, to < _queueItemRefs.count else { return }
        let item = _queueItemRefs.remove(at: from)
        _queueItemRefs.insert(item, at: to)
        aviation_coordinator_queue_move(coord, Int32(from), Int32(to))
    }

    func clearQueue() throws {
        guard let coord = coordinator else { return }
        _queueItemRefs.removeAll()
        aviation_coordinator_queue_clear(coord)
    }

    func skipToNext() throws -> Promise<Void> {
        let promise = Promise<Void>()
        guard coordinator != nil else {
            promise.reject(withError: NSError(domain: "aviation", code: -20,
                                              userInfo: [NSLocalizedDescriptionKey: "Engine not initialized"]))
            return promise
        }

        // _currentItem is updated on the main thread in handleDomainEvent(CURRENT_ITEM_CHANGED)
        // using the event's queueIndex — no direct assignment here to avoid a cross-thread data race.

        // The C coordinator already loaded the item — return a promise for load completion
        DispatchQueue.main.async { [weak self] in
            guard let self else { return }
            guard !_released, let coord = coordinator else {
                promise.reject(withError: NSError(domain: "aviation", code: -20,
                                                  userInfo: [NSLocalizedDescriptionKey: "Engine not initialized"]))
                return
            }
            resolvePendingPromises() // superseded by this newer navigation
            loadPromise = promise

            let newIndex = aviation_coordinator_skip_next(coord)
            if newIndex < 0 {
                loadPromise = nil
                promise.reject(withError: NSError(domain: "aviation", code: -20,
                                                  userInfo: [NSLocalizedDescriptionKey: "Queue ended"]))
            }
        }
        return promise
    }

    func skipToPrevious() throws -> Promise<Void> {
        let promise = Promise<Void>()
        guard coordinator != nil else {
            promise.reject(withError: NSError(domain: "aviation", code: -21,
                                              userInfo: [NSLocalizedDescriptionKey: "Engine not initialized"]))
            return promise
        }

        // _currentItem is updated on the main thread in handleDomainEvent(CURRENT_ITEM_CHANGED)
        // using the event's queueIndex — no direct assignment here to avoid a cross-thread data race.

        DispatchQueue.main.async { [weak self] in
            guard let self else { return }
            guard !_released, let coord = coordinator else {
                promise.reject(withError: NSError(domain: "aviation", code: -21,
                                                  userInfo: [NSLocalizedDescriptionKey: "Engine not initialized"]))
                return
            }
            resolvePendingPromises() // superseded by this newer navigation
            loadPromise = promise

            let newIndex = aviation_coordinator_skip_previous(coord)
            if newIndex < 0 {
                loadPromise = nil
                promise.reject(withError: NSError(domain: "aviation", code: -21,
                                                  userInfo: [NSLocalizedDescriptionKey: "No previous item"]))
            }
        }
        return promise
    }

    func skipToIndex(index: Double, autoPlay: Bool) throws -> Promise<Void> {
        let promise = Promise<Void>()
        guard coordinator != nil else {
            promise.reject(withError: NSError(domain: "aviation", code: -22,
                                              userInfo: [NSLocalizedDescriptionKey: "Engine not initialized"]))
            return promise
        }

        let idx = Int(index)
        #if DEBUG
            NSLog("[Aviation] skipToIndex(%d, autoPlay=%d) queueCount=%d", idx, autoPlay ? 1 : 0, _queueItemRefs.count)
        #endif
        guard idx >= 0, idx < _queueItemRefs.count else {
            promise.reject(withError: NSError(domain: "aviation", code: -22,
                                              userInfo: [NSLocalizedDescriptionKey: "Index \(idx) out of range"]))
            return promise
        }

        // _currentItem is updated on the main thread in handleDomainEvent(CURRENT_ITEM_CHANGED)
        // using the event's queueIndex — no direct assignment here to avoid a cross-thread data race.

        DispatchQueue.main.async { [weak self] in
            guard let self else { return }
            guard !_released, let coord = coordinator else {
                promise.reject(withError: NSError(domain: "aviation", code: -22,
                                                  userInfo: [NSLocalizedDescriptionKey: "Engine not initialized"]))
                return
            }
            resolvePendingPromises() // superseded by this newer navigation
            loadPromise = promise

            let result = aviation_coordinator_skip_to_index_ex(coord, Int32(idx), autoPlay)
            if result < 0 {
                loadPromise = nil
                promise.reject(withError: NSError(domain: "aviation", code: -22,
                                                  userInfo: [NSLocalizedDescriptionKey: "Index \(idx) out of range"]))
            }
        }
        return promise
    }

    var queueItems: [any HybridMediaItemSpec] { _queueItemRefs }

    var queueIndex: Double {
        guard let coord = coordinator else { return -1 }
        return Double(aviation_coordinator_queue_get_index(coord))
    }

    var queueCount: Double {
        guard let coord = coordinator else { return 0 }
        return Double(aviation_coordinator_queue_get_count(coord))
    }

    var repeatMode: RepeatMode {
        get {
            guard let coord = coordinator else { return .off }
            let mode = aviation_coordinator_queue_get_repeat(coord)
            switch mode {
            case AVIATION_REPEAT_OFF: return .off
            case AVIATION_REPEAT_ONE: return .one
            case AVIATION_REPEAT_ALL: return .all
            default: return .off
            }
        }
        set {
            guard let coord = coordinator else { return }
            let cMode: AviationRepeatMode = switch newValue {
            case .off: AVIATION_REPEAT_OFF
            case .one: AVIATION_REPEAT_ONE
            case .all: AVIATION_REPEAT_ALL
            }
            aviation_coordinator_queue_set_repeat(coord, cMode)
        }
    }

    var shuffleEnabled: Bool {
        get {
            guard let coord = coordinator else { return false }
            return aviation_coordinator_queue_get_shuffle(coord)
        }
        set {
            guard let coord = coordinator else { return }
            aviation_coordinator_queue_set_shuffle(coord, newValue)
        }
    }
}
