//! # `chanora_bridge`
//!
//! Typed Flutter/Rust bridge — schema-controlled DTOs for commands,
//! results, and events. Backed by `flutter_rust_bridge` 2.x per
//! DEC-014.
//!
//! ## Alpha scope
//!
//! Exposes three commands that target the Alpha release goal:
//!
//! * `bridge_init()` — one-time process initialisation. Sets up
//!   logging.
//! * `connect(host, nickname)` — connects to a TS3-compatible
//!   server and returns a typed [`ConnectResultDto`] containing the
//!   server snapshot.
//! * `disconnect()` — clean disconnect.
//! * `snapshot()` — re-fetch the current server snapshot.
//!
//! No audio commands cross the bridge in Alpha; audio is Beta scope.
//!
//! ## Boundary discipline
//!
//! Every type in this module is built from owned primitives or
//! `String`s — no `tsclientlib`, `cpal`, or backend types may
//! appear in the public surface (SAD-067, SDD-079). Cross-language
//! serialisation is handled by `flutter_rust_bridge`'s generated
//! glue, so most public DTOs and the `BridgeEvent` enum intentionally
//! do *not* carry `serde::{Serialize, Deserialize}` derives — FRB
//! emits its own SSE encoders/decoders. `BridgeError` carries serde
//! derives historically; new bridge types should follow the
//! FRB-only convention unless an explicit non-FRB consumer is added.
//!
//! Note: this crate cannot use `#![forbid(unsafe_code)]` because the
//! FRB-generated glue (in `frb_generated`) legitimately uses unsafe
//! for the FFI boundary. Hand-written code in this crate must
//! nonetheless avoid `unsafe` and is held to that standard by review.

#![warn(missing_docs)]

pub mod api;
mod frb_generated;

#[cfg(target_os = "android")]
mod android_init;

#[cfg(target_os = "android")]
mod permission_jni;

use thiserror::Error;

/// Errors raised at the bridge boundary. Production code must keep
/// these user-safe — no secrets, no protocol details, no path
/// information beyond what the redaction policy permits.
#[derive(Debug, Error, Clone, serde::Serialize, serde::Deserialize)]
pub enum BridgeError {
    /// The caller submitted a malformed command DTO.
    #[error("invalid command: {0}")]
    InvalidCommand(String),
    /// Hostname resolution failed. Distinct from `Connection` so the
    /// UI can show a meaningful "Server not found" message.
    #[error("dns: could not resolve '{host}': {reason}")]
    DnsFailed {
        /// The hostname (or `host:port`) the caller submitted.
        host: String,
        /// Reason from the platform resolver.
        reason: String,
    },
    /// Connection layer failure (typed-mapped from CoreError).
    #[error("connection: {0}")]
    Connection(String),
    /// Operation requires an active connection.
    #[error("not connected")]
    NotConnected,
    /// Already connected; DEC-006 forbids a second concurrent
    /// connection.
    #[error("already connected")]
    AlreadyConnected,
    /// A server command was rejected by the TeamSpeak server. The
    /// `code` is the canonical TS3 error number (see
    /// https://github.com/ReSpeak/tsdeclarations Errors.csv); the
    /// `message` is the server-supplied text. The UI uses `code`
    /// to look up a localised explanation.
    #[error("server rejected (code {code}): {message}")]
    ServerRejected {
        /// Raw TS3 error code.
        code: u32,
        /// Server-supplied message text.
        message: String,
    },
    /// An unmapped error escaped the subsystem boundary. Production
    /// callers should never see this; if they do, it is a mapping
    /// bug here.
    #[error("unmapped: {0}")]
    Unmapped(String),
}

impl From<chanora_core::CoreError> for BridgeError {
    fn from(e: chanora_core::CoreError) -> Self {
        match e {
            chanora_core::CoreError::NotConnected => BridgeError::NotConnected,
            chanora_core::CoreError::AlreadyConnected => BridgeError::AlreadyConnected,
            chanora_core::CoreError::AudioNotStarted => {
                BridgeError::InvalidCommand("audio not started".to_string())
            }
            chanora_core::CoreError::Protocol(chanora_core::ProtocolError::DnsFailed {
                host,
                reason,
            }) => BridgeError::DnsFailed { host, reason },
            chanora_core::CoreError::Protocol(chanora_core::ProtocolError::ServerRejected {
                code,
                message,
            }) => BridgeError::ServerRejected { code, message },
            chanora_core::CoreError::Protocol(p) => BridgeError::Connection(format!("{p}")),
            chanora_core::CoreError::Audio(a) => BridgeError::Connection(format!("audio: {a}")),
            chanora_core::CoreError::Storage(s) => BridgeError::Connection(format!("storage: {s}")),
            other => BridgeError::Unmapped(format!("{other}")),
        }
    }
}
