//! # `chanora_protocol`
//!
//! TeamSpeak-compatible protocol adapter. Isolates `tsclientlib`
//! behind a typed boundary so the rest of Chanora is decoupled from
//! the upstream library's types (SAD-067, SysDes-011, SysDes-029).
//!
//! ## What this crate exposes
//!
//! * [`ConnectConfig`] — typed connection parameters.
//! * [`ProtocolClient`] — async handle owning the connection task.
//! * [`ServerSnapshot`], [`ChannelInfo`], [`ClientInfo`] — opaque
//!   DTOs containing only `String`s and primitives.
//! * [`ProtocolError`] — typed error catalogue.
//!
//! ## What this crate does NOT expose
//!
//! * `tsclientlib::*` types.
//! * `tsproto::*` types.
//! * Any audio-related types — those live in `chanora_audio`.
//!
//! Promoted from `poc/tsclientlib-connect-spike` on 2026-05-14
//! as part of the Alpha build.
//!
//! ## Hostname resolution (A.1)
//!
//! Upstream `tsclientlib` uses `hickory-resolver` which reads
//! `/etc/resolv.conf`. That file does not exist on Android or iOS,
//! and the Beta UI surfaced the resulting cryptic "connection task
//! exited before signalling ready" errors. We side-step the issue by
//! resolving hostnames ourselves with `tokio::net::lookup_host`,
//! which uses platform `getaddrinfo` (works correctly on every
//! supported platform), and feeding the resulting `SocketAddr`
//! directly to `tsclientlib::Connection::build`. A small in-process
//! positive-result cache keeps reconnects fast.

#![forbid(unsafe_code)]
#![warn(missing_docs)]

mod adapter;
mod dto;
mod resolver;

pub use adapter::{ConnectConfig, DisconnectReason, InboundVoice, ProtocolClient, SnapshotProbe};
pub use dto::{ChannelId, ChannelInfo, ClientId, ClientInfo, ServerSnapshot};

// Re-export the upstream voice types so chanora_audio can build outbound
// voice packets without taking a direct dependency on tsclientlib /
// tsproto_packets. Per SAD-067 this is the *one* deliberate
// re-export: the audio path is performance-sensitive and a parallel
// type hierarchy would force copies for every 20 ms frame.
pub use tsproto_packets::packets::{
    AudioData, CodecType, Direction, InAudioBuf, OutAudio, OutPacket,
};

use thiserror::Error;

/// Errors surfaced by the protocol adapter. None of these expose
/// `tsclientlib`-specific types; raw upstream errors are mapped here
/// to typed arms.
#[derive(Debug, Error)]
pub enum ProtocolError {
    /// Configuration is invalid before any I/O is attempted (bad
    /// hostname, missing identity, etc.).
    #[error("invalid protocol configuration: {0}")]
    Invalid(String),

    /// Hostname resolution failed. Distinct from [`Self::Connect`]
    /// so the UI can show a meaningful "Server not found" message
    /// instead of a generic connection error.
    #[error("dns lookup failed for '{host}': {reason}")]
    DnsFailed {
        /// The hostname (or `host:port`) the caller submitted.
        host: String,
        /// Reason from the platform resolver.
        reason: String,
    },

    /// Failed to dial / handshake with the server.
    #[error("connect failed: {0}")]
    Connect(String),

    /// The connection ended before becoming ready.
    #[error("disconnected before ready: {0}")]
    DisconnectedEarly(String),

    /// Connection lost after becoming ready.
    #[error("connection lost: {0}")]
    Lost(String),

    /// Identity parsing failed.
    #[error("identity error: {0}")]
    Identity(String),

    /// Operation timed out.
    #[error("protocol timeout")]
    Timeout,

    /// A backend error escaped the mapping. Production callers
    /// should never see this; if they do, it is a mapping bug here.
    #[error("protocol backend: {0}")]
    Backend(String),
}
