package manager

import (
	
	
	
	

	
	
	

	
	
	
	
	
	
	
)

type protoConn interface {
	Invoke(ctx context.Context, input bin.Encoder, output bin.Decoder) error
	Run(ctx context.Context, f func(ctx context.Context) error) error
	Ping(ctx context.Context) error
}

//go:generate go run -modfile=../../../_tools/go.mod golang.org/x/tools/cmd/stringer -type=ConnMode

// ConnMode represents connection mode.
type ConnMode byte

const (
	// ConnModeUpdates is update connection mode.
	ConnModeUpdates ConnMode = iota
	// ConnModeData is data connection mode.
	ConnModeData
	// ConnModeCDN is CDN connection mode.
	ConnModeCDN
)

// Conn is a Telegram client connection.
type Conn struct {
	// Connection parameters.
	mode ConnMode // immutable
	dc   int      // immutable
	// MTProto connection.
	proto protoConn // immutable

	// InitConnection parameters.
	appID  int          // immutable
	device DeviceConfig // immutable

	// setup is callback which called after initConnection, but before ready signaling.
	// This is necessary to transfer auth from previous connection to another DC.
	setup SetupCallback // nilable

	// onDead is called on connection death.
	onDead func(error)

	// Wrappers for external world, like logs or PRNG.
	// Should be immutable.
	clock clock.Clock // immutable
	log   log.Helper // immutable

	// Handler passed by client.
	handler Handler // immutable

	// State fields.
	cfg tg.Config
	// cdnNeedsInit mirrors TDesktop connectionInited state for CDN transport.
	// true means requests must go via invokeWithLayer(initConnection).
	cdnNeedsInit atomic.Bool
	// pending buffers OnSession events until initConnection config is available.
	pending []mtproto.Session
	ongoing int
	latest  time.Time
	mux     sync.Mutex

	sessionInit *tdsync.Ready // immutable
	gotConfig   *tdsync.Ready // immutable
	dead        *tdsync.Ready // immutable

	connBackoff func(ctx context.Context) backoff.BackOff // immutable
}

// OnSession implements mtproto.Handler.
func ( *Conn) ( mtproto.Session) error {
	.log.Info(context.Background(), "SessionInit")
	.sessionInit.Signal()

	// Quote (PFS): "Once auth.bindTempAuthKey has been executed successfully,
	// the client can continue generating API calls as usual."
	// Link: https://core.telegram.org/api/pfs
	//
	// In PFS mode bind is performed before initConnection, so OnSession can happen
	// before config is ready. We must not block read-loop handler here.
	.mux.Lock()
	.pending = append(.pending, )
	.mux.Unlock()

	if !.configReady() {
		return nil
	}
	return .flushPendingSession()
}

func ( *Conn) () bool {
	select {
	case <-.gotConfig.Ready():
		return true
	default:
		return false
	}
}

func ( *Conn) () error {
	.mux.Lock()
	 := append([]mtproto.Session(nil), .pending...)
	 := .cfg
	.pending = .pending[:0]
	.mux.Unlock()
	if len() == 0 {
		return nil
	}
	for ,  := range  {
		// Preserve event ordering in case multiple session events arrive before
		// config is ready.
		if  := .handler.OnSession(, );  != nil {
			return 
		}
	}
	return nil
}

func ( *Conn) () func() {
	 := .clock.Now()

	.mux.Lock()
	defer .mux.Unlock()

	.ongoing++
	.latest = 

	return func() {
		.mux.Lock()
		defer .mux.Unlock()

		.ongoing--
		 := .clock.Now()
		.latest = 

		.log.Debug(context.Background(), "Invoke",
			log.Duration("duration", .Sub()),
			log.Int("ongoing", .ongoing),
		)
	}
}

// Run initialize connection.
func ( *Conn) ( context.Context) ( error) {
	defer .dead.Signal()
	defer func() {
		if  != nil && .Err() == nil {
			.log.Debug(, "Connection dead", log.Error())
			if .onDead != nil {
				.onDead()
			}
		}
	}()
	return .proto.Run(, func( context.Context) error {
		// Signal death on init error to unblock waiters in waitSession/OnSession.
		 := .init()
		if  != nil {
			.dead.Signal()
		}
		return 
	})
}

func ( *Conn) ( context.Context) error {
	select {
	// Connection is considered ready only after mode-specific init succeeded.
	case <-.gotConfig.Ready():
		return nil
	case <-.dead.Ready():
		return pool.ErrConnDead
	case <-.Done():
		return .Err()
	}
}

// Ready returns channel to determine connection readiness.
// Useful for pooling.
func ( *Conn) () <-chan struct{} {
	// Pool should expose readiness only when Invoke can send API calls.
	return .gotConfig.Ready()
}

// Invoke implements Invoker.
func ( *Conn) ( context.Context,  bin.Encoder,  bin.Decoder) error {
	// Tracking ongoing invokes.
	defer .trackInvoke()()
	if  := .waitSession();  != nil {
		return errors.Wrap(, "waitSession")
	}

	if .mode == ConnModeCDN {
		// CDN mode has dedicated request wrapping rules (see invokeCDN).
		 := .invokeCDN(, , )
		return 
	}
	 := .wrapRequest(noopDecoder{})
	 := .wrapRequest(&tg.InvokeWithLayerRequest{
		Layer: tg.Layer,
		Query: ,
	})
	 := .proto.Invoke(, , )
	return 
}
func ( *Conn) (
	 context.Context,
	 bin.Encoder,
	 bin.Decoder,
) error {
	// TDesktop model:
	// - while connection is "not inited": wrap every query in invokeWithLayer(initConnection);
	// - after first successful reply: use raw CDN methods;
	// - if server returns CONNECTION_NOT_INITED/LAYER_INVALID on raw call:
	//   mark "not inited" and retry wrapped once.
	if .cdnNeedsInit.Load() {
		 := .invokeCDNWrapped(, , )
		if  == nil {
			.cdnNeedsInit.Store(false)
			return nil
		}
		return 
	}

	 := .invokeCDNRaw(, , )
	if  == nil {
		return nil
	}
	if .shouldCDNRetryWrapped() {
		.cdnNeedsInit.Store(true)
		 := .invokeCDNWrapped(, , )
		if  == nil {
			.cdnNeedsInit.Store(false)
			return nil
		}
		return 
	}
	return 
}
func ( *Conn) ( context.Context,  bin.Encoder,  bin.Decoder) error {
	 := &tg.InvokeWithLayerRequest{
		Layer: tg.Layer,
		Query: .cdnInitRequest(noopDecoder{}),
	}
	return .proto.Invoke(, , )
}
func ( *Conn) ( context.Context,  bin.Encoder,  bin.Decoder) error {
	return .proto.Invoke(, , )
}
func ( *Conn) ( error) bool {
	if  == nil {
		return false
	}
	if ,  := tgerr.As();  {
		// Retry wrapped only for not-inited/layer-invalid transport state.
		 := .IsOneOf(
			"CONNECTION_NOT_INITED",
			"CONNECTION_LAYER_INVALID",
		)
		return 
	}
	return false
}

// OnMessage implements mtproto.Handler.
func ( *Conn) ( *bin.Buffer) error {
	return .handler.OnMessage()
}

type noopDecoder struct {
	bin.Encoder
}

func ( noopDecoder) ( *bin.Buffer) error {
	return errors.New("not implemented")
}

func ( *Conn) ( bin.Object) bin.Object {
	if .mode == ConnModeData {
		return &tg.InvokeWithoutUpdatesRequest{
			Query: ,
		}
	}

	return 
}

func ( *Conn) ( bin.Object) bin.Object {
	// Match TDesktop CDN init wrapper:
	// only device/system are anonymized, the rest of initConnection
	// parameters stay aligned with regular connection settings.
	return &tg.InitConnectionRequest{
		APIID:          .appID,
		DeviceModel:    "n/a",
		SystemVersion:  "n/a",
		AppVersion:     .device.AppVersion,
		SystemLangCode: .device.SystemLangCode,
		LangPack:       .device.LangPack,
		LangCode:       .device.LangCode,
		Proxy:          .device.Proxy,
		Params:         .device.Params,
		Query:          ,
	}
}
func ( *Conn) ( context.Context) error {
	.log.Debug(, "Initializing")

	if .mode == ConnModeCDN {
		// CDN connections skip help.getConfig init flow and become ready
		// immediately after MTProto auth-key exchange.
		.cdnNeedsInit.Store(true)
		.mux.Lock()
		.latest = .clock.Now()
		.cfg = tg.Config{ThisDC: .dc}
		.mux.Unlock()
		.gotConfig.Signal()
		 := .flushPendingSession()
		return 
	}
	 := .wrapRequest(&tg.InitConnectionRequest{
		APIID:          .appID,
		DeviceModel:    .device.DeviceModel,
		SystemVersion:  .device.SystemVersion,
		AppVersion:     .device.AppVersion,
		SystemLangCode: .device.SystemLangCode,
		LangPack:       .device.LangPack,
		LangCode:       .device.LangCode,
		Proxy:          .device.Proxy,
		Params:         .device.Params,
		Query:          .wrapRequest(&tg.HelpGetConfigRequest{}),
	})
	 := .wrapRequest(&tg.InvokeWithLayerRequest{
		Layer: tg.Layer,
		Query: ,
	})

	var  tg.Config
	if  := backoff.RetryNotify(func() error {
		if  := .proto.Invoke(, , &);  != nil {
			if tgerr.Is(, tgerr.ErrFloodWait) {
				// Server sometimes returns FLOOD_WAIT(0) if you create
				// multiple connections in short period of time.
				//
				// See https://github.com/gotd/td/issues/388.
				return errors.Wrap(, "flood wait")
			}
			// Not retrying other errors.
			return backoff.Permanent(errors.Wrap(, "invoke"))
		}

		return nil
	}, .connBackoff(), func( error,  time.Duration) {
		.log.Debug(, "Retrying connection initialization",
			log.Error(), log.Duration("duration", ),
		)
	});  != nil {
		return errors.Wrap(, "initConnection")
	}

	if .setup != nil {
		if  := .setup(, );  != nil {
			return errors.Wrap(, "setup")
		}
	}

	.mux.Lock()
	.latest = .clock.Now()
	.cfg = 
	.mux.Unlock()

	.gotConfig.Signal()
	 := .flushPendingSession()
	return 
}

// Ping calls ping for underlying protocol connection.
func ( *Conn) ( context.Context) error {
	return .proto.Ping()
}