//! Event channel from Rust to the webview.
//!
//! The frontend is a *view* (§4.3): it never computes ride state, it renders
//! what arrives here. Every event name is declared once, in this module, and
//! mirrored in `ui/src/lib/events.ts`.

use bikecontrol_core::types::{ConnectionState, ControlMode, ControlTarget};
use serde::Serialize;

use crate::devices::DeviceInfo;
use crate::profile_view::ProfileView;
use crate::trainer::TrainerStatus;

/// `RideSnapshot`, pushed at [`crate::engine::TICK_HZ`].
pub const RIDE_SNAPSHOT: &str = "ride://snapshot";
/// Low-frequency ride state: status, mode, targets, laps, loaded profile.
pub const RIDE_STATE: &str = "ride://state";
/// A lap marker was inserted (FR-3.19 / FR-8.7).
pub const RIDE_LAP: &str = "ride://lap";
/// The ride ended and its activity was written (FR-9.13). Carries a
/// `crate::recording::RideSummary`.
pub const RIDE_SUMMARY: &str = "ride://summary";
/// The full device list changed (FR-9.1).
pub const DEVICES_UPDATED: &str = "devices://updated";
/// One device changed connection or control state (FR-1.7, FR-9.3).
pub const DEVICE_CONNECTION: &str = "devices://connection";
/// User-facing message: confirmation, warning or error (FR-9.2).
pub const APP_NOTICE: &str = "app://notice";
/// Acknowledgement that an input registered, so the UI can flash (FR-9.9).
pub const INPUT_ACK: &str = "app://input-ack";
/// A Zwift Click button edge. The webview routes these to the same intents as
/// the equivalent keypress, so controller and keyboard cannot drift apart.
pub const CONTROLLER_INPUT: &str = "controller://input";
/// Controller link state and battery.
pub const CONTROLLER_STATUS: &str = "controller://status";

/// Ride lifecycle, mirroring `bikecontrol_core::session::RideStatus` but
/// serialisable across the IPC boundary.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum RideStatus {
    Idle,
    Running,
    Paused,
    Finished,
}

/// Everything the ride screen needs that is *not* in a `RideSnapshot`.
/// Emitted on change, not on a timer.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RideState {
    pub status: RideStatus,
    pub mode: ControlMode,
    pub target: Option<ControlTarget>,
    /// Manual gradient trim on top of the profile's base gradient (FR-4.2).
    pub gradient_offset_pct: f32,
    pub manual_gradient_pct: f32,
    pub resistance_level: i16,
    pub power_target_w: u16,
    /// Selected virtual gear, one-based, and how many there are (FR-4.1).
    pub gear: usize,
    pub gear_count: usize,
    pub lap: u32,
    pub laps: Vec<LapSummary>,
    pub profile: Option<ProfileView>,
    /// Trainer link, so the ride screen can say when the numbers stopped being
    /// real rather than quietly showing zeros (FR-1.8, FR-9.3).
    pub trainer: TrainerStatus,
}

#[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LapSummary {
    pub index: u32,
    pub elapsed_ms: u64,
    pub distance_m: f64,
    pub avg_power_w: f32,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ConnectionEvent {
    pub device_id: String,
    pub state: ConnectionState,
    /// FTMS control point acquired. Connected is *not* controllable (FR-9.3).
    pub control_acquired: bool,
    pub error: Option<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum NoticeLevel {
    Info,
    Warn,
    Error,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Notice {
    pub level: NoticeLevel,
    pub message: String,
}

impl Notice {
    pub fn info(message: impl Into<String>) -> Self {
        Self {
            level: NoticeLevel::Info,
            message: message.into(),
        }
    }
    pub fn warn(message: impl Into<String>) -> Self {
        Self {
            level: NoticeLevel::Warn,
            message: message.into(),
        }
    }
    pub fn error(message: impl Into<String>) -> Self {
        Self {
            level: NoticeLevel::Error,
            message: message.into(),
        }
    }
}

/// Confirms an intent was accepted, so the UI can flash the control (FR-9.9).
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InputAck {
    pub action: String,
    pub detail: Option<String>,
}

/// The full device list.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DeviceList {
    pub scanning: bool,
    pub devices: Vec<DeviceInfo>,
}
