import APIClient
import AVFoundation
import PlayerUtilities

public actor OmniPlayerState {
    // Playback configuration
    private var playbackConfiguration = PlaybackConfiguration()
    private var playbackConfigurationOverride: PlaybackConfiguration?

    // Queue state
    private var queue: [Clip] = [] // Source of truth queue
    private var presentationQueue: [Clip] = [] // Queue for UI presentation with rotation
    private var presentationQueueOffset: Int = 0 // Tracks where in the presentation queue our "real" start is
    private var queueOverride: [Clip]?
    private var currentClipIndex: Int = 0
    private var savedMainQueueIndex: Int? // Saved index from main queue when override is active
    private var isPlaying: Bool = false
    private var manuallyPaused: Bool = false
    private var isAttached = true
    private var isReplacingCurrentItem = false
    private var currentTime: CMTime = .zero // Track the latest playback time

    // Task cancellation for rapid swipe handling
    private var pendingClipChangeTask: Task<Void, Never>?
    // Keep track of preload tasks to cancel them properly
    private var preloadTasks: [Task<Void, Never>] = []
    // Track pending seeks to prevent race conditions
    private var pendingSeekTask: Task<Void, Error>?

    func getCurrentClip() async -> Clip? {
        if let queueOverride = queueOverride,
           !queueOverride.isEmpty
        {
            // When using queue override, return the clip at the current index
            guard currentClipIndex >= 0, currentClipIndex < queueOverride.count else {
                return queueOverride.first
            }
            return queueOverride[currentClipIndex]
        }

        // Map presentation queue index to source queue index for UI
        guard !queue.isEmpty, currentClipIndex >= 0 else { return nil }
        let sourceIndex = (currentClipIndex - presentationQueueOffset + queue.count * 1000) % queue.count
        return queue[sourceIndex]
    }

    func getQueue() async -> [Clip] {
        return queueOverride ?? presentationQueue // Return presentation queue for infinite carousel
    }

    func getActiveQueue() async -> [Clip] {
        // Always return the actual active queue for internal operations
        return queueOverride ?? presentationQueue
    }

    func getSourceQueue() async -> [Clip] {
        // Access to the source of truth queue
        return queue
    }

    func getActiveClipIndex() async -> Int {
        return currentClipIndex
    }

    func getIsPlaying() async -> Bool {
        isPlaying
    }

    func getManualPauseState() async -> Bool {
        manuallyPaused
    }

    func getIsAttached() async -> Bool {
        isAttached
    }

    func getCurrentClipIndex() async -> Int {
        return currentClipIndex
    }

    func getIsReplacingCurrentItem() async -> Bool {
        isReplacingCurrentItem
    }

    func getPlaybackConfiguration() async -> PlaybackConfiguration {
        playbackConfigurationOverride ?? playbackConfiguration
    }

    func setPlaybackConfiguration(_ config: PlaybackConfiguration) async {
        playbackConfiguration = config
    }

    func setPlaybackConfigurationOverride(_ override: PlaybackConfiguration) async {
        playbackConfigurationOverride = override
    }

    func clearPlaybackConfigurationOverride() async {
        playbackConfigurationOverride = nil
    }

    func setQueueOverride(_ newQueue: [Clip]) async {
        // This ensures we can restore the main queue position when clearing the override
        if queueOverride == nil {
            savedMainQueueIndex = currentClipIndex
        }
        queueOverride = newQueue
        // Always reset to index 0 when setting a new queue override
        // This ensures each hook/override starts at the beginning
        currentClipIndex = 0
    }

    func clearQueueOverride() async {
        queueOverride = nil
        // Restore the saved main queue index if it's within bounds
        if let savedIndex = savedMainQueueIndex {
            let queueCount = max(presentationQueue.count, 1)
            currentClipIndex = min(savedIndex, queueCount - 1)
            savedMainQueueIndex = nil
        }
    }

    func setIsAttached(_ attached: Bool) async {
        isAttached = attached
    }

    func setIsReplacingCurrentItem(_ replacing: Bool) async {
        isReplacingCurrentItem = replacing
    }

    func setIsPlaying(_ playing: Bool) async {
        isPlaying = playing
    }

    func setManuallyPaused(_ paused: Bool) async {
        manuallyPaused = paused
    }

    func setCurrentClipIndex(_ index: Int) async {
        currentClipIndex = index
    }

    func appendToQueue(_ clips: [Clip]) async {
        if var override = queueOverride {
            override.append(contentsOf: clips)
            queueOverride = override
        } else {
            queue.append(contentsOf: clips)
            presentationQueue.append(contentsOf: clips)
        }
    }

    func clearQueue() async {
        queue.removeAll()
        presentationQueue.removeAll()
        currentClipIndex = 0
        // Also clear queue override
        queueOverride = nil
    }

    func setQueue(_ newQueue: [Clip]) async {
        queue = newQueue
        // Initialize presentation queue with copies for infinite scrolling
        // Start with 3 copies of the queue to allow scrolling in both directions
        let playbackConfiguration = await getPlaybackConfiguration()
        if playbackConfiguration.repeatMode == .off {
            presentationQueue = newQueue
        } else {
            presentationQueue = newQueue + newQueue + newQueue
        }
        presentationQueueOffset = newQueue.count // Start at the middle copy
        currentClipIndex = newQueue.isEmpty ? 0 : presentationQueueOffset // Start at the beginning of the middle copy
    }

    func rotateQueueForward() async {
        // Expand presentation queue forward for infinite scrolling
        guard !queue.isEmpty else { return }

        let playbackConfiguration = await getPlaybackConfiguration()
        if playbackConfiguration.repeatMode == .off {
            return
        }

        // Add more clips from the source queue to the end
        let clipsToAdd = queue.count // Add a full cycle
        for i in 0 ..< clipsToAdd {
            presentationQueue.append(queue[i])
        }
    }

    func rotateQueueBackward() async {
        // Expand presentation queue backward for infinite scrolling
        guard !queue.isEmpty else { return }

        let playbackConfiguration = await getPlaybackConfiguration()
        if playbackConfiguration.repeatMode == .off {
            return
        }

        // Add clips from the source queue to the beginning
        let clipsToAdd = queue.count // Add a full cycle
        for i in (0 ..< clipsToAdd).reversed() {
            presentationQueue.insert(queue[i], at: 0)
        }

        // Update offset and current index since we inserted at the beginning
        presentationQueueOffset += clipsToAdd
        currentClipIndex += clipsToAdd
    }

    func expandQueueIfNeeded(for index: Int) async {
        guard !queue.isEmpty else { return }

        // Always check the playback configuration and don't expand if repeat mode is off
        let playbackConfiguration = await getPlaybackConfiguration()
        if playbackConfiguration.repeatMode == .off {
            return
        }

        // Expand forward if we're near the end
        if index >= presentationQueue.count - queue.count {
            await rotateQueueForward()
        }

        // Expand backward if we're near the beginning
        if index < queue.count {
            await rotateQueueBackward()
        }
    }

    func getActualSourceIndex(from presentationIndex: Int) async -> Int {
        // Convert presentation queue index to source queue index
        guard !queue.isEmpty else { return 0 }
        return (presentationIndex - presentationQueueOffset + queue.count * 1000) % queue.count
    }

    func removeClipFromQueue(at index: Int) async -> Clip {
        if var override = queueOverride {
            let removed = override.remove(at: index)
            queueOverride = override
            return removed
        } else {
            // Remove from presentation queue
            let removed = presentationQueue.remove(at: index)

            // Find and remove from source queue too
            if let sourceIndex = queue.firstIndex(of: removed) {
                queue.remove(at: sourceIndex)
            }

            // Adjust currentClipIndex if needed
            if index < currentClipIndex {
                currentClipIndex -= 1
            } else if index == currentClipIndex && currentClipIndex >= presentationQueue.count {
                currentClipIndex = max(0, presentationQueue.count - 1)
            }

            return removed
        }
    }

    func updateClipPositionInQueue(from: Int, to: Int) async {
        if var override = queueOverride {
            guard from >= 0, from < override.count, to >= 0, to < override.count else { return }

            let clip = override.remove(at: from)
            override.insert(clip, at: to)
            queueOverride = override
        } else {
            guard from >= 0, from < presentationQueue.count, to >= 0, to < presentationQueue.count else { return }

            let clip = presentationQueue.remove(at: from)
            presentationQueue.insert(clip, at: to)

            // Also update the source queue if possible
            if let sourceFromIndex = queue.firstIndex(of: clip) {
                queue.remove(at: sourceFromIndex)

                // Try to insert at a reasonable position in the source queue
                // Note: This is an approximation since we can't directly map presentation indices to source indices
                let idealSourceTo = min(to, queue.count)
                queue.insert(clip, at: idealSourceTo)
            }

            // Adjust current index if needed
            if currentClipIndex == from {
                currentClipIndex = to
            } else if from < currentClipIndex, to >= currentClipIndex {
                currentClipIndex -= 1
            } else if from > currentClipIndex, to <= currentClipIndex {
                currentClipIndex += 1
            }
        }
    }

    func updateClipInQueue(_ updatedClip: Clip) async {
        if var override = queueOverride {
            if let index = override.firstIndex(where: { $0.id == updatedClip.id }) {
                override[index] = updatedClip
                queueOverride = override
            }
        } else {
            if let index = presentationQueue.firstIndex(where: { $0.id == updatedClip.id }) {
                presentationQueue[index] = updatedClip
            }

            if let index = queue.firstIndex(where: { $0.id == updatedClip.id }) {
                queue[index] = updatedClip
            }
        }
    }

    func shuffleQueue() async -> Int? {
        if var override = queueOverride {
            guard !override.isEmpty else { return nil }

            // Remember the current clip
            let currentClip = override.first

            // Shuffle the queue
            override.shuffle()
            queueOverride = override

            // Update the current index to point to the same clip
            if let currentClip, let newIndex = override.firstIndex(of: currentClip) {
                return newIndex
            } else {
                return 0
            }
        } else {
            guard !queue.isEmpty else { return nil }

            // Remember the current clip
            let currentClip = currentClipIndex < presentationQueue.count ? presentationQueue[currentClipIndex] : nil

            // Shuffle both queues
            queue.shuffle()
            presentationQueue = queue // Reset presentation queue to match source queue

            // Update the current index to point to the same clip
            if let currentClip, let newIndex = presentationQueue.firstIndex(of: currentClip) {
                currentClipIndex = newIndex
                return newIndex
            } else {
                currentClipIndex = 0
                return 0
            }
        }
    }

    func cancelPendingClipChangeTask() async {
        pendingClipChangeTask?.cancel()
        pendingClipChangeTask = nil
    }

    func setPendingClipChangeTask(_ task: Task<Void, Never>) async {
        // Cancel any existing task before setting the new one
        pendingClipChangeTask?.cancel()
        pendingClipChangeTask = task
    }

    func addPreloadTask(_ task: Task<Void, Never>) async {
        preloadTasks.append(task)
    }

    func cancelAllPreloadTasks() async {
        // Cancel and remove all preload tasks
        for task in preloadTasks {
            task.cancel()
        }
        preloadTasks.removeAll()
    }

    func setPendingSeek(_ task: Task<Void, Error>) async {
        // Cancel any existing seek before setting the new one
        pendingSeekTask?.cancel()
        pendingSeekTask = task
    }

    func cancelPendingSeek() async {
        pendingSeekTask?.cancel()
        pendingSeekTask = nil
    }

    func getCurrentTime() -> CMTime {
        return currentTime
    }

    func setCurrentTime(_ time: CMTime) {
        currentTime = time
    }

    func updateClip(_ clip: Clip) async {
        if var override = queueOverride {
            if let index = override.firstIndex(where: { $0.id == clip.id }) {
                override[index] = clip
                queueOverride = override
            }
        } else {
            if let index = presentationQueue.firstIndex(where: { $0.id == clip.id }) {
                presentationQueue[index] = clip
            }

            if let index = queue.firstIndex(where: { $0.id == clip.id }) {
                queue[index] = clip
            }
        }
    }
}
