//
// Copyright (c) Double Symmetry GmbH
// Commercial use requires a license. See https://rntp.dev/pricing
//

import Foundation

/// Protocol for the minimal player interface the sleep timer needs.
protocol SleepTimerPlayer: AnyObject {
    var volume: Float { get set }
    var currentIndex: Int { get }
    func pause()
}

/// Makes AudioPlayer usable as a SleepTimerPlayer.
extension AudioPlayer: SleepTimerPlayer {}

class SleepTimerController {

    /// Called when the sleep timer fires (pauses playback).
    var onTriggered: ((_ type: String) -> Void)?

    private weak var player: SleepTimerPlayer?

    private(set) var sleepTimerType: String?
    private(set) var sleepTimerRemainingSeconds: Double = 0
    private(set) var sleepTimerFadeOutSeconds: Double = 0
    private(set) var sleepTimerTargetIndex: Int?
    private(set) var sleepTimerPreviousIndex: Int?
    private var sleepTimer: Timer?
    private var sleepTimerPreFadeVolume: Float?

    init(player: SleepTimerPlayer) {
        self.player = player
    }

    // MARK: - Public API

    func sleepAfterTime(seconds: Double, fadeOutSeconds: Double) {
        cancelInternal(restoreVolume: true)
        sleepTimerType = "time"
        sleepTimerRemainingSeconds = seconds
        sleepTimerFadeOutSeconds = min(fadeOutSeconds, seconds)

        if seconds <= 0 {
            player?.pause()
            onTriggered?("time")
            cancelInternal(restoreVolume: true)
            return
        }

        startCountdownTimer()
    }

    func sleepAfterMediaItemAtIndex(index: Int) {
        cancelInternal(restoreVolume: true)
        sleepTimerType = "mediaItem"
        sleepTimerTargetIndex = index
        sleepTimerPreviousIndex = player?.currentIndex
    }

    func cancel() {
        cancelInternal(restoreVolume: true)
    }

    func getState() -> [String: Any]? {
        guard let type = sleepTimerType else { return nil }
        if type == "time" {
            return [
                "type": "time",
                "remainingSeconds": sleepTimerRemainingSeconds,
                "fadeOutSeconds": sleepTimerFadeOutSeconds
            ]
        } else {
            return [
                "type": "mediaItem",
                "index": sleepTimerTargetIndex ?? 0
            ]
        }
    }

    /// Call this when the current media item changes.
    /// Returns true if the timer fired.
    func handleItemTransition(to index: Int) -> Bool {
        if sleepTimerType == "mediaItem", let targetIndex = sleepTimerTargetIndex {
            if sleepTimerPreviousIndex == targetIndex && index != targetIndex {
                onTriggered?("mediaItem")
                cancelInternal(restoreVolume: false)
                sleepTimerPreviousIndex = index
                return true
            }
        }
        sleepTimerPreviousIndex = index
        return false
    }

    /// Call this manually in tests to simulate a tick. In production, the Timer calls this.
    func tick() {
        onSleepTimerTick()
    }

    // MARK: - Internal

    func cancelInternal(restoreVolume: Bool) {
        sleepTimer?.invalidate()
        sleepTimer = nil
        if restoreVolume, let preFadeVolume = sleepTimerPreFadeVolume {
            player?.volume = preFadeVolume
        }
        sleepTimerPreFadeVolume = nil
        sleepTimerType = nil
        sleepTimerRemainingSeconds = 0
        sleepTimerFadeOutSeconds = 0
        sleepTimerTargetIndex = nil
        sleepTimerPreviousIndex = nil
    }

    private func onSleepTimerTick() {
        guard sleepTimerType == "time" else { return }
        sleepTimerRemainingSeconds -= 1

        if sleepTimerFadeOutSeconds > 0 && sleepTimerRemainingSeconds < sleepTimerFadeOutSeconds {
            if sleepTimerPreFadeVolume == nil {
                sleepTimerPreFadeVolume = player?.volume ?? 1.0
            }
            let progress = max(0, sleepTimerRemainingSeconds) / sleepTimerFadeOutSeconds
            player?.volume = (sleepTimerPreFadeVolume ?? 1.0) * Float(progress)
        }

        if sleepTimerRemainingSeconds <= 0 {
            sleepTimerRemainingSeconds = 0
            player?.pause()
            onTriggered?("time")
            cancelInternal(restoreVolume: true)
            return
        }
    }

    private func startCountdownTimer() {
        sleepTimer?.invalidate()
        let timer = Timer(timeInterval: 1.0, repeats: true) { [weak self] _ in
            self?.onSleepTimerTick()
        }
        RunLoop.main.add(timer, forMode: .common)
        sleepTimer = timer
    }
}
