//! Primitives for describing git submodules.
#![deny(missing_docs)]
#![forbid(unsafe_code)]

use std::collections::BTreeMap;

use bstr::ByteSlice;

/// All relevant information about a git module, typically from `.gitmodules` files.
///
/// Note that overrides from other configuration might be relevant, which is why this type
/// can be used to take these into consideration when presented with other configuration
/// from the superproject.
#[derive(Clone)]
pub struct File {
    config: gix_config::File,
}

mod access;

///
pub mod config;

///
pub mod is_active_platform;

/// A platform to keep the state necessary to perform repeated active checks, created by [File::is_active_platform()].
pub struct IsActivePlatform {
    pub(crate) search: Option<gix_pathspec::Search>,
}

/// Mutation
impl File {
    /// This can be used to let `config` override some values we know about submodules, namely…
    ///
    /// * `url`
    /// * `fetchRecurseSubmodules`
    /// * `ignore`
    /// * `update`
    /// * `branch`
    ///
    /// These values aren't validated yet, which will happen upon query.
    pub fn append_submodule_overrides(
        &mut self,
        config: &gix_config::File,
    ) -> Result<&mut Self, gix_config::file::section::value::Error> {
        let mut values = BTreeMap::<_, Vec<_>>::new();
        for (module_name, section) in config
            .sections_by_name("submodule")
            .into_iter()
            .flatten()
            .filter_map(|s| s.header().subsection_name().map(|n| (n, s)))
        {
            for field in ["url", "fetchRecurseSubmodules", "ignore", "update", "branch"] {
                if let Some(value) = section.value(field) {
                    values.entry((module_name, field)).or_default().push(value);
                }
            }
        }

        let values = {
            let mut v: Vec<_> = values.into_iter().collect();
            v.sort_by_key(|a| a.0.0);
            v
        };

        let mut config_to_append = gix_config::File::new(config.meta_owned());
        let mut prev_name = None;
        for ((module_name, field), values) in values {
            if prev_name != Some(module_name) {
                config_to_append
                    .new_section("submodule", module_name)
                    .expect("all names come from valid configuration, so remain valid");
                prev_name = Some(module_name);
            }
            config_to_append
                .section_mut("submodule", Some(module_name))
                .expect("always set at this point")
                .push(
                    field,
                    Some(
                        values
                            .last()
                            .expect("at least one value or we wouldn't be here")
                            .as_bstr(),
                    ),
                )?;
        }

        self.config.append(config_to_append)?;
        Ok(self)
    }
}

///
pub mod init {
    use std::path::PathBuf;

    use crate::File;

    impl std::fmt::Debug for File {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            f.debug_struct("File")
                .field("config_path", &self.config_path())
                .field("config", &format_args!("r#\"{}\"#", self.config))
                .finish()
        }
    }

    /// A marker we use when listing names to not pick them up from overridden sections.
    pub(crate) const META_MARKER: gix_config::Source = gix_config::Source::Api;

    /// Lifecycle
    /// The error returned when parsing a submodule configuration file.
    #[derive(Debug, thiserror::Error)]
    pub enum Error {
        /// The configuration could not be parsed.
        #[error(transparent)]
        Parse(#[from] gix_config::parse::Error),
        /// Applying configuration overrides exceeded the supported span size.
        #[error(transparent)]
        Span(#[from] gix_config::parse::span::Error),
        /// Applying configuration overrides failed.
        #[error(transparent)]
        SectionValue(#[from] gix_config::file::section::value::Error),
    }

    impl File {
        /// Parse `bytes` as git configuration, typically from `.gitmodules`, without doing any further validation.
        /// `path` can be provided to keep track of where the file was read from in the underlying [`config`](Self::config())
        /// instance.
        /// `config` is used to [apply value overrides](File::append_submodule_overrides), which can be empty if overrides
        /// should be applied at a later time.
        ///
        /// Future access to the module information is lazy and configuration errors are exposed there on a per-value basis.
        ///
        /// ### Security Considerations
        ///
        /// The information itself should be used with care as it can direct the caller to fetch from remotes. It is, however,
        /// on the caller to assure the input data can be trusted.
        pub fn from_bytes(
            bytes: &[u8],
            path: impl Into<Option<PathBuf>>,
            config: &gix_config::File,
        ) -> Result<Self, Error> {
            let metadata = {
                let mut meta = gix_config::file::Metadata::from(META_MARKER);
                meta.path = path.into();
                meta
            };
            let modules = gix_config::File::from_parse_events_no_includes(
                gix_config::parse::Events::from_bytes(bytes, None)?,
                metadata,
            );

            let mut res = Self { config: modules };
            res.append_submodule_overrides(config)?;
            Ok(res)
        }

        /// Turn ourselves into the underlying parsed configuration file.
        pub fn into_config(self) -> gix_config::File {
            self.config
        }
    }
}
