import Algorithms
import AnalyticsClient
import APIClient
import AVFoundation
import CommentsClient
import ComponentLibrary
import ComposableArchitecture
import EventBusClient
import FeatureClipDetail
import FeatureShare
import LyricsClient
import MediaPlayer
import OmniPlayerClient
import PlayerUtilities
import StatsigClient
import Utilities

@ObservableState
public struct OmniPlayerPlaybackState: Equatable {
    public enum ScrubState: Equatable {
        case scrubbing
        case complete(CMTime)

        public var time: CMTime? {
            switch self {
            case .scrubbing: return nil
            case .complete(let time): return time
            }
        }
    }

    public var clip: Clip
    public var source: PlaybackSource
    public var queue: IdentifiedArrayOf<QueueClip>

    @Shared public var me: Me
    public var isMyClip: Bool { me.user.id == clip.userId }

    @Shared(.inMemory(.playingClipKey)) var playingClip: Clip?
    @Shared(.inMemory(.isPlayingKey)) public var isPlaying: Bool = false

    public var elapsedTime: CMTime = .zero
    public var totalTime: CMTime {
        clip.duration > 0 ? CMTime(seconds: clip.duration, preferredTimescale: 600) : .zero
    }

    public var scrub: ScrubState?
    public var isScrubbing: Bool {
        guard let scrub, case .scrubbing = scrub else {
            return false
        }
        return true
    }
    public var isSceneReadyToPlay: Bool {
        return playingClip?.videoToSongVideoOutputUrl != nil
    }

    public var hasPlayableScene: Bool {
        return clip.videoToSongVideoOutputUrl != nil
    }

    public var hasPlayableInputVideo: Bool {
        return me.user.id == clip.userId
    }

    public var displayTime: CMTime {
        // Normalize timescales to milliseconds for consistent comparison
        let normalizedElapsedTime = CMTimeConvertScale(elapsedTime, timescale: 1000, method: .default)

        if let scrubTime = scrub?.time {
            return scrubTime
        } else {
            return normalizedElapsedTime
        }
    }

    public var timeControlStatus: AVPlayer.TimeControlStatus = .waitingToPlayAtSpecifiedRate {
        didSet {
            $isPlaying.withLock { $0 = timeControlStatus == .playing }
        }
    }

    public var isAtStartOfQueue: Bool {
        return clip.id == queue.first?.clip.id
    }

    public var isAtEndOfQueue: Bool {
        return clip.id == queue.last?.clip.id
    }

    public var isPlayingNewGeneration: Bool = false

    public var fallbackTotalTimeSeconds: Double { clip.isScene ? 30 : 120 }

    public init(
        clip: Clip,
        queue: [Clip],
        me: Shared<Me>,
        source: PlaybackSource = .omniPlayer,
        restartQueueAfterLastSong _: Bool = true,
        isPlayingNewGeneration: Bool = false
    ) {
        self.clip = clip
        // Don't remove duplicates for infinite carousel - each position gets a unique identifier
        self.queue = .init(uniqueElements: queue.enumerated().map { index, clip in
            QueueClip(clip: clip, position: index)
        })

        self._me = me
        self.isPlayingNewGeneration = isPlayingNewGeneration
        self.source = source
    }

    func timeString(seconds: TimeInterval) -> String {
        seconds.positionalTime
    }

    var displayedTotalTime: String {
        if clip.duration > 0 {
            return timeString(seconds: clip.duration)
        } else if clip.status == .streaming {
            return "--:--"
        } else {
            return timeString(seconds: fallbackTotalTimeSeconds)
        }
    }
}

// Extension for helpers outside of the "Playback" domain
extension OmniPlayerPlaybackState {
    var hasRemixableClip: Bool {
        (isMyClip || clip.canRemix) && // You can always remix your own song
            FeatureFlag.create.remixAndAttribution
    }
}

// Generic wrapper to give each queue item position a unique identifier for infinite carousel effect
public struct QueueItem<Content: Equatable & Identifiable>: Equatable, Identifiable {
    public let id: String
    public var clip: Clip
    public let position: Int

    public init(clip: Clip, position: Int) {
        self.clip = clip
        self.position = position
        self.id = "\(clip.id)_\(position)"
    }
}

public typealias QueueClip = QueueItem<Clip>
// Future: loading placeholders, or maybe some sort of interactive content
