import APIClient
import ComposableArchitecture
import Foundation
import SwiftUI
import Waveform

/*
    TCA is causing a giant lag on this screen
    switching to ObservableObject to avoid
 */

public final class VisualizerGalleryObservable: ObservableObject {
    enum WindowTime: String, Identifiable {
        static var menuOrderedDurations: [WindowTime] {
            return [.tenSeconds, .twentySeconds, .thirtySeconds]
        }

        case tenSeconds
        case twentySeconds
        case thirtySeconds

        var id: String {
            return rawValue
        }

        var timeInterval: TimeInterval {
            switch self {
            case .tenSeconds:
                return 10.0
            case .twentySeconds:
                return 20.0
            case .thirtySeconds:
                return 30.0
            }
        }
    }

    static let defaultDuration: TimeInterval = 30.0

    @Published var state = State()

    struct State: Equatable {
        var gallerySize: CGSize = .zero
        var originalDragPosition: CGFloat = .zero
        var activeDragPosition: CGFloat = .zero

        var activeIndex: Int = .zero
        var shouldShowTitle: Bool = false
        var selectedRange: ClosedRange<TimeInterval> = 0.0 ... 30.0
        var windowTime: WindowTime = .thirtySeconds

        var isPlaying: Bool = true
        var elapsedTime: TimeInterval = .zero

        var selectedDuration: TimeInterval {
            selectedRange.upperBound - selectedRange.lowerBound
        }

        var progress: CGFloat {
            guard selectedDuration > 0 else { return 0.0 }
            return CGFloat(elapsedTime / selectedDuration)
        }
    }

    @Shared(.appStorage(.isShareAssetCreationMuted)) var isMuted: Bool = false

    private var showTitleTask: Task<Void, Never>?
    private var timerTask: Task<Void, Never>?

    var selectedStartTime: TimeInterval {
        return state.selectedRange.lowerBound
    }

    var selectedEndTime: TimeInterval {
        return state.selectedRange.upperBound
    }

    var currentTime: TimeInterval {
        return state.elapsedTime + selectedStartTime
    }

    public init() {
        /*
            This init should not actually be used
            it is only here to avoid optional on ObservedObject
         */
    }

    func getCenteredRangeFromClip(_ clip: Clip) -> ClosedRange<TimeInterval> {
        return clip.centeredRange(duration: VisualizerGalleryObservable.defaultDuration)
    }

    func setSelectedRange(_ range: ClosedRange<TimeInterval>) {
        Task {
            await updateState {
                $0.selectedRange = range
            }
        }
    }

    func toggleMute() async -> Bool {
        await updateState { $0.elapsedTime = .zero }
        return $isMuted.withLock {
            $0 = !$0
            return $0
        }
    }

    func showTitle() {
        // Cancel any previously scheduled task
        showTitleTask?.cancel()
        // Start a new task and store the reference
        showTitleTask = Task {
            // Ensure we're on the main thread when updating UI state
            await updateState { $0.shouldShowTitle = true }

            // Sleep for 3 seconds; use try? in case the task gets cancelled
            try? await Task.sleep(for: .seconds(2.0))

            // Before hiding, confirm we're not cancelled (optional, as task cancellation will skip the sleep)
            guard !Task.isCancelled else { return }

            await updateState { $0.shouldShowTitle = false }
        }
    }

    var selectedRangeAsString: String {
        return TimeInterval.formattedStartEndTimeRange(state.selectedRange)
    }

    var selectedRangeDurationAsString: String {
        return TimeInterval.formattedDurationTimeRange(state.selectedRange)
    }

    var lastFrameDate: TimeInterval?

    // NOTE:
    // This calculation works well, but `SlideWindowWaveformView` currently does its own recalculation after adjusting window time that essentially disregards this value.
    // A fix for this would probably take a fundamental improvement on `SlideWindowWaveformView`.
    func updateWindowTime(_ windowTime: WindowTime, clipDuration: TimeInterval) async {
        let oldWindowTime = state.windowTime
        // If no change, return
        guard windowTime.timeInterval != oldWindowTime.timeInterval else { return }

        let snapshot = await updateState {
            $0.windowTime = windowTime
            $0.elapsedTime = .zero
        }

        /// Calculate start + end
        let selectedRange = snapshot.selectedRange
        var newStart: TimeInterval = selectedRange.lowerBound
        var newEnd: TimeInterval = newStart + windowTime.timeInterval
        /// Move range backwards if we exceed the clip duration
        if newEnd > clipDuration {
            let delta = newEnd - clipDuration
            newStart -= delta
            newEnd -= delta
        }
        /// Move start if it's less than zero.
        /// Our window will be the full song in this case.
        newStart = max(0.0, newStart)

        await updateState { $0.selectedRange = newStart ... newEnd }
    }

    public func startTimer(onResetToZeroElapsedTime: @escaping () -> Void) {
        timerTask?.cancel()
        timerTask = Task { [weak self] in
            guard let self else { return }

            // initialize lastDate to now before the loop
            var lastDate = Date()

            // how many nanoseconds per frame at 30 Hz
            let frameDuration = 1.0 / 30.0
            let frameNanos = UInt64(frameDuration * 1_000_000_000)

            while !Task.isCancelled {
                let t0 = DispatchTime.now().uptimeNanoseconds

                // compute delta for *this* frame
                let now = Date()
                let delta = now.timeIntervalSince(lastDate)
                lastDate = now

                let snapshot = state
                let isPlaying = snapshot.isPlaying
                let oldElapsedTime = snapshot.elapsedTime
                let selectedDuration = snapshot.selectedDuration

                if isPlaying && !isMuted {
                    await MainActor.run { [weak self] in
                        guard let self else { return }
                        let wrapped = (oldElapsedTime + delta).truncatingRemainder(dividingBy: selectedDuration)
                        if wrapped < oldElapsedTime {
                            onResetToZeroElapsedTime()
                        }
                        updateState { $0.elapsedTime = wrapped }
                    }
                }

                let work = DispatchTime.now().uptimeNanoseconds - t0
                let sleep = work < frameNanos ? frameNanos - work : 0
                try? await Task.sleep(nanoseconds: sleep)
            }
        }
    }

    public func stopTimer() {
        timerTask?.cancel()
        timerTask = nil
    }

    public func stopTitleTask() {
        showTitleTask?.cancel()
        showTitleTask = nil
    }

    deinit {
        stopTimer()
        stopTitleTask()
    }
}

private extension VisualizerGalleryObservable {
    @MainActor
    @discardableResult
    func updateState(
        _ update: @escaping (inout State) -> Void
    ) -> State {
        var snapshot = state
        update(&snapshot)
        state = snapshot
        return snapshot
    }
}

extension Clip {
    func centeredRange(duration: TimeInterval) -> ClosedRange<TimeInterval> {
        let start = 0.0
        let end = self.duration
        let mid = self.duration / 2

        return max(start, mid - duration / 2) ... min(end, mid + duration / 2)
    }
}
