/**
 * TypeScript mirror of the Rust payloads.
 *
 * The authoritative definitions are `crates/core/src/types.rs` (frozen),
 * `src-tauri/src/events.rs`, `src-tauri/src/derive.rs` and
 * `src-tauri/src/profile_view.rs`. Nothing here is computed — these are shapes
 * that arrive over the event channel.
 */

// --- crates/core/src/types.rs (frozen contract) -----------------------------

export interface Telemetry {
  elapsedMs: number;
  power_w: number | null;
  cadence_rpm: number | null;
  speed_kph: number | null;
  resistance_level: number | null;
  heart_rate_bpm: number | null;
  total_distance_m: number | null;
  total_energy_kcal: number | null;
}

/** Serde externally-tagged enum. */
export type ControlTarget =
  | { Gradient: { percent: number } }
  | { Resistance: { level: number } }
  | { Power: { watts: number } };

export type ControlMode = 'ManualGrade' | 'Resistance' | 'Profile' | 'Erg';

export type ConnectionState =
  | 'Idle'
  | 'Scanning'
  | 'Connecting'
  | 'Connected'
  | 'Controlling'
  | 'Reconnecting'
  | { Lost: { reason: string } };

export interface RideSnapshot {
  elapsed_ms: number;
  telemetry: {
    elapsed_ms: number;
    power_w: number | null;
    cadence_rpm: number | null;
    speed_kph: number | null;
    resistance_level: number | null;
    heart_rate_bpm: number | null;
    total_distance_m: number | null;
    total_energy_kcal: number | null;
  };
  virtual_speed_kph: number;
  virtual_distance_m: number;
  /** The gradient of the *road*. What the trainer was asked for is `target`. */
  gradient_pct: number;
  elevation_gain_m: number;
  /** Selected virtual gear, one-based (FR-4.1). */
  gear: number;
  gear_count: number;
  /** Metres travelled per crank revolution in the selected gear. */
  development_m: number;
  /** Cadence the selected gear implies at the current speed. */
  target_cadence_rpm: number;
  /** Which rule set the speed. `NoCadence` is a fault, not a mode. */
  speed_source: 'Drivetrain' | 'Coasting' | 'NoCadence' | 'Stopped';
  /** Force at the pedal, newtons — the commanded load as the legs feel it. */
  pedal_force_n: number;
  mode: ControlMode;
  target: ControlTarget | null;
  profile_progress: number | null;
}

export interface RiderConfig {
  rider_kg: number;
  bike_kg: number;
  crr: number;
  cda: number;
  drivetrain_efficiency: number;
  air_density: number;
  wheel_circumference_m: number;
  /** Crank length (pedal circle radius), metres. Road bikes are 0.170–0.175. */
  crank_length_m: number;
  descent_load_floor_pct: number;
  /** Development of the real gear through the Zwift Cog, m per crank rev. */
  physical_development_m: number;
}

export interface SafetyLimits {
  min_gradient_pct: number;
  max_gradient_pct: number;
  min_resistance: number;
  max_resistance: number;
  min_power_w: number;
  max_power_w: number;
}

// --- src-tauri/src/derive.rs -------------------------------------------------

export type EtaKind = 'exact' | 'estimated' | 'held' | 'looping' | 'unavailable';
export type XUnit = 'seconds' | 'metres';

export interface Derived {
  etaKind: EtaKind;
  timeRemainingS: number | null;
  distanceTotalM: number | null;
  distanceRemainingM: number | null;
  elevationM: number | null;
  ascentRemainingM: number | null;
  positionX: number;
  axisUnit: XUnit;
  axisTotal: number;
  loopIndex: number | null;
  /** 45 s mean, for the ETA. Too slow to show a gear change — see below. */
  smoothedSpeedKph: number;
  /** 3 s mean: what the speedometer shows, so a shift is visible at once. */
  displaySpeedKph: number;
  rollingPowerW: number;
  rollingPowerWindowS: number;
  avgPowerW: number;
  maxPowerW: number;
  normalisedPowerW: number | null;
  avgCadenceRpm: number;
  energyKj: number;
  /** Estimated rider energy expenditure, kcal — not the same as `energyKj`. */
  caloriesKcal: number;
}

/** What arrives on `ride://snapshot`. */
export interface RideFrame {
  snapshot: RideSnapshot;
  derived: Derived;
}

// --- src-tauri/src/profile_view.rs ------------------------------------------

export type Channel = 'gradient' | 'resistance' | 'power';

export interface BlockSummary {
  index: number;
  kind: 'constant' | 'ramp' | 'wave' | 'segments' | 'terrain';
  channel: Channel;
  label: string;
  startX: number;
  endX: number;
  unit: XUnit;
}

export interface ProfileView {
  name: string;
  description: string | null;
  looping: boolean;
  source: string;
  channel: Channel;
  xUnit: XUnit;
  totalX: number;
  totalSeconds: number | null;
  totalMetres: number | null;
  series: [number, number][];
  elevation: [number, number][] | null;
  elevationMinM: number | null;
  elevationMaxM: number | null;
  totalAscentM: number | null;
  blocks: BlockSummary[];
  yaml: string;
}

// --- src-tauri/src/events.rs -------------------------------------------------

export type RideStatus = 'idle' | 'running' | 'paused' | 'finished';

export interface LapSummary {
  index: number;
  elapsedMs: number;
  distanceM: number;
  avgPowerW: number;
}

// --- src-tauri/src/recording.rs ----------------------------------------------

/** A finished ride, as the summary screen shows it (FR-9.13, FR-9.14). */
export interface RideSummary {
  durationS: number;
  /** Excludes time spent paused. */
  movingS: number;
  distanceM: number;
  ascentM: number;
  avgPowerW: number | null;
  maxPowerW: number | null;
  avgCadenceRpm: number | null;
  calories: number | null;
  records: number;
  laps: number;
  /** BLE dropouts spanned (FR-8.5). */
  gaps: number;
  /** True when the activity was rebuilt from a journal with no end marker. */
  recoveredFromCrash: boolean;
  /** Journal lines that could not be parsed. Non-zero means data was lost. */
  skippedLogLines: number;
  /** The automatic copy, in the app's data directory. */
  fitPath: string;
  /** Where the rider chose to save it, once they have (FR-9.14). */
  savedPath: string | null;
}

/** A ride rebuilt from an interrupted session at startup (FR-8.4). */
export interface Recovered {
  fitPath: string;
  summary: RideSummary;
}

/** Trainer link state, mirrored from `src-tauri/src/trainer.rs`. */
export interface TrainerStatus {
  state: ConnectionState;
  /** FTMS control point acquired. Connected is NOT controllable (FR-9.3). */
  controlAcquired: boolean;
  address: string | null;
  name: string | null;
  error: string | null;
  /** Connected, but no Indoor Bike Data for several seconds (FR-1.8). */
  stale: boolean;
}

export interface RideState {
  status: RideStatus;
  mode: ControlMode;
  target: ControlTarget | null;
  gradientOffsetPct: number;
  manualGradientPct: number;
  resistanceLevel: number;
  powerTargetW: number;
  /** Selected virtual gear, one-based, and how many there are (FR-4.1). */
  gear: number;
  gearCount: number;
  lap: number;
  laps: LapSummary[];
  profile: ProfileView | null;
  trainer: TrainerStatus;
}

/** Click pods are named for the shift paddle each carries — see `Pod` in
 *  `bridge.ts` for why that is not left and right. */
export type DeviceKind = 'trainer' | 'clickMinus' | 'clickPlus' | 'heartRate' | 'unknown';

export interface DeviceInfo {
  id: string;
  name: string;
  address: string;
  rssi: number;
  kind: DeviceKind;
  state: ConnectionState;
  controlAcquired: boolean;
  services: string[];
  remembered: boolean;
  batteryPct: number | null;
  /** Live reading from a connected heart rate monitor — proof data is flowing. */
  heartRateBpm: number | null;
  error: string | null;
}

export interface DeviceList {
  scanning: boolean;
  devices: DeviceInfo[];
}

export interface Notice {
  level: 'info' | 'warn' | 'error';
  message: string;
}

export interface InputAck {
  action: string;
  detail: string | null;
}

export interface SampleProfile {
  name: string;
  summary: string;
  text: string;
  isGpx: boolean;
}
