// Protocol Buffers - Google's data interchange format
// Copyright 2024 Google LLC.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

/// proto! enables the use of Rust struct initialization syntax to create
/// protobuf messages. The macro does its best to try and detect the
/// initialization of submessages, but it is only able to do so while the
/// submessage is being defined as part of the original struct literal.
/// Introducing an expression using () or {} as the value of a field will
/// require another call to this macro in order to return a submessage
/// literal.``` /*
/// Given the following proto definition
/// message Data {
///     int32 number = 1;
///     string letters = 2;
///     Data nested = 3;
/// }
/// */
/// use protobuf::proto;
/// let message = proto!(Data {
///     number: 42,
///     letters: "Hello World",
///     nested: Data {
///         number: {
///             let x = 100;
///             x + 1
///         }
///     }
/// }); ```
#[macro_export]
macro_rules! proto {
    ($msgtype:ty { $($tt:tt)* }) => {
        $crate::proto_internal!($msgtype { $($tt)* })
    }
}

#[macro_export(local_inner_macros)]
#[doc(hidden)]
macro_rules! proto_internal {
    // @merge rules are used to find a trailing ..expr on the message and call merge_from on it
    // before the fields of the message are set.
    (@merge $msg:ident $ident:ident : $expr:expr, $($rest:tt)*) => {
        proto_internal!(@merge $msg $($rest)*);
    };

    (@merge $msg:ident $ident:ident : $expr:expr) => {
    };

    (@merge $msg:ident ..$expr:expr) => {
        $crate::MergeFrom::merge_from(&mut $msg, $expr);
    };

    // @msg rules are used to set the fields of the message. There is a lot of duplication here
    // because we need to parse the message type using a :: separated list of identifiers.
    // nested message and trailing fields
    (@msg $msg:ident $submsg:ident : $($msgtype:ident)::+ { $($value:tt)* }, $($rest:tt)*) => {
        proto_internal!(@msg $msg $submsg : $($msgtype)::+ { $($value)* });
        proto_internal!(@msg $msg $($rest)*);
    };
    // nested message with leading :: on type and trailing fields
    (@msg $msg:ident $submsg:ident : ::$($msgtype:ident)::+ { $($value:tt)* }, $($rest:tt)*) => {
        proto_internal!(@msg $msg $submsg : ::$($msgtype)::+ { $($value)* });
        proto_internal!(@msg $msg $($rest)*);
    };

    // nested message using __
    (@msg $msg:ident $submsg:ident : __ { $($value:tt)* }) => {
        {
            let mut $msg = $crate::__internal::paste!($msg.[<$submsg _mut>]());
            proto_internal!(@merge $msg $($value)*);
            proto_internal!(@msg $msg $($value)*);
        }
    };
    // nested message
    (@msg $msg:ident $submsg:ident : $($msgtype:ident)::+ { $($value:tt)* }) => {
        {
            let mut $msg: <$($msgtype)::+ as $crate::MutProxied>::Mut<'_> = $crate::__internal::paste!($msg.[<$submsg _mut>]());
            proto_internal!(@merge $msg $($value)*);
            proto_internal!(@msg $msg $($value)*);
        }
    };
    // nested message with leading ::
    (@msg $msg:ident $submsg:ident : ::$($msgtype:ident)::+ { $($value:tt)* }) => {
        {
            let mut $msg: <::$($msgtype)::+ as $crate::MutProxied>::Mut<'_> = $crate::__internal::paste!($msg.[<$submsg _mut>]());
            proto_internal!(@merge $msg $($value)*);
            proto_internal!(@msg $msg $($value)*);
        }
    };

    // field with array literal and trailing fields
    (@msg $msg:ident $ident:ident : [$($elems:tt)*], $($rest:tt)*) => {
        proto_internal!(@msg $msg $ident : [$($elems)*]);
        proto_internal!(@msg $msg $($rest)*);
    };
    // field with array literal, calls out to @array to look for nested messages
    (@msg $msg:ident $ident:ident : [$($elems:tt)*]) => {
        {
            let _repeated = $crate::__internal::paste!($msg.[<$ident>]());
            let elems = proto_internal!(@array $msg _repeated [] $($elems)*);
            $crate::__internal::paste!($msg.[<set_ $ident>](elems.into_iter()));
        }
    };

    // @array searches through an array literal for nested messages.
    // If a message is found then we recursively call the macro on it to set the fields.
    // This will create an array literal of owned messages to be used while setting the field.
    // For primitive types they should just be passed through as an $expr.
    // The array literal is constructed recursively, so the [] case has to be handled separately so
    // that we can properly insert commas. This leads to a lot of duplication.

    // Message nested in array literal with trailing array items
    (@array $msg:ident $repeated:ident [$($vals:expr),+] __ { $($value:tt)* }, $($rest:tt)*) => {
        proto_internal!(@array $msg $repeated [
                $($vals),+ ,
                {
                    let mut $msg = $crate::get_repeated_default_value($crate::__internal::Private, $repeated);
                    proto_internal!(@merge $msg $($value)*);
                    proto_internal!(@msg $msg $($value)*);
                    $msg
                }
            ] $($rest)*)
    };

    // Message nested in [] literal
    (@array $msg:ident $repeated:ident [$($vals:expr),+] __ { $($value:tt)* }) => {
        [
            $($vals),+ ,
            {
                let mut $msg = $crate::get_repeated_default_value($crate::__internal::Private, $repeated);
                proto_internal!(@merge $msg $($value)*);
                proto_internal!(@msg $msg $($value)*);
                $msg
            }
        ]
    };

    // Message nested in array literal with trailing array items
    (@array $msg:ident $repeated:ident [] __ { $($value:tt)* }, $($rest:tt)*) => {
        proto_internal!(@array $msg $repeated [
                {
                    let mut $msg = $crate::get_repeated_default_value($crate::__internal::Private, $repeated);
                    proto_internal!(@merge $msg $($value)*);
                    proto_internal!(@msg $msg $($value)*);
                    $msg
                }
            ] $($rest)*)
    };

    // Message nested in [] literal
    (@array $msg:ident $repeated:ident [] __ { $($value:tt)* }) => {
        [
            {
                let mut $msg = $crate::get_repeated_default_value($crate::__internal::Private, $repeated);
                proto_internal!(@merge $msg $($value)*);
                proto_internal!(@msg $msg $($value)*);
                $msg
            }
        ]
    };

    // End of __ repeated, now we need to handle named types

    // Message nested in array literal with trailing array items
    (@array $msg:ident $repeated:ident [$($vals:expr),+] $($msgtype:ident)::+ { $($value:tt)* }, $($rest:tt)*) => {
        proto_internal!(@array $msg $repeated [
                $($vals),+ ,
                {
                    let mut $msg = $($msgtype)::+::new();
                    proto_internal!(@merge $msg $($value)*);
                    proto_internal!(@msg $msg $($value)*);
                    $msg
                }
            ] $($rest)*)
    };
    // Message nested in [] literal with leading :: on type and trailing array items
    (@array $msg:ident $repeated:ident [$($vals:expr),+] ::$($msgtype:ident)::+ { $($value:tt)* }, $($rest:tt)*) => {
        proto_internal!(@array $msg $repeated [
                $($vals),+ ,
                {
                    let mut $msg = ::$($msgtype)::+::new();
                    proto_internal!(@merge $msg $($value)*);
                    proto_internal!(@msg $msg $($value)*);
                    $msg
                }
            ] $($rest)*)
    };
    // Message nested in [] literal
    (@array $msg:ident $repeated:ident [$($vals:expr),+] $($msgtype:ident)::+ { $($value:tt)* }) => {
        [
            $($vals),+ ,
            {
                let mut $msg = $($msgtype)::+::new();
                proto_internal!(@merge $msg $($value)*);
                proto_internal!(@msg $msg $($value)*);
                $msg
            }
        ]
    };
    // Message nested in [] literal with leading :: on type
    (@array $msg:ident $repeated:ident [$($vals:expr),+] ::$($msgtype:ident)::+ { $($value:tt)* }) => {
        [
            $($vals),+ ,
            {
                let mut $msg = ::$($msgtype)::+::new();
                proto_internal!(@merge $msg $($value)*);
                proto_internal!(@msg $msg $($value)*);
                $msg
            }
        ]
    };

    // Message nested in array literal with trailing array items
    (@array $msg:ident $repeated:ident [] $($msgtype:ident)::+ { $($value:tt)* }, $($rest:tt)*) => {
        proto_internal!(@array $msg $repeated [
                {
                    let mut $msg = $($msgtype)::+::new();
                    proto_internal!(@merge $msg $($value)*);
                    proto_internal!(@msg $msg $($value)*);
                    $msg
                }
            ] $($rest)*)
    };
    // with leading ::
    (@array $msg:ident $repeated:ident [] ::$($msgtype:ident)::+ { $($value:tt)* }, $($rest:tt)*) => {
        proto_internal!(@array $msg $repeated [
                {
                    let mut $msg = ::$($msgtype)::+::new();
                    proto_internal!(@merge $msg $($value)*);
                    proto_internal!(@msg $msg $($value)*);
                    $msg
                }
            ] $($rest)*)
    };
    // Message nested in [] literal
    (@array $msg:ident $repeated:ident [] $($msgtype:ident)::+ { $($value:tt)* }) => {
        [
            {
                let mut $msg = $($msgtype)::+::new();
                proto_internal!(@merge $msg $($value)*);
                proto_internal!(@msg $msg $($value)*);
                $msg
            }
        ]
    };
    (@array $msg:ident $repeated:ident [] ::$($msgtype:ident)::+ { $($value:tt)* }) => {
        [
            {
                let mut $msg = ::$($msgtype)::+::new();
                proto_internal!(@merge $msg $($value)*);
                proto_internal!(@msg $msg $($value)*);
                $msg
            }
        ]
    };

    (@array $msg:ident $repeated:ident [$($vals:expr),+] $expr:expr, $($rest:tt)*) => {
        proto_internal!(@array $msg $repeated [$($vals),+, $expr]  $($rest)*)
    };
    (@array $msg:ident $repeated:ident [$($vals:expr),+] $expr:expr) => {
        [$($vals),+, $expr]
    };
    (@array $msg:ident $repeated:ident [] $expr:expr, $($rest:tt)*) => {
        proto_internal!(@array $msg $repeated [$expr]  $($rest)*)
    };
    (@array $msg:ident $repeated:ident [] $expr:expr) => {
        [$expr]
    };
    (@array $msg:ident $repeated:ident []) => {
        []
    };

    // field: expr,
    (@msg $msg:ident $ident:ident : $expr:expr, $($rest:tt)*) => {
        // delegate without ,
        proto_internal!(@msg $msg $ident : $expr);
        proto_internal!(@msg $msg $($rest)*);
    };

    // field: expr
    (@msg $msg:ident $ident:ident : $expr:expr) => {
        $crate::__internal::paste!($msg.[<set_ $ident>]($expr));
    };

    (@msg $msg:ident ..$expr:expr) => {
    };

    (@msg $msg:ident) => {};
    (@merge $msg:ident) => {};

    // entry point
    ($msgtype:ty { $($tt:tt)* }) => {
        {
            let mut message = <$msgtype>::new();
            proto_internal!(@merge message $($tt)*);
            proto_internal!(@msg message $($tt)*);
            message
        }
    };
}
