use crate::pool::slot::WRITE_BUF_CAP;

/// Storage abstraction for a slot's outbound staging buffer.
///
/// Stable Rust can't write `[u8; <L as CodecLayer>::WRITE_BUF_CAP]` because
/// the array length isn't known unless `generic_const_exprs` is enabled, so
/// the buffer type is associated rather than const-sized: `()` for layers
/// that don't stage (zero bytes per slot), [`WriteBufArr`] for layers that
/// do.
pub trait WriteBufStorage: Default + 'static {
    fn as_slice(&self) -> &[u8];
    fn as_mut_slice(&mut self) -> &mut [u8];
}

impl WriteBufStorage for () {
    #[inline(always)]
    fn as_slice(&self) -> &[u8] {
        &[]
    }
    #[inline(always)]
    fn as_mut_slice(&mut self) -> &mut [u8] {
        &mut []
    }
}

/// Inline staging buffer used by codec layers. Newtype because `[u8; N]:
/// Default` only holds for small `N` on stable Rust.
#[repr(transparent)]
pub struct WriteBufArr(pub(crate) [u8; WRITE_BUF_CAP]);

impl Default for WriteBufArr {
    #[inline(always)]
    fn default() -> Self {
        Self([0u8; WRITE_BUF_CAP])
    }
}

impl WriteBufStorage for WriteBufArr {
    #[inline(always)]
    fn as_slice(&self) -> &[u8] {
        &self.0
    }
    #[inline(always)]
    fn as_mut_slice(&mut self) -> &mut [u8] {
        &mut self.0
    }
}

/// Pool-side dispatch for a connection's wire-flow.
///
/// Two kinds of impls exist:
///
/// * Passthrough — [`Plain`]. `IS_PASSTHROUGH = true`, `WriteBuf = ()` (ZST,
///   no per-slot staging cost), all codec methods are no-ops because pool
///   skips them via the const branch.
/// * Codec — e.g. `dope_tls::TlsCodec`. `IS_PASSTHROUGH = false`,
///   `WriteBuf = WriteBufArr`, all codec methods do real work.
///
/// Pool generics are over `L: CodecLayer`. Real codecs implement this trait
/// directly — there is no separate `Codec` / `Coded<C>` indirection.
///
/// Protocols that require a non-passthrough layer (e.g. a PostgreSQL client
/// that performs STARTTLS) bound on the concrete layer type:
///
/// ```ignore
/// type CodecLayer = dope_tls::LazyTlsCodec;
/// ```
pub trait CodecLayer: 'static {
    type ConnState: Default + 'static;
    type WriteBuf: WriteBufStorage;

    /// True if this layer bypasses the codec staging path.
    /// Pool branches on this const to pick the direct fill_msghdr path
    /// (passthrough) vs the write_buf → process_outbound → take_inflight
    /// path (codec). The branch is monomorphized → DCE.
    const IS_PASSTHROUGH: bool;

    fn is_active(state: &Self::ConnState) -> bool;
    fn process_inbound(state: &mut Self::ConnState, wire_in: &[u8]);
    fn process_outbound(state: &mut Self::ConnState, plaintext_in: &[u8]);
    fn take_plaintext(state: &mut Self::ConnState) -> Vec<u8>;
    fn install_plaintext(state: &mut Self::ConnState, plaintext: Vec<u8>);
    fn take_inflight(state: &mut Self::ConnState) -> Option<(*const u8, u32)>;
    fn consume_inflight(state: &mut Self::ConnState, n: usize) -> bool;
    fn has_inflight(state: &Self::ConnState) -> bool;
    fn wire_slice(state: &Self::ConnState) -> &[u8];
    fn close_pending(state: &Self::ConnState) -> bool;
}

/// Passthrough layer: no codec, no staging buffer, no codec state.
///
/// All trait methods are no-ops; pool's `IS_PASSTHROUGH` branch never
/// dispatches them.
pub struct Plain;

impl CodecLayer for Plain {
    type ConnState = ();
    type WriteBuf = ();
    const IS_PASSTHROUGH: bool = true;

    #[inline(always)]
    fn is_active(_: &()) -> bool {
        true
    }
    #[inline(always)]
    fn process_inbound(_: &mut (), _: &[u8]) {}
    #[inline(always)]
    fn process_outbound(_: &mut (), _: &[u8]) {}
    #[inline(always)]
    fn take_plaintext(_: &mut ()) -> Vec<u8> {
        Vec::new()
    }
    #[inline(always)]
    fn install_plaintext(_: &mut (), _: Vec<u8>) {}
    #[inline(always)]
    fn take_inflight(_: &mut ()) -> Option<(*const u8, u32)> {
        None
    }
    #[inline(always)]
    fn consume_inflight(_: &mut (), _: usize) -> bool {
        false
    }
    #[inline(always)]
    fn has_inflight(_: &()) -> bool {
        false
    }
    #[inline(always)]
    fn wire_slice(_: &()) -> &[u8] {
        &[]
    }
    #[inline(always)]
    fn close_pending(_: &()) -> bool {
        false
    }
}
