#[cfg(any(feature = "blocking-client", feature = "async-client"))]
mod error {
    use crate::handshake::refs::parse;

    /// The error returned by invoking a [`super::function::LsRefsCommand`].
    #[derive(Debug, thiserror::Error)]
    #[allow(missing_docs)]
    pub enum Error {
        #[error(transparent)]
        Io(#[from] std::io::Error),
        #[error(transparent)]
        Transport(#[from] gix_transport::client::Error),
        #[error(transparent)]
        Parse(#[from] parse::Error),
        #[error(transparent)]
        ArgumentValidation(#[from] crate::command::validate_argument_prefixes::Error),
    }

    impl gix_transport::IsSpuriousError for Error {
        fn is_spurious(&self) -> bool {
            match self {
                Error::Io(err) => err.is_spurious(),
                Error::Transport(err) => err.is_spurious(),
                _ => false,
            }
        }
    }
}
#[cfg(any(feature = "blocking-client", feature = "async-client"))]
pub use error::Error;

#[cfg(any(feature = "blocking-client", feature = "async-client"))]
pub(crate) mod function {
    use std::{borrow::Cow, collections::HashSet};

    use bstr::{BString, ByteVec};
    use gix_features::progress::Progress;
    use gix_transport::client::Capabilities;

    use super::Error;
    #[cfg(feature = "async-client")]
    use crate::transport::client::async_io::{self, TransportV2Ext as _};
    #[cfg(feature = "blocking-client")]
    use crate::transport::client::blocking_io::{self, TransportV2Ext as _};
    use crate::{
        handshake::{refs::from_v2_refs, Ref},
        Command,
    };

    /// A command to list references from a remote Git repository.
    ///
    /// It acts as a utility to separate the invocation into the shared blocking portion,
    /// and the one that performs IO either blocking or `async`.
    pub struct LsRefsCommand<'a> {
        pub(crate) capabilities: &'a Capabilities,
        features: Vec<(&'static str, Option<Cow<'static, str>>)>,
        arguments: Vec<BString>,
    }

    impl<'a> LsRefsCommand<'a> {
        /// Build a command to list refs from the given server `capabilities`,
        /// using `agent` information to identify ourselves.
        pub fn new(
            prefix_refspecs: Option<&[gix_refspec::RefSpec]>,
            capabilities: &'a Capabilities,
            agent: (&'static str, Option<Cow<'static, str>>),
        ) -> Self {
            let ls_refs = Command::LsRefs;
            let mut features = ls_refs.default_features(gix_transport::Protocol::V2, capabilities);
            features.push(agent);
            let mut arguments = ls_refs.initial_v2_arguments(&features);
            if capabilities
                .capability("ls-refs")
                .and_then(|cap| cap.supports("unborn"))
                .unwrap_or_default()
            {
                arguments.push("unborn".into());
            }

            if let Some(refspecs) = prefix_refspecs {
                let mut seen = HashSet::new();
                for spec in refspecs {
                    let spec = spec.to_ref();
                    if seen.insert(spec.instruction()) {
                        let mut prefixes = Vec::with_capacity(1);
                        spec.expand_prefixes(&mut prefixes);
                        for mut prefix in prefixes {
                            prefix.insert_str(0, "ref-prefix ");
                            arguments.push(prefix);
                        }
                    }
                }
            }

            Self {
                capabilities,
                features,
                arguments,
            }
        }

        /// Invoke a ls-refs V2 command on `transport`.
        ///
        /// `progress` is used to provide feedback.
        /// If `trace` is `true`, all packetlines received or sent will be passed to the facilities of the `gix-trace` crate.
        #[cfg(feature = "async-client")]
        pub async fn invoke_async(
            self,
            mut transport: impl async_io::Transport,
            progress: &mut impl Progress,
            trace: bool,
        ) -> Result<Vec<Ref>, Error> {
            let _span = gix_features::trace::detail!("gix_protocol::LsRefsCommand::invoke_async()");
            Command::LsRefs.validate_argument_prefixes(
                gix_transport::Protocol::V2,
                self.capabilities,
                &self.arguments,
                &self.features,
            )?;

            progress.step();
            progress.set_name("list refs".into());
            let mut remote_refs = transport
                .invoke(
                    Command::LsRefs.as_str(),
                    self.features.into_iter(),
                    if self.arguments.is_empty() {
                        None
                    } else {
                        Some(self.arguments.into_iter())
                    },
                    trace,
                )
                .await?;
            Ok(from_v2_refs(&mut remote_refs).await?)
        }

        /// Invoke a ls-refs V2 command on `transport`.
        ///
        /// `progress` is used to provide feedback.
        /// If `trace` is `true`, all packetlines received or sent will be passed to the facilities of the `gix-trace` crate.
        #[cfg(feature = "blocking-client")]
        pub fn invoke_blocking(
            self,
            mut transport: impl blocking_io::Transport,
            progress: &mut impl Progress,
            trace: bool,
        ) -> Result<Vec<Ref>, Error> {
            let _span = gix_features::trace::detail!("gix_protocol::LsRefsCommand::invoke_blocking()");
            Command::LsRefs.validate_argument_prefixes(
                gix_transport::Protocol::V2,
                self.capabilities,
                &self.arguments,
                &self.features,
            )?;

            progress.step();
            progress.set_name("list refs".into());
            let mut remote_refs = transport.invoke(
                Command::LsRefs.as_str(),
                self.features.into_iter(),
                if self.arguments.is_empty() {
                    None
                } else {
                    Some(self.arguments.into_iter())
                },
                trace,
            )?;
            Ok(from_v2_refs(&mut remote_refs)?)
        }
    }
}
