//! All events that can be received from the API.

use chrono::Duration;
use serde::Deserialize;

use crate::common::{SceneItem, SceneItemTransform};

/// Events are sent when a recognized action occurs within OBS.
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Event {
    #[serde(default, deserialize_with = "crate::de::duration_opt")]
    /// Time elapsed between now and stream start (only present if OBS Studio is streaming).
    pub stream_timecode: Option<Duration>,
    /// Time elapsed between now and recording start (only present if OBS Studio is recording).
    #[serde(default, deserialize_with = "crate::de::duration_opt")]
    pub rec_timecode: Option<Duration>,
    /// The type of event.
    #[serde(flatten)]
    pub ty: EventType,
}

/// All possible event types that can occur while the user interacts with OBS.
#[derive(Clone, Debug, Deserialize)]
#[serde(tag = "update-type")]
pub enum EventType {
    // --------------------------------
    // Scenes
    // --------------------------------
    /// Indicates a scene change.
    #[serde(rename_all = "kebab-case")]
    SwitchScenes {
        /// The new scene.
        scene_name: String,
        /// List of scene items in the new scene.
        sources: Vec<SceneItem>,
    },
    /// The scene list has been modified. Scenes have been added, removed, or renamed.
    ///
    /// Note: This event is not fired when the scenes are reordered.
    ScenesChanged {
        /// Scenes list.
        scenes: Vec<Scene>,
    },
    /// Triggered when switching to another scene collection or when renaming the current scene
    /// collection.
    #[serde(rename_all = "camelCase")]
    SceneCollectionChanged {
        /// Name of the new current scene collection.
        scene_collection: String,
    },
    /// Triggered when a scene collection is created, added, renamed, or removed.
    SceneCollectionListChanged {
        /// Scene collections list.
        scene_collections: Vec<SceneCollection>,
    },
    // --------------------------------
    // Transitions
    // --------------------------------
    /// The active transition has been changed.
    #[serde(rename_all = "kebab-case")]
    SwitchTransition {
        /// The name of the new active transition.
        transition_name: String,
    },
    /// The list of available transitions has been modified. Transitions have been added, removed,
    /// or renamed.
    TransitionListChanged {
        /// Transitions list.
        transitions: Vec<Transition>,
    },
    /// The active transition duration has been changed.
    #[serde(rename_all = "kebab-case")]
    TransitionDurationChanged {
        /// New transition duration.
        #[serde(deserialize_with = "crate::de::duration_millis")]
        new_duration: Duration,
    },
    /// A transition (other than "cut") has begun.
    #[serde(rename_all = "kebab-case")]
    TransitionBegin {
        /// Transition name.
        name: String,
        /// Transition type.
        #[serde(rename = "type")]
        ty: String,
        /// Transition duration (in milliseconds). Will be -1 for any transition with a fixed
        /// duration, such as a Stinger, due to limitations of the OBS API.
        #[serde(deserialize_with = "crate::de::duration_millis_opt")]
        duration: Option<Duration>,
        /// Source scene of the transition.
        from_scene: Option<String>,
        /// Destination scene of the transition.
        to_scene: String,
    },
    /// A transition (other than "cut") has ended.
    ///
    /// Note: The `from-scene` field is not available in TransitionEnd.
    #[serde(rename_all = "kebab-case")]
    TransitionEnd {
        /// Transition name.
        name: String,
        /// Transition type.
        #[serde(rename = "type")]
        ty: String,
        /// Transition duration (in milliseconds).
        #[serde(deserialize_with = "crate::de::duration_millis")]
        duration: Duration,
        /// Destination scene of the transition.
        to_scene: String,
    },
    /// A stinger transition has finished playing its video.
    #[serde(rename_all = "kebab-case")]
    TransitionVideoEnd {
        /// Transition name.
        name: String,
        /// Transition type.
        #[serde(rename = "type")]
        ty: String,
        /// Transition duration (in milliseconds).
        #[serde(deserialize_with = "crate::de::duration_millis")]
        duration: Duration,
        /// Source scene of the transition.
        from_scene: Option<String>,
        /// Destination scene of the transition.
        to_scene: String,
    },
    // --------------------------------
    // Profiles
    // --------------------------------
    /// Triggered when switching to another profile or when renaming the current profile.
    ProfileChanged {
        /// Name of the new current profile.
        profile: String,
    },
    /// Triggered when a profile is created, added, renamed, or removed.
    ProfileListChanged {
        /// Profiles list.
        profiles: Vec<Profile>,
    },
    // --------------------------------
    // Streaming
    // --------------------------------
    /// A request to start streaming has been issued.
    #[serde(rename_all = "kebab-case")]
    StreamStarting {
        /// Always false (retrocompatibility).
        #[serde(default)]
        preview_only: bool,
    },
    /// Streaming started successfully.
    StreamStarted,
    /// A request to stop streaming has been issued.
    #[serde(rename_all = "kebab-case")]
    StreamStopping {
        /// Always false (retrocompatibility).
        #[serde(default)]
        preview_only: bool,
    },
    /// Streaming stopped successfully.
    StreamStopped,
    /// Emitted every 2 seconds when stream is active.
    #[serde(rename_all = "kebab-case")]
    StreamStatus {
        /// Current streaming state.
        streaming: bool,
        /// Current recording state.
        recording: bool,
        /// Replay Buffer status.
        replay_buffer_active: bool,
        /// Amount of data per second (in bytes) transmitted by the stream encoder.
        bytes_per_sec: u64,
        /// Amount of data per second (in kilobits) transmitted by the stream encoder.
        kbits_per_sec: u64,
        /// Percentage of dropped frames.
        strain: f64,
        /// Total time (in seconds) since the stream started.
        total_stream_time: u64,
        /// Total number of frames transmitted since the stream started.
        num_total_frames: u64,
        /// Number of frames dropped by the encoder since the stream started.
        num_dropped_frames: u64,
        /// Current framerate.
        fps: f64,
        /// Number of frames rendered.
        render_total_frames: u64,
        /// Number of frames missed due to rendering lag.
        render_missed_frames: u64,
        /// Number of frames outputted.
        output_total_frames: u64,
        /// Number of frames skipped due to encoding lag.
        output_skipped_frames: u64,
        /// Average frame time (in milliseconds).
        average_frame_time: f64,
        /// Current CPU usage (percentage).
        cpu_usage: f64,
        /// Current RAM usage (in megabytes).
        memory_usage: f64,
        /// Free recording disk space (in megabytes).
        free_disk_space: f64,
        /// Always false (retrocompatibility).
        #[serde(default)]
        preview_only: bool,
    },
    // --------------------------------
    // Recording
    // --------------------------------
    /// A request to start recording has been issued.
    ///
    /// Note: `recordingFilename` is not provided in this event because this information is not
    /// available at the time this event is emitted.
    RecordingStarting,
    /// Recording started successfully.
    #[serde(rename_all = "camelCase")]
    RecordingStarted {
        /// Absolute path to the file of the current recording.
        recording_filename: String,
    },
    /// A request to stop recording has been issued.
    #[serde(rename_all = "camelCase")]
    RecordingStopping {
        /// Absolute path to the file of the current recording.
        recording_filename: String,
    },
    /// Recording stopped successfully.
    #[serde(rename_all = "camelCase")]
    RecordingStopped {
        /// Absolute path to the file of the current recording.
        recording_filename: String,
    },
    /// Current recording paused.
    RecordingPaused,
    /// Current recording resumed.
    RecordingResumed,
    // --------------------------------
    // Replay Buffer
    // --------------------------------
    /// A request to start the replay buffer has been issued.
    ReplayStarting,
    /// Replay Buffer started successfully.
    ReplayStarted,
    /// A request to stop the replay buffer has been issued.
    ReplayStopping,
    /// Replay Buffer stopped successfully.
    ReplayStopped,
    // --------------------------------
    // Other
    // --------------------------------
    /// OBS is exiting.
    Exiting,
    // --------------------------------
    // General
    // --------------------------------
    /// A custom broadcast message, sent by the server, requested by one of the websocket clients.
    BroadcastCustomMessage {
        /// Identifier provided by the sender.
        realm: String,
        /// User-defined data.
        data: serde_json::Map<String, serde_json::Value>,
    },
    // --------------------------------
    // Sources
    // --------------------------------
    /// A source has been created. A source can be an input, a scene or a transition.
    #[serde(rename_all = "camelCase")]
    SourceCreated {
        /// Source name.
        source_name: String,
        /// Source type. Can be "input", "scene", "transition" or "filter".
        source_type: SourceType,
        /// Source kind.
        source_kind: String,
        /// Source settings.
        source_settings: serde_json::Value,
    },
    /// A source has been destroyed/removed. A source can be an input, a scene or a transition.
    #[serde(rename_all = "camelCase")]
    SourceDestroyed {
        /// Source name.
        source_name: String,
        /// Source type. Can be "input", "scene", "transition" or "filter".
        source_type: SourceType,
        /// Source kind.
        source_kind: String,
    },
    /// The volume of a source has changed.
    #[serde(rename_all = "camelCase")]
    SourceVolumeChanged {
        /// Source name.
        source_name: String,
        /// Source volume.
        volume: f32,
    },
    /// A source has been muted or unmuted.
    #[serde(rename_all = "camelCase")]
    SourceMuteStateChanged {
        /// Source name.
        source_name: String,
        /// Mute status of the source.
        muted: bool,
    },
    /// A source has removed audio.
    #[serde(rename_all = "camelCase")]
    SourceAudioDeactivated {
        /// Source name.
        source_name: String,
    },
    /// A source has added audio.
    #[serde(rename_all = "camelCase")]
    SourceAudioActivated {
        /// Source name.
        source_name: String,
    },
    /// The audio sync offset of a source has changed.
    #[serde(rename_all = "camelCase")]
    SourceAudioSyncOffsetChanged {
        /// Source name.
        source_name: String,
        /// Audio sync offset of the source (in nanoseconds).
        #[serde(deserialize_with = "crate::de::duration_nanos")]
        sync_offset: Duration,
    },
    /// Audio mixer routing changed on a source.
    #[serde(rename_all = "camelCase")]
    SourceAudioMixersChanged {
        /// Source name.
        source_name: String,
        /// Routing status of the source for each audio mixer (array of 6 values).
        mixers: [AudioMixer; 6],
        /// Raw mixer flags (little-endian, one bit per mixer) as an hexadecimal value.
        hex_mixers_value: String,
    },
    /// A source has been renamed.
    #[serde(rename_all = "camelCase")]
    SourceRenamed {
        /// Previous source name.
        previous_name: String,
        /// New source name.
        new_name: String,
        /// Type of source (input, scene, filter, transition).
        source_type: SourceType,
    },
    /// A filter was added to a source.
    #[serde(rename_all = "camelCase")]
    SourceFilterAdded {
        /// Source name.
        source_name: String,
        /// Filter name.
        filter_name: String,
        /// Filter type.
        filter_type: String,
        /// Filter settings.
        filter_settings: serde_json::Value,
    },
    /// A filter was removed from a source.
    #[serde(rename_all = "camelCase")]
    SourceFilterRemoved {
        /// Source name.
        source_name: String,
        /// Filter name.
        filter_name: String,
        /// Filter type.
        filter_type: String,
    },
    /// The visibility/enabled state of a filter changed.
    #[serde(rename_all = "camelCase")]
    SourceFilterVisibilityChanged {
        /// Source name.
        source_name: String,
        /// Filter name.
        filter_name: String,
        /// New filter state.
        filter_enabled: bool,
    },
    /// Filters in a source have been reordered.
    #[serde(rename_all = "camelCase")]
    SourceFiltersReordered {
        /// Source name.
        source_name: String,
        /// Ordered Filters list.
        filters: Vec<SourceFilter>,
    },
    // --------------------------------
    // Media
    // --------------------------------
    /// Media is playing.
    ///
    /// Note: This event is only emitted when something actively controls the media/VLC source. In
    /// other words, the source will never emit this on its own naturally.
    #[serde(rename_all = "camelCase")]
    MediaPlaying {
        /// Source name.
        source_name: String,
        /// The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`).
        source_kind: String,
    },
    /// Media playback paused.
    ///
    /// Note: This event is only emitted when something actively controls the media/VLC source. In
    /// other words, the source will never emit this on its own naturally.
    #[serde(rename_all = "camelCase")]
    MediaPaused {
        /// Source name.
        source_name: String,
        /// The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`).
        source_kind: String,
    },
    /// Media playback restarted.
    ///
    /// Note: This event is only emitted when something actively controls the media/VLC source. In
    /// other words, the source will never emit this on its own naturally.
    #[serde(rename_all = "camelCase")]
    MediaRestarted {
        /// Source name.
        source_name: String,
        /// The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`).
        source_kind: String,
    },
    /// Media playback stopped.
    ///
    /// Note: This event is only emitted when something actively controls the media/VLC source. In
    /// other words, the source will never emit this on its own naturally.
    #[serde(rename_all = "camelCase")]
    MediaStopped {
        /// Source name.
        source_name: String,
        /// The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`).
        source_kind: String,
    },
    /// Next media started.
    ///
    /// Note: This event is only emitted when something actively controls the media/VLC source. In
    /// other words, the source will never emit this on its own naturally.
    #[serde(rename_all = "camelCase")]
    MediaNext {
        /// Source name.
        source_name: String,
        /// The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`).
        source_kind: String,
    },
    /// Previous media started.
    ///
    /// Note: This event is only emitted when something actively controls the media/VLC source. In
    /// other words, the source will never emit this on its own naturally.
    #[serde(rename_all = "camelCase")]
    MediaPrevious {
        /// Source name.
        source_name: String,
        /// The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`).
        source_kind: String,
    },
    /// Media playback started.
    ///
    /// Note: This event is only emitted when something actively controls the media/VLC source. In
    /// other words, the source will never emit this on its own naturally.
    #[serde(rename_all = "camelCase")]
    MediaStarted {
        /// Source name.
        source_name: String,
        /// The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`).
        source_kind: String,
    },
    /// Media playback ended.
    ///
    /// Note: This event is only emitted when something actively controls the media/VLC source. In
    /// other words, the source will never emit this on its own naturally.
    #[serde(rename_all = "camelCase")]
    MediaEnded {
        /// Source name.
        source_name: String,
        /// The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`).
        source_kind: String,
    },
    // --------------------------------
    // Scene Items
    // --------------------------------
    /// Scene items within a scene have been reordered.
    #[serde(rename_all = "kebab-case")]
    SourceOrderChanged {
        /// Name of the scene where items have been reordered.
        scene_name: String,
        /// Ordered list of scene items.
        scene_items: Vec<SourceOrderSceneItem>,
    },
    /// A scene item has been added to a scene.
    #[serde(rename_all = "kebab-case")]
    SceneItemAdded {
        /// Name of the scene.
        scene_name: String,
        /// Name of the item added to the scene.
        item_name: String,
        /// Scene item ID.
        item_id: i64,
    },
    /// A scene item has been removed from a scene.
    #[serde(rename_all = "kebab-case")]
    SceneItemRemoved {
        /// Name of the scene.
        scene_name: String,
        /// Name of the item removed from the scene.
        item_name: String,
        /// Scene item ID.
        item_id: i64,
    },
    /// A scene item's visibility has been toggled.
    #[serde(rename_all = "kebab-case")]
    SceneItemVisibilityChanged {
        /// Name of the scene.
        scene_name: String,
        /// Name of the item in the scene.
        item_name: String,
        /// Scene item ID.
        item_id: i64,
        /// New visibility state of the item.
        item_visible: bool,
    },
    /// A scene item's locked status has been toggled.
    #[serde(rename_all = "kebab-case")]
    SceneItemLockChanged {
        /// Name of the scene.
        scene_name: String,
        /// Name of the item in the scene.
        item_name: String,
        /// Scene item ID.
        item_id: i64,
        /// New locked state of the item.
        item_locked: bool,
    },
    /// A scene item's transform has been changed.
    #[serde(rename_all = "kebab-case")]
    SceneItemTransformChanged {
        /// Name of the scene.
        scene_name: String,
        /// Name of the item in the scene.
        item_name: String,
        /// Scene item ID.
        item_id: i64,
        /// Scene item transform properties.
        transform: SceneItemTransform,
    },
    /// A scene item is selected.
    #[serde(rename_all = "kebab-case")]
    SceneItemSelected {
        /// Name of the scene.
        scene_name: String,
        /// Name of the item in the scene.
        item_name: String,
        /// ID of the item in the scene.
        item_id: i64,
    },
    /// A scene item is deselected.
    #[serde(rename_all = "kebab-case")]
    SceneItemDeselected {
        /// Name of the scene.
        scene_name: String,
        /// Name of the item in the scene.
        item_name: String,
        /// ID of the item in the scene.
        item_id: i64,
    },
    // --------------------------------
    // Studio Mode
    // --------------------------------
    /// The selected preview scene has changed (only available in Studio Mode).
    #[serde(rename_all = "kebab-case")]
    PreviewSceneChanged {
        /// Name of the scene being previewed.
        scene_name: String,
        /// List of sources composing the scene.
        sources: Vec<SceneItem>,
    },
    /// Studio Mode has been enabled or disabled.
    #[serde(rename_all = "kebab-case")]
    StudioModeSwitched {
        /// The new enabled state of Studio Mode.
        new_state: bool,
    },
    /// WebSocket server is stopping.
    ServerStopping,
    /// WebSocket server has stopped.
    ServerStopped,
    /// Fallback value for any unknown event type.
    #[serde(other)]
    Unknown,
}

/// Part of [`EventType::ScenesChanged`].
#[derive(Clone, Debug, Deserialize)]
pub struct Scene {
    /// Name of the currently active scene.
    pub name: String,
    /// Ordered list of the current scene's source items.
    pub sources: Vec<SceneItem>,
}

/// Part of [`EventType::SceneCollectionListChanged`].
#[derive(Clone, Debug, Deserialize)]
pub struct SceneCollection {
    /// Scene collection name.
    pub name: String,
}

/// Part of [`EventType::TransitionListChanged`].
#[derive(Clone, Debug, Deserialize)]
pub struct Transition {
    /// Transition name.
    pub name: String,
}

/// Part of [`EventType::ProfileListChanged`].
#[derive(Clone, Debug, Deserialize)]
pub struct Profile {
    /// Profile name.
    pub name: String,
}

/// Part of [`EventType::SourceAudioMixersChanged`].
#[derive(Clone, Debug, Deserialize)]
pub struct AudioMixer {
    /// Mixer number.
    pub id: i64,
    /// Routing status.
    pub enabled: bool,
}

/// Part of [`EventType::SourceFiltersReordered`].
#[derive(Clone, Debug, Deserialize)]
pub struct SourceFilter {
    /// Filter name.
    pub name: String,
    /// Filter type.
    #[serde(rename = "type")]
    pub ty: String,
    /// Filter visibility status.
    pub enabled: bool,
}

/// Part of [`EventType::SourceOrderChanged`].
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct SourceOrderSceneItem {
    /// Item source name.
    pub source_name: String,
    /// Scene item unique ID.
    pub item_id: i64,
}

/// Part of [`EventType::SourceCreated`], [`EventType::SourceDestroyed`] and
/// [`EventType::SourceRenamed`].
#[derive(Clone, Copy, Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceType {
    /// An input source.
    Input,
    /// A scene.
    Scene,
    /// Transition between scenes.
    Transition,
    /// Filter for scene items.
    Filter,
}
