use crate::data_result::DataResult;
use crate::dynamic_ops::DynamicOps;
use crate::lifecycle::Lifecycle;

/// A trait specifying a builder to add key-value pairs in order to create a composite type.
///
/// `Value` is the dynamic type for this builder.
/// For a struct, some methods here can be implemented via the `impl_struct_builder` macro.
pub trait StructBuilder {
    type Value;

    /// Adds a single key-value pair to this builder and returns the builder.
    #[must_use]
    fn add_key_value(self, key: Self::Value, value: Self::Value) -> Self;

    /// Adds a single key-'value result' pair to this builder and returns the builder.
    #[must_use]
    fn add_key_value_result(self, key: Self::Value, value: DataResult<Self::Value>) -> Self;

    /// Adds a single 'key result'-'value result' pair to this builder and returns the builder.
    #[must_use]
    fn add_key_result_value_result(
        self,
        key: DataResult<Self::Value>,
        value: DataResult<Self::Value>,
    ) -> Self;

    /// Adds the error message from a provided `DataResult` (if any) to this builder and returns the builder.
    #[must_use]
    fn with_errors_from<T>(self, result: &DataResult<T>) -> Self;

    /// Adds a string key-value pair to this builder and returns the builder.
    #[must_use]
    fn add_string_key_value(self, key: &str, value: Self::Value) -> Self;

    /// Adds a string key-'value result' pair to this builder and returns the builder.
    #[must_use]
    fn add_string_key_value_result(self, key: &str, value: DataResult<Self::Value>) -> Self;

    /// Sets the lifecycle of this builder and returns the builder.
    #[must_use]
    fn set_lifecycle(self, lifecycle: Lifecycle) -> Self;

    /// Maps the error from the internal builder to the function `f` and returns the builder.
    #[must_use]
    fn map_error(self, f: impl FnOnce(String) -> String) -> Self;

    /// Builds the map stored in this builder along with a prefix and returns the result.
    fn build(self, prefix: Self::Value) -> DataResult<Self::Value>;

    /// Builds the map stored in this builder along with a `DataResult` prefix and returns the result.
    fn build_with_result_prefix(self, prefix: DataResult<Self::Value>) -> DataResult<Self::Value>
    where
        Self: Sized,
    {
        prefix.flat_map(|p| self.build(p))
    }
}

/// A [`StructBuilder`] with a specified `Result` type for builders.
pub trait ResultStructBuilder: StructBuilder {
    type Result;

    /// Builds the map stored in `builder` along with a prefix and returns the result.
    fn build_with_builder(
        self,
        builder: Self::Result,
        prefix: Self::Value,
    ) -> DataResult<Self::Value>;
}

/// A subtrait of [`StructBuilder`] for appending string keys instead of dynamic type keys.
/// The methods in `StructBuilder` can also be implemented via the `impl_string_struct_builder` macro.
pub trait StringStructBuilder: ResultStructBuilder {
    /// Appends a string key-value pair to `builder`, mutating it.
    fn append(&self, key: &str, value: Self::Value, builder: Self::Result) -> Self::Result;
}

/// A subtrait of [`StructBuilder`] for appending dynamic keys. The methods in `StructBuilder`
/// can also be implemented via the `impl_universal_struct_builder` macro.
pub trait UniversalStructBuilder: ResultStructBuilder {
    /// Appends a key-value pair to `builder`, mutating it.
    fn append(&self, key: Self::Value, value: Self::Value, builder: Self::Result) -> Self::Result;
}

/// A macro to be placed inside an `impl` block of a struct implementing [`StructBuilder`].
///
/// Place this in a `impl StructBuilder for ...` block.
/// This automatically implements the methods to add key-value pairs to the builder.
/// Make sure to have a struct field of type [`DataResult<Self::Value>`] of name `$builder`.
#[macro_export]
macro_rules! impl_struct_builder {
    ($builder:ident) => {
        fn set_lifecycle(mut self, lifecycle: Lifecycle) -> Self {
            self.$builder = self.$builder.with_lifecycle(lifecycle);
            self
        }

        fn map_error(mut self, f: impl FnOnce(String) -> String) -> Self {
            self.$builder = self.$builder.map_error(f);
            self
        }

        fn with_errors_from<U>(mut self, result: &DataResult<U>) -> Self {
            self.$builder = self.$builder.with_errors_from(result);
            self
        }

        fn build(self, prefix: Self::Value) -> DataResult<Self::Value> {
            self.$builder
                .clone()
                .flat_map(|b| self.build_with_builder(b, prefix))
        }
    };
}

/// A macro to be placed inside an `impl` block of a struct implementing [`StringStructBuilder`].
///
/// Place this in a `impl StructBuilder for ...` block.
/// This automatically implements the methods to add key-value pairs to the builder.
#[macro_export]
macro_rules! impl_string_struct_builder {
    (@internal $builder:ident) => {
        fn add_string_key_value(mut self, key: &str, value: Self::Value) -> Self {
            self.$builder = std::mem::take(&mut self.$builder).map(|r| self.append(key, value, r));
            self
        }

        fn add_string_key_value_result(mut self, key: &str, value: DataResult<Self::Value>) -> Self {
            self.$builder = std::mem::take(&mut self.$builder).apply_2_and_make_stable(|r, v| self.append(key, v, r), value);
            self
        }
    };

    // For constant ops
    ($builder:ident, $ops:ident) => {

        impl_string_struct_builder!(@internal $builder);

        fn add_key_value(mut self, key: Self::Value, value: Self::Value) -> Self {
            self.$builder = $ops.get_string(&key).flat_map(
                |s| std::mem::take(&mut self.$builder).map(|r| self.append(&s, value, r))
            );
            self
        }

        fn add_key_value_result(mut self, key: Self::Value, value: DataResult<Self::Value>) -> Self {
            self.$builder = $ops.get_string(&key).flat_map(
                |s| std::mem::take(&mut self.$builder).apply_2_and_make_stable(|r, v| self.append(&s, v, r), value)
            );
            self
        }

        fn add_key_result_value_result(
            mut self,
            key: DataResult<Self::Value>,
            value: DataResult<Self::Value>,
        ) -> Self {
            self.$builder = key.flat_map(|v| $ops.get_string(&v)).flat_map(|s| {
                std::mem::take(&mut self.$builder).apply_2_and_make_stable(|r, v| self.append(&s, v, r), value)
            });
            self
        }
    };

    // For stored ops
    ($builder:ident, self. $ops:ident) => {

        impl_string_struct_builder!(@internal $builder);

        fn add_key_value(mut self, key: Self::Value, value: Self::Value) -> Self {
            self.$builder = self.$ops.get_string(&key).flat_map(
                |s| std::mem::take(&mut self.$builder).map(|r| self.append(&s, value, r))
            );
            self
        }

        fn add_key_value_result(mut self, key: Self::Value, value: DataResult<Self::Value>) -> Self {
            self.$builder = self.$ops.get_string(&key).flat_map(
                |s| std::mem::take(&mut self.$builder).apply_2_and_make_stable(|r, v| self.append(&s, v, r), value)
            );
            self
        }

        fn add_key_result_value_result(
            mut self,
            key: DataResult<Self::Value>,
            value: DataResult<Self::Value>,
        ) -> Self {
            self.$builder = key.flat_map(|v| self.$ops.get_string(&v)).flat_map(|s| {
                std::mem::take(&mut self.$builder).apply_2_and_make_stable(|r, v| self.append(&s, v, r), value)
            });
            self
        }
    };
}

/// A macro to be placed inside an `impl` block of a struct implementing `UniversalStructBuilder`.
///
/// Place this in a `impl StructBuilder for ...` block.
/// This automatically implements the methods to add key-value pairs to the builder.
#[macro_export]
macro_rules! impl_universal_struct_builder {
    (@internal $builder:ident) => {
        fn add_key_value(mut self, key: Self::Value, value: Self::Value) -> Self {
            self.$builder = std::mem::take(&mut self.$builder).map(|b| self.append(key, value, b));
            self
        }

        fn add_key_value_result(mut self, key: Self::Value, value: DataResult<Self::Value>) -> Self {
            self.$builder = std::mem::take(&mut self.$builder)
                .apply_2_and_make_stable(|b, v| self.append(key, v, b), value);
            self
        }

        fn add_key_result_value_result(
            mut self,
            key: DataResult<Self::Value>,
            value: DataResult<Self::Value>,
        ) -> Self {
            self.$builder = self
                .$builder
                .clone()
                .apply(key.apply_2_and_make_stable(|k, v| (|b| self.append(k, v, b)), value));
            self
        }
    };

    // For constant ops
    ($builder:ident, $ops:ident) => {
        impl_universal_struct_builder!(@internal $builder);

        fn add_string_key_value(self, key: &str, value: Self::Value) -> Self {
            self.add_key_value($ops.create_string(key), value)
        }

        fn add_string_key_value_result(self, key: &str, value: DataResult<Self::Value>) -> Self {
            self.add_key_value_result($ops.create_string(key), value)
        }
    };

    // For stored ops
    ($builder:ident, self. $ops:ident) => {
        impl_universal_struct_builder!(@internal $builder);

        fn add_string_key_value(self, key: &str, value: Self::Value) -> Self {
            let string = self.$ops.create_string(key);
            self.add_key_value(string, value)
        }

        fn add_string_key_value_result(self, key: &str, value: DataResult<Self::Value>) -> Self {
            let string = self.$ops.create_string(key);
            self.add_key_value_result(string, value)
        }
    };
}

pub struct MapBuilder<T, O: DynamicOps<Value = T> + 'static> {
    builder: DataResult<Vec<(T, T)>>,
    ops: &'static O,
}

impl<T: Clone, O: DynamicOps<Value = T>> StructBuilder for MapBuilder<T, O> {
    type Value = T;

    impl_struct_builder!(builder);
    impl_universal_struct_builder!(builder, self.ops);
}

impl<T: Clone, O: DynamicOps<Value = T>> ResultStructBuilder for MapBuilder<T, O> {
    type Result = Vec<(T, T)>;

    fn build_with_builder(
        self,
        builder: Self::Result,
        prefix: Self::Value,
    ) -> DataResult<Self::Value> {
        self.ops.merge_entries_into_map(prefix, builder)
    }
}

impl<T: Clone, O: DynamicOps<Value = T>> UniversalStructBuilder for MapBuilder<T, O> {
    fn append(
        &self,
        key: Self::Value,
        value: Self::Value,
        mut builder: Self::Result,
    ) -> Self::Result {
        builder.push((key, value));
        builder
    }
}
