import AppKit
import NucleicCore

/// Drives the "escalating alarm": while a chat needs the user and they haven't opened it,
/// repeat the "Submarine" system sound — with a short gap between plays — until they return
/// to that chat. `AppStore` tracks *which* chats are blocked-and-unviewed and calls
/// `setActive(_:)` as that set flips between empty and non-empty (`AppStore.chatAlarmHook`,
/// installed in `NucleicApp.init`); this type owns the repeating playback and honors the
/// off-by-default Settings switch. A singleton so the one alarm loop is shared app-wide.
@MainActor
final class ChatAlarm {
    static let shared = ChatAlarm()
    private init() {}

    /// Silence between consecutive plays, so the sound repeats as a deliberate pulse rather
    /// than the next copy stacking on top of the last while it's still sounding.
    private let gap = Duration.seconds(0.5)

    /// The running repeat loop, or nil when silent. Cancelling it stops scheduling new plays;
    /// `current?.stop()` cuts off whatever is sounding right now for instant silence on return.
    private var loop: Task<Void, Never>?
    private var current: NSSound?

    /// Start or stop the alarm. Honors the Settings switch: with the alarm turned off,
    /// `active == true` is a no-op, so a blocked chat still rings only its one-shot "Pop" cue.
    /// `active == false` always stops, so toggling the switch off mid-alarm silences it at once.
    func setActive(_ active: Bool) {
        if active && ChatStatusSounds.escalatingAlarm {
            start()
        } else {
            stop()
        }
    }

    private func start() {
        guard loop == nil else { return }  // already ringing — don't stack loops
        loop = Task { @MainActor [weak self] in
            guard let self else { return }
            // Pace each cycle by the clip's own length plus the gap, so a ~1.5s sound doesn't
            // pile copies on itself; fall back to a sane length if the system can't report one.
            let probe = NSSound(named: NSSound.Name(ChatStatusSounds.alarmSoundName))
            let clip: Duration = (probe?.duration ?? 0) > 0 ? .seconds(probe!.duration) : .seconds(1.5)
            while !Task.isCancelled {
                self.ring()
                try? await Task.sleep(for: clip + self.gap)
            }
        }
    }

    private func stop() {
        loop?.cancel()
        loop = nil
        current?.stop()
        current = nil
    }

    /// Play one fresh copy of the alarm sound, holding the reference so `stop()` can cut it off
    /// the instant the user returns to the chat.
    private func ring() {
        guard let sound = NSSound(named: NSSound.Name(ChatStatusSounds.alarmSoundName))?.copy() as? NSSound
        else { return }
        current = sound
        sound.play()
    }
}
