//! Helper for defining sub-builders that map a serializable type (typically String)
//! to a configuration type.

/// Define a map type, and an associated builder struct, suitable for use in a configuration object.
///
/// We use this macro when we want a configuration structure to contain a key-to-value map,
/// and therefore we want its associated builder structure to contain
/// a map from the same key type to a value-builder type.
///
/// The key of the map type must implement `Serialize`, `Clone`, and `Debug`.
/// The value of the map type must have an associated "Builder"
/// type formed by appending `Builder` to its name.
/// This Builder type must implement `Serialize`, `Deserialize`, `Clone`, and `Debug`,
/// and it must have a `build(&self)` method returning `Result<value, ConfigBuildError>`.
///
/// # Syntax and behavior
///
/// ```ignore
/// define_map_builder! {
///     BuilderAttributes
///     pub struct BuilderName =>
///
///     MapAttributes
///     pub type MapName = ContainerType<KeyType, ValueType>;
///
///     defaults: defaults_func(); // <--- this line is optional
/// }
/// ```
///
/// In the example above,
///
/// * BuilderName, MapName, and ContainerType may be replaced with any identifier;
/// * BuilderAttributes and MapAttributes can be replaced with any set of attributes
///   (such sa doc comments, `#derive`, and so on);
/// * The `pub`s may be replaced with any visibility;
/// * and `KeyType` and `ValueType` may be replaced with any appropriate types.
///    * `ValueType` must have a corresponding `ValueTypeBuilder`.
///    * `ValueTypeBuilder` must implement
///      [`ExtendBuilder`](crate::extend_builder::ExtendBuilder).
///
/// Given this syntax, this macro will define "MapType" as an alias for
/// `Container<KeyType,ValueType>`,
/// and "BuilderName" as a builder type for that container.
///
/// "BuilderName" will implement:
///  * `Deref` and `DerefMut` with a target type of `Container<KeyType, ValueTypeBuilder>`
///  * `Default`, `Clone`, and `Debug`.
///  * `Serialize` and `Deserialize`
///  * A `build()` function that invokes `build()` on every value in its contained map.
///
/// (Note that in order to work as a sub-builder within our configuration system,
/// "BuilderName" should be the same as "MapName" concatenated with "Builder.")
///
/// The `defaults_func()`, if provided, must be
/// a function returning `ContainerType<KeyType, ValueType>`.
/// The values returned by `default_func()` map are used to implement
/// `Default` and `Deserialize` for `BuilderName`,
/// extending from the defaults with `ExtendStrategy::ReplaceLists`.
/// If no `defaults_func` is given, `ContainerType::default()` is used.
///
/// # Example
///
/// ```
/// # use derive_deftly::Deftly;
/// # use std::collections::BTreeMap;
/// # use tor_config::{ConfigBuildError, define_map_builder, derive_deftly_template_ExtendBuilder};
/// # use tor_config::derive::prelude::*;
/// # use serde::{Serialize, Deserialize};
/// # use tor_config::extend_builder::{ExtendBuilder,ExtendStrategy};
/// #[derive(Clone, Debug, Deftly, Eq, PartialEq)]
/// #[derive_deftly(TorConfig)]
/// pub struct ConnectionsConfig {
///     // Note: Ordinarily, you might choose to `tor_config(map)` instead,
///     // to automate more of this derivation.
///     #[deftly(tor_config(sub_builder, no_magic))]
///     conns: ConnectionMap
/// }
///
/// define_map_builder! {
///     pub struct ConnectionMapBuilder =>
///     pub type ConnectionMap = BTreeMap<String, ConnConfig>;
/// }
///
/// #[derive(Clone, Debug, Deftly, Eq, PartialEq)]
/// #[derive_deftly(TorConfig)]
/// pub struct ConnConfig {
///     #[deftly(tor_config(default="true"))]
///     enabled: bool,
///     #[deftly(tor_config(default="9999"))]
///     port: u16,
/// }
///
/// let defaults: ConnectionsConfigBuilder = toml::from_str(r#"
/// [conns."socks"]
/// enabled = true
/// port = 9150
///
/// [conns."http"]
/// enabled = false
/// port = 1234
///
/// [conns."wombat"]
/// port = 5050
/// "#).unwrap();
/// let user_settings: ConnectionsConfigBuilder = toml::from_str(r#"
/// [conns."http"]
/// enabled = false
/// [conns."quokka"]
/// enabled = true
/// port = 9999
/// "#).unwrap();
///
/// let mut cfg = defaults.clone();
/// cfg.extend_from(user_settings, ExtendStrategy::ReplaceLists);
/// let cfg = cfg.build().unwrap();
/// assert_eq!(cfg, ConnectionsConfig {
///     conns: vec![
///         ("http".into(), ConnConfig { enabled: false, port: 1234}),
///         ("quokka".into(), ConnConfig { enabled: true, port: 9999}),
///         ("socks".into(), ConnConfig { enabled: true, port: 9150}),
///         ("wombat".into(), ConnConfig { enabled: true, port: 5050}),
///     ].into_iter().collect(),
/// });
/// ```
///
/// In the example above, the `derive_map_builder` macro expands to something like:
///
/// ```ignore
/// pub type ConnectionMap = BTreeMap<String, ConnConfig>;
///
/// #[derive(Clone,Debug,Serialize,Educe)]
/// #[educe(Deref,DerefMut)]
/// pub struct ConnectionMapBuilder(BTreeMap<String, ConnConfigBuilder);
///
/// impl ConnectionMapBuilder {
///     fn build(&self) -> Result<ConnectionMap, ConfigBuildError> {
///         ...
///     }
/// }
/// impl Default for ConnectionMapBuilder { ... }
/// impl Deserialize for ConnectionMapBuilder { ... }
/// impl ExtendBuilder for ConnectionMapBuilder { ... }
/// ```
///
/// # Notes and rationale
///
/// We use this macro, instead of using a Map directly in our configuration object,
/// so that we can have a separate Builder type with a reasonable build() implementation.
///
/// We don't support complicated keys; instead we require that the keys implement Deserialize.
/// If we ever need to support keys with their own builders,
/// we'll have to define a new macro.
///
/// We use `ExtendBuilder` to implement Deserialize with defaults,
/// so that the provided configuration options can override
/// only those parts of the default configuration tree
/// that they actually replace.
#[macro_export]
macro_rules! define_map_builder {
    {
        $(#[ $b_m:meta ])*
        $b_v:vis struct $btype:ident =>
        $(#[ $m:meta ])*
        $v:vis type $maptype:ident = $coltype:ident < $keytype:ty , $valtype: ty >;
        $( defaults: $defaults:expr; )?
    } => {
        $crate::deps::paste!{$crate::define_map_builder! {
            $(#[$b_m])*
            $b_v struct $btype =>
            $(#[$m])*
            $v type $maptype = {
                map: $coltype < $keytype , $valtype >,
                builder_map: $coltype < $keytype, [<$valtype Builder>] > ,
            }
            $( defaults: $defaults; )?
        }}
    };
    // This _undocumented_ internal syntax allows us to take the map types explicitly,
    // so that we can accept derive-deftly outputs.
    //
    // Syntax:
    // ```
    //   «#[builder_attrs]»
    //   «vis» struct «FooMapBuilder» =>
    //   «#[maptype_attrs]»
    //   «vis» type «FooMap» = {
    //       map: «maptype»,
    //       builder_map: «builder_map_type»,
    //   }
    //   ⟦ defaults: «default_expr» ; ⟧
    // ```
    //
    // The defaults line and the attributes are optional.
    // This (undocumented) syntax is identical to the documented variant above,
    // except in the braced section after the `=` and before the optional `defaults`.
    // In that section,
    // the `maptype` is the type of the map which we are trying to build,
    // (for example, `HashMap<String, WombatCfg>`),
    // and the `builder_map` is the type of the map which instantiates the builder
    // (for example, `HashMap<String, WombatCfgBuilder>`).
    {
        $(#[ $b_m:meta ])*
        $b_v:vis struct $btype:ident =>
        $(#[ $m:meta ])*
        $v:vis type $maptype:ident = {
            map: $mtype:ty,
            builder_map: $bmtype:ty $(,)?
        }
        $( defaults: $defaults:expr; )?
    } =>
    {$crate::deps::paste!{
        $(#[ $m ])*
        $v type $maptype = $mtype ;

        $(#[ $b_m ])*
        #[derive(Clone,Debug,$crate::deps::serde::Serialize, $crate::deps::educe::Educe)]
        #[educe(Deref, DerefMut)]
        #[serde(transparent)]
        $b_v struct $btype( $bmtype );

        impl $btype {
            /// Construct the final map.
            $b_v fn build(&self) -> ::std::result::Result<$maptype, $crate::ConfigBuildError> {
                self.0
                    .iter()
                    .map(|(k,v)| Ok((k.clone(), v.build()?)))
                    .collect()
            }
        }
        impl $crate::load::Builder for $btype {
            type Built = $maptype;
            fn build(&self) -> ::std::result::Result<$maptype, $crate::ConfigBuildError> {
                $btype :: build(self)
            }
        }

        impl $crate::load::ConfigBuilder for $btype {
            fn apply_defaults(&mut self) -> ::std::result::Result<(), $crate::ConfigBuildError> {
                #[allow(unused_imports)]
                use $crate::load::ConfigBuilder as _;
                for v in self.0.values_mut() {
                    v.apply_defaults()?;
                }
                Ok(())
            }
        }
        $(
            // This section is expanded when we have a defaults_fn().
            impl ::std::default::Default for $btype {
                fn default() -> Self {
                    Self($defaults)
                }
            }
            impl<'de> $crate::deps::serde::Deserialize<'de> for $btype {
                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
                where
                    D: $crate::deps::serde::Deserializer<'de> {
                        use $crate::deps::serde::Deserialize;
                        // To deserialize into this type, we create a builder holding the defaults,
                        // and we create a builder holding the values from the deserializer.
                        // We then use ExtendBuilder to extend the defaults with the deserialized values.
                        let deserialized = <$bmtype as Deserialize>::deserialize(deserializer)?;
                        let mut defaults = $btype::default();
                        $crate::extend_builder::ExtendBuilder::extend_from(
                            &mut defaults,
                            Self(deserialized),
                            $crate::extend_builder::ExtendStrategy::ReplaceLists);
                        Ok(defaults)
                    }
            }
        )?
        $crate::define_map_builder!{@if_empty { $($defaults)? } {
            // This section is expanded when we don't have a defaults_fn().
            impl ::std::default::Default for $btype {
                fn default() -> Self {
                    Self(Default::default())
                }
            }
            // We can't conditionally derive() here, since Rust doesn't like macros that expand to
            // attributes.
            impl<'de> $crate::deps::serde::Deserialize<'de> for $btype {
                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
                where
                    D: $crate::deps::serde::Deserializer<'de> {
                    use $crate::deps::serde::Deserialize;
                    Ok(Self(<$bmtype as Deserialize>::deserialize(deserializer)?))
                }
            }
        }}
        impl $crate::extend_builder::ExtendBuilder for $btype
        {
            fn extend_from(&mut self, other: Self, strategy: $crate::extend_builder::ExtendStrategy) {
                $crate::extend_builder::ExtendBuilder::extend_from(&mut self.0, other.0, strategy);
            }
        }
    }};
    {@if_empty {} {$($x:tt)*}} => {$($x)*};
    {@if_empty {$($y:tt)*} {$($x:tt)*}} => {};
}

#[cfg(test)]
mod test {
    // @@ begin test lint list maintained by maint/add_warning @@
    #![allow(clippy::bool_assert_comparison)]
    #![allow(clippy::clone_on_copy)]
    #![allow(clippy::dbg_macro)]
    #![allow(clippy::mixed_attributes_style)]
    #![allow(clippy::print_stderr)]
    #![allow(clippy::print_stdout)]
    #![allow(clippy::single_char_pattern)]
    #![allow(clippy::unwrap_used)]
    #![allow(clippy::unchecked_time_subtraction)]
    #![allow(clippy::useless_vec)]
    #![allow(clippy::needless_pass_by_value)]
    #![allow(clippy::string_slice)] // See arti#2571
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->

    use crate::derive::prelude::*;
    use derive_deftly::Deftly;
    use std::collections::BTreeMap;

    #[derive(Clone, Debug, Eq, PartialEq, Deftly)]
    #[derive_deftly(TorConfig)]
    struct Outer {
        #[deftly(tor_config(sub_builder, no_magic))]
        things: ThingMap,
    }

    #[derive(Clone, Debug, Eq, PartialEq, Deftly)]
    #[derive_deftly(TorConfig)]
    struct Inner {
        #[deftly(tor_config(default))]
        fun: bool,
        #[deftly(tor_config(default))]
        explosive: bool,
    }

    impl InnerBuilder {
        // Testing only. We don't want to use derive_deftly(TorConfig) on Inner
        // because we are trying to test define_map_builder by hand.
        #[expect(clippy::unnecessary_wraps)]
        fn apply_defaults(&mut self) -> Result<(), crate::ConfigBuildError> {
            self.fun.get_or_insert_default();
            self.explosive.get_or_insert_default();
            Ok(())
        }
    }

    define_map_builder! {
        struct ThingMapBuilder =>
        type ThingMap = BTreeMap<String, Inner>;
    }

    #[test]
    fn parse_and_build() {
        let builder: OuterBuilder = toml::from_str(
            r#"
[things.x]
fun = true
explosive = false

[things.yy]
explosive = true
fun = true
"#,
        )
        .unwrap();

        let built = builder.build().unwrap();
        assert_eq!(
            built.things.get("x").unwrap(),
            &Inner {
                fun: true,
                explosive: false
            }
        );
        assert_eq!(
            built.things.get("yy").unwrap(),
            &Inner {
                fun: true,
                explosive: true
            }
        );
    }

    #[test]
    fn build_directly() {
        let mut builder = OuterBuilder::default();
        let mut bld = InnerBuilder::default();
        bld.fun(true);
        builder.things().insert("x".into(), bld);
        let built = builder.build().unwrap();
        assert_eq!(
            built.things.get("x").unwrap(),
            &Inner {
                fun: true,
                explosive: false
            }
        );
    }

    define_map_builder! {
        struct ThingMap2Builder =>
        type ThingMap2 = BTreeMap<String, Inner>;
        defaults: thingmap2_default();
    }
    fn thingmap2_default() -> BTreeMap<String, InnerBuilder> {
        let mut map = BTreeMap::new();
        {
            let mut bld = InnerBuilder::default();
            bld.fun(true);
            map.insert("x".to_string(), bld);
        }
        {
            let mut bld = InnerBuilder::default();
            bld.explosive(true);
            map.insert("y".to_string(), bld);
        }
        map
    }
    #[test]
    fn with_defaults() {
        let mut tm2 = ThingMap2Builder::default();
        tm2.get_mut("x").unwrap().explosive(true);
        let mut bld = InnerBuilder::default();
        bld.fun(true);
        tm2.insert("zz".into(), bld);
        let built = tm2.build().unwrap();

        assert_eq!(
            built.get("x").unwrap(),
            &Inner {
                fun: true,
                explosive: true
            }
        );
        assert_eq!(
            built.get("y").unwrap(),
            &Inner {
                fun: false,
                explosive: true
            }
        );
        assert_eq!(
            built.get("zz").unwrap(),
            &Inner {
                fun: true,
                explosive: false
            }
        );

        let tm2: ThingMap2Builder = toml::from_str(
            r#"
            [x]
            explosive = true
            [zz]
            fun = true
            "#,
        )
        .unwrap();
        let built2 = tm2.build().unwrap();
        assert_eq!(built, built2);
    }
}
