import * as t from '@babel/types';
import { ProtoType } from '@cosmology/types';
import { BILLION, identifier, TypeLong } from '../../../utils';
import { ProtoParseContext } from '../../context';
import { getDefaultTSTypeFromProtoType, getFieldNames, getDefaultTSTypeFromAminoTypeDefault } from '../../types';
import { getInterfaceToAminoName } from '../implements';
import { ToAminoJSONMethod } from './index';
import { shouldOmitEmpty } from '@cosmology/utils';

const setValue = (args: ToAminoJSONMethod, valExpr?: t.Expression) => {
    const { propName, origName } = getFieldNames(args.field);

    const omitEmpty = shouldOmitEmpty(args.context, args.field);

    valExpr = valExpr ?? t.memberExpression(t.identifier("message"), t.identifier(propName));

      if (omitEmpty) {
          valExpr = t.conditionalExpression(t.binaryExpression(
              "===",
              valExpr,
              getDefaultTSTypeFromProtoType(args.context, args.field, args.isOneOf, true)
          ), t.identifier('undefined'), valExpr);
      } else {
          valExpr = t.logicalExpression(
              "??",
              valExpr,
              getDefaultTSTypeFromProtoType(args.context, args.field, args.isOneOf, true)
          );
    }

    return t.expressionStatement(
      t.assignmentExpression(
        "=",
        t.memberExpression(t.identifier("obj"), t.identifier(origName)),
        valExpr
      )
    );
  };

  export const toAminoJSON = {

      scalar(args: ToAminoJSONMethod, valExpr?: t.Expression) {
        return setValue(args, valExpr)
      },

      string(args: ToAminoJSONMethod) {
        let valueExpr: t.Expression;
        const useCosmosSDKDec = args.context.pluginValue('aminoEncoding.customTypes.useCosmosSDKDec');

        if(useCosmosSDKDec){
          const isCosmosSDKDec =
              (args.field.options?.['(gogoproto.customtype)'] ==
                  'github.com/cosmos/cosmos-sdk/types.Dec') ||
              (args.field.options?.['(gogoproto.customtype)'] ==
                  'cosmossdk.io/math.LegacyDec');

          if (isCosmosSDKDec) {
              args.context.addUtil('padDecimal');
              const { propName } = getFieldNames(args.field);
              valueExpr = t.callExpression(
                  t.identifier('padDecimal'),
                  [
                      t.memberExpression(
                          t.identifier('message'),
                          t.identifier(propName)
                      )
                  ]
              )
          }
        }

        return setValue(args, valueExpr);
      },
      double(args: ToAminoJSONMethod) {
          return toAminoJSON.scalar(args);
      },
      float(args: ToAminoJSONMethod) {
          return toAminoJSON.scalar(args);
      },
      bool(args: ToAminoJSONMethod) {
          return toAminoJSON.scalar(args);
      },

      number(args: ToAminoJSONMethod) {
          return toAminoJSON.scalar(args);
      },

      int32(args: ToAminoJSONMethod) {
          return toAminoJSON.scalar(args);
      },

      uint32(args: ToAminoJSONMethod) {
          return toAminoJSON.scalar(args);
      },

      sint32(args: ToAminoJSONMethod) {
          return toAminoJSON.scalar(args);
      },
      fixed32(args: ToAminoJSONMethod) {
          return toAminoJSON.scalar(args);
      },
      sfixed32(args: ToAminoJSONMethod) {
          return toAminoJSON.scalar(args);
      },

      // obj.big = message.big ? message.big.toString() : "0";
      // obj.o_big = message.oBig !== BigInt(0) ? message.oBig.toString() : undefined;
      long(args: ToAminoJSONMethod) {
        const { propName, origName } = getFieldNames(args.field);

        const omitEmpty = shouldOmitEmpty(args.context, args.field);
        const nullTest = omitEmpty
            ? TypeLong.getLongNotZero(propName, args.context)
            : t.memberExpression(
                  t.identifier("message"),
                  t.identifier(propName)
              );

        const template = require('@babel/template').default;
        // Create a template that represents message.oBig.toString()
        const expressionTemplate = template.expression(`
          MESSAGE.OBJECT?.METHOD()
        `);

        // Generate AST node from the template by providing the identifiers
        const messageNode = expressionTemplate({
          MESSAGE: t.identifier('message'),
          OBJECT: t.identifier(propName),
          METHOD: t.identifier('toString'),
        });
        return t.expressionStatement(
            t.assignmentExpression(
                "=",
                t.memberExpression(t.identifier("obj"), t.identifier(origName)),
                t.conditionalExpression(
                    nullTest,
                    messageNode,
                    omitEmpty ? t.identifier("undefined") : t.stringLiteral("0")
                )
            )
        );
      },
      int64(args: ToAminoJSONMethod) {
          return toAminoJSON.long(args);
      },
      uint64(args: ToAminoJSONMethod) {
          return toAminoJSON.long(args);
      },
      sint64(args: ToAminoJSONMethod) {
          return toAminoJSON.long(args);
      },
      fixed64(args: ToAminoJSONMethod) {
          return toAminoJSON.long(args);
      },
      sfixed64(args: ToAminoJSONMethod) {
          return toAminoJSON.long(args);
      },

      // obj.proto = message.proto ? AccessConfig.toAmino(message.proto) : AccessConfig.toAmino(AccessConfig.fromPartial({}));
      // obj.o_proto = message.oProto ? AccessConfig.toAmino(message.oProto) : undefined;
      protoType(args: ToAminoJSONMethod) {
          const { propName, origName } = getFieldNames(args.field);
          const name = args.context.getTypeName(args.field);

          const omitEmpty = shouldOmitEmpty(args.context, args.field);

          let defaultValue: t.Expression = omitEmpty ?  t.identifier('undefined') : getDefaultTSTypeFromAminoTypeDefault(args.context, args.field);

          if (args.field.type === 'ibc.core.client.v1.Height') {
              defaultValue = t.objectExpression([])
          }

          return t.expressionStatement(
              t.assignmentExpression(
                  '=',
                  t.memberExpression(
                      t.identifier('obj'),
                      t.identifier(origName)
                  ),
                  t.conditionalExpression(
                      t.memberExpression(
                          t.identifier('message'),
                          t.identifier(propName)
                      ),
                      t.callExpression(
                          t.memberExpression(
                              t.identifier(name),
                              t.identifier('toAmino')
                          ),
                          [
                              t.memberExpression(
                                  t.identifier('message'),
                                  t.identifier(propName)
                              ),
                              ...(args.context.options.interfaces.enabled && args.context.options.interfaces.useUseInterfacesParams ? [
                                  t.identifier('useInterfaces')
                              ] : []),
                          ]
                      ),
                      defaultValue
                  )
              )
          );
      },

      anyType(args: ToAminoJSONMethod) {
          const { propName, origName } = getFieldNames(args.field);
          const interfaceName = args.field.options['(cosmos_proto.accepts_interface)'];
          const interfaceFnName = getInterfaceToAminoName(interfaceName)

          args.context.getTypeName(args.field);

          const omitEmpty = shouldOmitEmpty(args.context, args.field);

          let defaultValue: t.Expression = !omitEmpty ? t.objectExpression([
            t.objectProperty(t.identifier("type"), t.stringLiteral("")),
            t.objectProperty(t.identifier("value"), t.objectExpression([])),
          ]) : t.identifier('undefined');

          let aminoFuncExpr: t.Expression = t.callExpression(
            t.identifier(interfaceFnName),
            [
                t.tsAsExpression(
                    t.memberExpression(
                        t.identifier('message'),
                        t.identifier(propName)
                    ),
                    t.tsTypeReference(
                        t.identifier('Any')
                    )
                ),
                ...(args.context.options.interfaces.enabled && args.context.options.interfaces.useUseInterfacesParams ? [
                    t.identifier('useInterfaces')
                ] : []),
            ]
          );

          const isGlobalRegistry = args.context.options.interfaces?.useGlobalDecoderRegistry;

          if(isGlobalRegistry) {
            aminoFuncExpr = t.callExpression(
              t.memberExpression(t.identifier('GlobalDecoderRegistry'), t.identifier('toAminoMsg')),
              [
                t.memberExpression(
                  t.identifier('message'),
                  t.identifier(propName)
                )
              ]
            )
          }

          return t.expressionStatement(
              t.assignmentExpression(
                  '=',
                  t.memberExpression(
                      t.identifier('obj'),
                      t.identifier(origName)
                  ),
                  t.conditionalExpression(
                      t.memberExpression(
                          t.identifier('message'),
                          t.identifier(propName)
                      ),
                      aminoFuncExpr,
                      defaultValue
                  )
              )
          );
      },

      type(args: ToAminoJSONMethod) {
          if (
              !args.context.options.aminoEncoding.useLegacyInlineEncoding &&
              args.context.options.interfaces.enabled &&
              args.field.type === 'google.protobuf.Any' &&
              args.field.options['(cosmos_proto.accepts_interface)']

          ) {
              return toAminoJSON.anyType(args);
          }

          return toAminoJSON.protoType(args);
      },

      enum(args: ToAminoJSONMethod) {
        return toAminoJSON.scalar(args);
      },

      bytes(args: ToAminoJSONMethod) {
          args.context.addUtil('base64FromBytes');
          const { propName, origName } = getFieldNames(args.field);


          const omitEmpty = shouldOmitEmpty(args.context,args.field);

          let defaultValue: t.Expression = !omitEmpty ? t.stringLiteral("") : t.identifier('undefined');

          const expr = t.callExpression(
            t.identifier('base64FromBytes'),
            [
                t.memberExpression(
                    t.identifier('message'),
                    t.identifier(propName)
                )
            ]
          );

          return t.expressionStatement(
            t.assignmentExpression(
                '=',
                t.memberExpression(
                    t.identifier('obj'),
                    t.identifier(origName)
                ),
                t.conditionalExpression(
                    t.memberExpression(
                        t.identifier('message'),
                        t.identifier(propName)
                    ),
                    expr,
                    defaultValue
                )
            )
          );
      },

      duration(args: ToAminoJSONMethod) {
          return toAminoJSON.type(args);
      },

      timestamp(args: ToAminoJSONMethod) {
          const timestampFormat = args.context.pluginValue(
              'prototypes.typingsFormat.timestamp'
          );
          switch (timestampFormat) {
              case 'timestamp':
                  return toAminoJSON.type(args);
              case 'date':
              default:
                  return toAminoJSON.timestampDate(args);
          }
      },

      timestampDate(args: ToAminoJSONMethod) {
          const { propName, origName } = getFieldNames(args.field);
          args.context.addUtil('toTimestamp');


          const omitEmpty = shouldOmitEmpty(args.context,args.field);

          let defaultValue: t.Expression = !omitEmpty ? getDefaultTSTypeFromProtoType(args.context, args.field, args.isOneOf, true) : t.identifier('undefined');

          return t.expressionStatement(
              t.assignmentExpression(
                  '=',
                  t.memberExpression(
                      t.identifier('obj'),
                      t.identifier(origName)
                  ),
                  t.conditionalExpression(
                      t.memberExpression(
                          t.identifier('message'),
                          t.identifier(propName)
                      ),
                      t.callExpression(
                          t.memberExpression(
                              t.identifier('Timestamp'),
                              t.identifier('toAmino')
                          ),
                          [
                              t.callExpression(
                                  t.identifier('toTimestamp'),
                                  [
                                      t.memberExpression(
                                          t.identifier('message'),
                                          t.identifier(propName)
                                      )
                                  ]
                              )
                          ]
                      ),
                      defaultValue
                  )
              )
          );
      },

      pubkey(args: ToAminoJSONMethod) {
          args.context.addUtil('decodePubkey');

          const { propName, origName } = getFieldNames(args.field);

          const omitEmpty = shouldOmitEmpty(args.context,args.field);

          let defaultValue: t.Expression = !omitEmpty ? getDefaultTSTypeFromProtoType(args.context, args.field, args.isOneOf, true) : t.identifier('undefined');

          return t.expressionStatement(
              t.assignmentExpression(
                  '=',
                  t.memberExpression(
                      t.identifier('obj'),
                      t.identifier(origName)
                  ),
                  t.conditionalExpression(
                      t.memberExpression(
                          t.identifier('message'),
                          t.identifier(propName)
                      ),
                      //
                      t.callExpression(
                        t.identifier('decodePubkey'),
                        [
                            t.memberExpression(
                                t.identifier('message'),
                                t.identifier(propName)
                            ),
                        ]
                      ),
                      //
                      defaultValue
                  )
              )
          );
      },

      rawBytes(args: ToAminoJSONMethod) {
          args.context.addUtil('fromUtf8');

          const { propName, origName } = getFieldNames(args.field);


          const omitEmpty = shouldOmitEmpty(args.context,args.field);

          let defaultValue: t.Expression = !omitEmpty ? t.objectExpression([]) : t.identifier('undefined');

          return t.expressionStatement(
              t.assignmentExpression(
                  '=',
                  t.memberExpression(
                      t.identifier('obj'),
                      t.identifier(origName)
                  ),
                  t.conditionalExpression(
                      t.memberExpression(
                          t.identifier('message'),
                          t.identifier(propName)
                      ),
                      //
                      t.callExpression(
                          t.memberExpression(
                              t.identifier('JSON'),
                              t.identifier('parse')
                          ),
                          [
                              t.callExpression(
                                  t.identifier('fromUtf8'),
                                  [
                                      t.memberExpression(
                                          t.identifier('message'),
                                          t.identifier(propName)
                                      ),
                                  ]
                              )
                          ]
                      ),
                      //
                      defaultValue
                  )
              )
          );
      },

      wasmByteCode(args: ToAminoJSONMethod) {
          args.context.addUtil('toBase64');

          const { propName, origName } = getFieldNames(args.field);


          const omitEmpty = shouldOmitEmpty(args.context,args.field);

          let defaultValue: t.Expression = !omitEmpty ? t.stringLiteral("") : t.identifier('undefined');

          return t.expressionStatement(
              t.assignmentExpression(
                  '=',
                  t.memberExpression(
                      t.identifier('obj'),
                      t.identifier(origName)
                  ),
                  t.conditionalExpression(
                      t.memberExpression(
                          t.identifier('message'),
                          t.identifier(propName)
                      ),
                      //
                      t.callExpression(
                          t.identifier('toBase64'),
                          [
                              t.memberExpression(
                                  t.identifier('message'),
                                  t.identifier(propName)
                              ),
                          ]
                      ),
            //
                  defaultValue
                )
            )
        );
    },



    keyHash(args: ToAminoJSONMethod) {

        const { propName, origName } = getFieldNames(args.field);
        const keyType = args.field.keyType;
        const valueType = args.field.parsedType.name;

        let toAminoJSON = null;
        switch (valueType) {
            case 'string':
                          toAminoJSON = t.identifier('v')
                break;
            case 'uint32':
            case 'int32':
                toAminoJSON = t.callExpression(
                    t.memberExpression(
                        t.identifier('Math'),
                        t.identifier('round')
                    ),
                    [
                        t.identifier('v')
                    ]
                )
                break;
            case 'int64':
            case 'uint64':
                toAminoJSON = t.callExpression(
                    t.memberExpression(
                        t.identifier('v'),
                        t.identifier('toString')
                    ),
                    []
                )
                break;
            default:
                toAminoJSON = t.callExpression(
                    t.memberExpression(
                        t.identifier(valueType),
                        t.identifier('toAmino')
                    ),
                    [
                        t.identifier('v')
                    ]
                )
        }


        return [
            t.expressionStatement(
                t.assignmentExpression(
                    '=',
                    t.memberExpression(
                        t.identifier('obj'),
                        t.identifier(origName)
                    ),
                    t.objectExpression([])
                )
            ),
            //
            t.ifStatement(
                t.memberExpression(
                    t.identifier('message'),
                    t.identifier(propName)
                ),
                t.blockStatement([
                    t.expressionStatement(
                        t.callExpression(
                            t.memberExpression(
                                t.callExpression(
                                    t.memberExpression(
                                        t.identifier('Object'),
                                        t.identifier('entries')
                                    ),
                                    [
                                        t.memberExpression(
                                            t.identifier('message'),
                                            t.identifier(propName)
                                        )
                                    ]
                                ),
                                t.identifier('forEach')
                            ),
                            [
                                t.arrowFunctionExpression(
                                    [
                                        t.arrayPattern(
                                            [
                                                t.identifier('k'),
                                                t.identifier('v')
                                            ]
                                        )
                                    ],
                                    t.blockStatement([
                                        t.expressionStatement(
                                            t.assignmentExpression(
                                                '=',
                                                t.memberExpression(
                                                    t.memberExpression(
                                                        t.identifier('obj'),
                                                        t.identifier(origName)
                                                    ),
                                                    t.identifier('k'),
                                                    true
                                                ),
                                                toAminoJSON
                                            )
                                        )
                                    ])
                                )
                            ]
                        )
                    )
                ])
            )
        ]
    },

    array(args: ToAminoJSONMethod, expr: t.Expression) {
        const { propName, origName } = getFieldNames(args.field);

        return t.ifStatement(
            t.memberExpression(
                t.identifier('message'),
                t.identifier(propName)
            ),
            t.blockStatement([
                t.expressionStatement(
                    t.assignmentExpression(
                        '=',
                        t.memberExpression(
                            t.identifier('obj'),
                            t.identifier(origName)
                        ),
                        t.callExpression(
                            t.memberExpression(
                                t.memberExpression(
                                    t.identifier('message'),
                                    t.identifier(propName)
                                ),
                                t.identifier('map')
                            ),
                            [
                                t.arrowFunctionExpression(
                                    [
                                        t.identifier('e')
                                    ],
                                    expr
                                )
                            ]
                        )
                    )
                )
            ]),
            t.blockStatement([
                t.expressionStatement(
                    t.assignmentExpression(
                        '=',
                        t.memberExpression(
                            t.identifier('obj'),
                            t.identifier(origName)
                        ),
                        t.memberExpression(
                            t.identifier('message'),
                            t.identifier(propName)
                        )
                    )
                )
            ])
        );
    }

};

export const arrayTypes = {
    scalar() {
        return t.identifier('e');
    },
    string(args: ToAminoJSONMethod) {
      const useCosmosSDKDec = args.context.pluginValue('aminoEncoding.customTypes.useCosmosSDKDec');

      if(useCosmosSDKDec){
        const isCosmosSDKDec =
            (args.field.options?.['(gogoproto.customtype)'] ==
                'github.com/cosmos/cosmos-sdk/types.Dec') ||
            (args.field.options?.['(gogoproto.customtype)'] ==
                'cosmossdk.io/math.LegacyDec');

        if (isCosmosSDKDec) {
            args.context.addUtil('padDecimal');
            const { propName } = getFieldNames(args.field);
            return t.callExpression(
                t.identifier('padDecimal'),
                [
                  t.identifier('e')
                ]
            )
        }
      }

      return arrayTypes.scalar();
    },
    double() {
        return arrayTypes.scalar();
    },
    float() {
        return arrayTypes.scalar();
    },
    bool() {
        return arrayTypes.scalar();
    },
    number() {
        return arrayTypes.scalar();
    },
    int32() {
        return arrayTypes.number();
    },
    uint32() {
        return arrayTypes.number();
    },
    sint32() {
        return arrayTypes.number();
    },
    fixed32() {
        return arrayTypes.number();
    },
    sfixed32() {
        return arrayTypes.number();
    },
    long(args: ToAminoJSONMethod) {
        return TypeLong.getToStringArray(args.context);
    },
    int64(args: ToAminoJSONMethod) {
        return arrayTypes.long(args);
    },
    uint64(args: ToAminoJSONMethod) {
        return arrayTypes.long(args);
    },
    sint64(args: ToAminoJSONMethod) {
        return arrayTypes.long(args);
    },
    fixed64(args: ToAminoJSONMethod) {
        return arrayTypes.long(args);
    },
    sfixed64(args: ToAminoJSONMethod) {
        return arrayTypes.long(args);
    },
    rawBytes(args: ToAminoJSONMethod) {
      args.context.addUtil("fromUtf8");

      return t.callExpression(
        t.memberExpression(t.identifier("JSON"), t.identifier("parse")),
        [
          t.callExpression(t.identifier("fromUtf8"), [t.identifier("e")]),
        ]
      );
    },

    wasmByteCode(args: ToAminoJSONMethod) {
      args.context.addUtil("toBase64");

      return t.callExpression(t.identifier("toBase64"), [
        t.identifier("e")
      ]);
    },
    bytes(args: ToAminoJSONMethod) {
      // bytes [RawContractMessage]
      if (args.field.options?.["(gogoproto.casttype)"] === "RawContractMessage") {
        return arrayTypes.rawBytes(args);
      }
      // bytes [WASMByteCode]
      if (args.field.options?.["(gogoproto.customname)"] === "WASMByteCode") {
        return arrayTypes.wasmByteCode(args);
      }

      //default
      args.context.addUtil("base64FromBytes");
      return t.callExpression(t.identifier("base64FromBytes"), [
        t.identifier("e"),
      ]);
    },
    enum() {
      return arrayTypes.scalar();
    },
    anyType(args: ToAminoJSONMethod) {
        const { propName, origName } = getFieldNames(args.field);
        const interfaceName = args.field.options['(cosmos_proto.accepts_interface)'];
        const interfaceFnName = getInterfaceToAminoName(interfaceName)

        const isGlobalRegistry = args.context.options.interfaces?.useGlobalDecoderRegistry;

        let aminoFuncExpr: t.Expression = t.callExpression(
                t.identifier(interfaceFnName),
                [
                    t.tsAsExpression(
                        t.identifier('e'),
                        t.tsTypeReference(
                            t.identifier('Any')
                        )
                    ),
                    ...(args.context.options.interfaces.enabled && args.context.options.interfaces.useUseInterfacesParams ? [
                        t.identifier('useInterfaces')
                    ] : []),
                ]
            );

        if(isGlobalRegistry) {
          aminoFuncExpr = t.callExpression(
            t.memberExpression(t.identifier('GlobalDecoderRegistry'), t.identifier('toAminoMsg')),
            [
              t.identifier('e')
            ]
          )
        }

        return t.conditionalExpression(
            t.identifier('e'),
            aminoFuncExpr,
            t.identifier('undefined')
        );
    },
    protoType(args: ToAminoJSONMethod) {
        const name = args.context.getTypeName(args.field);
        return t.conditionalExpression(
            t.identifier('e'),
            t.callExpression(
                t.memberExpression(
                    t.identifier(name),
                    t.identifier('toAmino')
                ),
                [
                    t.identifier('e'),
                    ...(args.context.options.interfaces.enabled && args.context.options.interfaces.useUseInterfacesParams ? [
                        t.identifier('useInterfaces')
                    ] : []),
                ]
            ),
            t.identifier('undefined')
        );
    },
    type(args: ToAminoJSONMethod) {
        if (
            !args.context.options.aminoEncoding.useLegacyInlineEncoding &&
            args.context.options.interfaces.enabled &&
            args.field.type === 'google.protobuf.Any' &&
            args.field.options['(cosmos_proto.accepts_interface)']

        ) {
            return arrayTypes.anyType(args);
        }
        return arrayTypes.protoType(args);
    }
}


export const toAminoMessages = {
    anyType() {
        return [
            t.variableDeclaration(
                'const',
                [
                    t.variableDeclarator(
                        identifier('obj', t.tsTypeAnnotation(t.tsAnyKeyword())),
                        t.objectExpression([])
                    )
                ]
            ),
            t.expressionStatement(
                t.assignmentExpression(
                    '=',
                    t.memberExpression(
                        t.identifier('obj'),
                        t.identifier('type')
                    ),
                    t.memberExpression(
                        t.identifier('message'),
                        t.identifier('typeUrl')
                    )
                )
            ),
            t.expressionStatement(
                t.assignmentExpression(
                    '=',
                    t.memberExpression(
                        t.identifier('obj'),
                        t.identifier('value')
                    ),
                    t.memberExpression(
                        t.identifier('message'),
                        t.identifier('value')
                    )
                )
            ),
            t.returnStatement(
                t.identifier('obj')
            )
        ]
    },
    timestamp(context: ProtoParseContext, name: string, proto: ProtoType) {
        context.addUtil('fromTimestamp');

        return t.returnStatement(
            t.callExpression(
                t.memberExpression(
                    t.callExpression(
                        t.memberExpression(
                            t.callExpression(
                                t.identifier('fromTimestamp'),
                                [t.identifier('message')]
                            ),
                            t.identifier('toISOString')
                        ),
                        []
                    ),
                    t.identifier('replace')
                ),
                [
                    t.regExpLiteral('\\.\\d+Z$'),
                    t.stringLiteral('Z')
                ]
            )
        )
    },
    duration(context: ProtoParseContext, name: string, proto: ProtoType) {
        const longType = TypeLong.getType(context);

        switch (longType) {
            case 'BigInt':
                return t.returnStatement(
                    t.callExpression(
                        t.memberExpression(
                            t.parenthesizedExpression(
                                t.binaryExpression(
                                    '+',
                                    t.binaryExpression(
                                        '*',
                                        t.memberExpression(
                                            t.identifier('message'),
                                            t.identifier('seconds'),
                                        ),
                                        t.callExpression(
                                            t.identifier('BigInt'),
                                            [t.stringLiteral("1000000000")],
                                        ),
                                    ),
                                    t.callExpression(
                                        t.identifier('BigInt'),
                                        [t.memberExpression(
                                            t.identifier('message'),
                                            t.identifier('nanos'),
                                        )],
                                    ),
                                ),
                            ),
                            t.identifier('toString'),
                        ),
                        [],
                    )
                )
            case 'Long':
            default:
                return t.returnStatement(
                    t.callExpression(
                        t.memberExpression(
                            t.binaryExpression(
                                '+',
                                t.binaryExpression(
                                    '*',
                                    t.callExpression(
                                        t.memberExpression(
                                            t.memberExpression(
                                                t.identifier('message'),
                                                t.identifier('seconds')
                                            ),
                                            t.identifier('toInt')
                                        ),
                                        []
                                    ),
                                    BILLION
                                ),
                                t.memberExpression(
                                    t.identifier('message'),
                                    t.identifier('nanos')
                                )
                            ),
                            t.identifier('toString')
                        ),
                        []
                    )
                );
        }

    }
}
