package telegram

import (
	
	
	

	
	
	

	

	
	
	
	
	
)

// API returns *tg.Client for calling raw MTProto methods.
func ( *Client) () *tg.Client {
	return .tg
}

// Invoke invokes raw MTProto RPC method. It sends input and decodes result
// into output.
func ( *Client) ( context.Context,  bin.Encoder,  bin.Decoder) error {
	if .tracer != nil {
		 := "Invoke"
		var  []attribute.KeyValue
		if ,  := .(interface{ () uint32 });  {
			 := .()
			 = append(,
				attribute.Int64("tg.method.id_int", int64()),
				attribute.String("tg.method.id", fmt.Sprintf("%x", )),
			)
			 := .opts.Types.Get()
			if  == "" {
				 = fmt.Sprintf("0x%x", )
			} else {
				 = append(, attribute.String("tg.method.name", ))
			}
			 = fmt.Sprintf("Invoke: %s", )
		}
		,  := .tracer.Start(, ,
			trace.WithAttributes(...),
			trace.WithSpanKind(trace.SpanKindClient),
		)
		 = 
		defer .End()
	}

	return .invoker.Invoke(, , )
}

// invokeDirect directly invokes RPC method, automatically handling datacenter redirects.
func ( *Client) ( context.Context,  bin.Encoder,  bin.Decoder) error {
	if  := .invokeConn(, , );  != nil {
		// Handling datacenter migration request.
		if ,  := tgerr.As();  && strings.HasSuffix(.Type, "_MIGRATE") {
			 := .Argument
			 := .log.With(
				log.String("error_type", .Type),
				log.Int("target_dc", ),
			)
			// If migration error is FILE_MIGRATE or STATS_MIGRATE, then the method
			// called by authorized client, so we should try to transfer auth to new DC
			// and create new connection.
			if .IsOneOf("FILE_MIGRATE", "STATS_MIGRATE") {
				.Debug(, "Invoking on target DC")
				return .invokeSub(, , , )
			}

			// Otherwise we should change primary DC.
			.Info(, "Migrating to target DC")
			return .invokeMigrate(, , , )
		}

		return 
	}

	return nil
}

// invokeConn directly invokes RPC call on primary connection without any
// additional handling.
//
// If the connection dies before the request is processed by the server,
// invokeConn waits until the reconnection loop replaces the connection and
// retries the request on it, see https://github.com/gotd/td/issues/1030.
func ( *Client) ( context.Context,  bin.Encoder,  bin.Decoder) error {
	for {
		.connMux.Lock()
		 := .conn
		 := .connChanged
		.connMux.Unlock()

		 := .Invoke(, , )
		if  == nil || !errRetryableOnNewConn() {
			return 
		}

		var  <-chan struct{}
		if .ctx != nil {
			 = .ctx.Done()
		}
		.log.Debug(, "Primary connection is dead, waiting for new connection to retry",
			log.Error(),
		)
		select {
		case <-.Done():
			return errors.Wrap(.Err(), "wait for reconnect")
		case <-:
			// Client is closed, no reconnection will happen.
			return errors.Wrap(.ctx.Err(), "client closed")
		case <-:
		}
	}
}

// errRetryableOnNewConn reports whether request failed because connection
// died before the request was processed by the server (request was not sent,
// or sent but not acknowledged), so it is safe to retry the request on a new
// connection.
func ( error) bool {
	return errors.Is(, pool.ErrConnDead) || errors.Is(, rpc.ErrEngineClosed)
}