package telegram

import (
	
	

	

	

	
	
	
	
	
	
	
)

// CloseInvoker is a closeable tg.Invoker.
type CloseInvoker interface {
	tg.Invoker
	Close() error
}

func ( *Client) ( int,  int64,  func() pool.Conn) (*pool.DC, error) {
	select {
	case <-.ctx.Done():
		return nil, errors.Wrap(.ctx.Err(), "client already closed")
	default:
	}

	 := pool.NewDC(.ctx, , , pool.DCOptions{
		Logger:             .log.Named("pool").With(log.Int("dc_id", )).Logger(),
		MaxOpenConnections: ,
	})

	return , nil
}

// Pool creates new multi-connection invoker to current DC.
func ( *Client) ( int64) (CloseInvoker, error) {
	if  < 0 {
		return nil, errors.Errorf("invalid max value %d", )
	}

	 := .session.Load()
	return .createPool(.DC, , func() pool.Conn {
		 := .connsCounter.Inc()
		return .createConn(, manager.ConnModeData, nil, func( error) {
			// Primary pool connections share persisted primary session state.
			.handlePrimaryConnDead()
			if .onDead != nil {
				.onDead()
			}
		})
	})
}

func ( *Client) (
	 context.Context,
	 int,
	 int64,
	 mtproto.Dialer,
	 manager.ConnMode,
) (*pool.DC, error) {
	if  < 0 {
		return nil, errors.Errorf("invalid max value %d", )
	}

	 := dcs.FindDCs(.cfg.Load().DCOptions, , false)
	if len() < 1 {
		return nil, errors.Errorf("unknown DC %d", )
	}
	.log.Debug(, "Creating pool",
		log.Int("dc_id", ),
		log.Int64("max", ),
		log.Int("candidates", len()),
	)

	 := .opts
	if  == manager.ConnModeCDN {
		// CDN datacenters reject auth.bindTempAuthKey with CDN_METHOD_INVALID:
		// a CDN connection must authenticate with a permanent key only, without
		// PFS. The official clients do the same, forcing perm-key-only on CDN
		// DCs regardless of the global PFS setting. opts is a local copy of
		// c.opts, so this disables PFS for the CDN pool only and leaves it
		// intact for the primary and data connections.
		.EnablePFS = false

		// TDesktop-compatible gate: CDN connection is allowed only when keyset
		// for requested CDN DC is present (or can be fetched).
		,  := .cachedCDNKeysForDC()
		if ! || len() == 0 {
			,  := .fetchCDNKeysForDC(, )
			if  != nil {
				return nil, errors.Wrapf(, "fetch CDN public keys for DC %d", )
			}
			 = 
		}
		if len() == 0 {
			return nil, errors.Errorf("no CDN public keys available for CDN DC %d", )
		}
		// Keep CDN keys first and extend with bundled keys for fingerprint
		// compatibility fallback, matching TDesktop key lookup behavior.
		.PublicKeys = mergePublicKeys(, .PublicKeys)
	}
	// suppressSetup temporarily disables per-connection transfer hook while
	// explicit first transfer below is running, avoiding duplicate import.
	var  atomic.Bool
	,  := .createPool(, , func() pool.Conn {
		 := .connsCounter.Inc()

		.sessionsMux.Lock()
		 := .sessions
		if  == manager.ConnModeCDN {
			// Keep CDN auth key lifecycle separated from regular DC sessions.
			 = .cdnSessions
		}
		,  := []
		if ! {
			 = pool.NewSyncSession(pool.Session{DC: })
			[] = 
		}
		.sessionsMux.Unlock()

		,  := .Options()
		 := manager.SetupCallback(nil)
		 := .asHandler()
		if  != manager.ConnModeCDN &&
			.AuthKey.Zero() &&
			.session.Load().DC !=  &&
			!.Load() {
			// Non-main DC key must be authorized via auth.export/import after
			// local key generation.
			 = .dcTransferSetup()
		}
		if  == manager.ConnModeCDN {
			// CDN pools do not process updates and use dedicated session store.
			 = .asCDNHandler()
		}
		.Logger = .log.Named("conn").With(
			log.Int64("conn_id", ),
			log.Int("dc_id", ),
		).Logger()
		return .create(
			, , .appID,
			, manager.ConnOptions{
				DC:      ,
				Device:  .device,
				Handler: ,
				Setup:   ,
				OnDead: func( error) {
					if  == manager.ConnModeCDN {
						// CDN dead handler also manages CDN key invalidation.
						.handleCDNConnDead(, )
						return
					}
					.handleDCConnDead(, )
				},
			},
		)
	})
	if  != nil {
		return nil, errors.Wrap(, "create pool")
	}

	if  == manager.ConnModeCDN {
		// No auth transfer for CDN mode: CDN API uses file tokens and does not
		// require auth.export/import bootstrap.
		return , nil
	}

	// First transfer is done explicitly to preserve old behavior: return
	// transfer errors from DC pool creation. Setup callback remains enabled for
	// future reconnections when keys are re-generated inside the pool.
	.Store(true)
	_,  = .transfer(, tg.NewClient(), )
	.Store(false)
	if  != nil {
		// Ignore case then we are not authorized.
		if auth.IsUnauthorized() {
			return , nil
		}

		// Kill pool if we got error.
		_ = .Close()
		return nil, errors.Wrap(, "transfer")
	}

	return , nil
}

// DC creates new multi-connection invoker to given DC.
func ( *Client) ( context.Context,  int,  int64) (CloseInvoker, error) {
	return .dc(, , , .primaryDC(), manager.ConnModeData)
}

// MediaOnly creates new multi-connection invoker to given DC ID.
// It connects to MediaOnly DCs.
func ( *Client) ( context.Context,  int,  int64) (CloseInvoker, error) {
	return .dc(, , , func( context.Context) (transport.Conn, error) {
		return .resolver.MediaOnly(, , .dcList())
	}, manager.ConnModeData)
}

// CDN creates new multi-connection invoker to given CDN DC ID.
// It connects to CDN DCs.
func ( *Client) ( context.Context,  int,  int64) (CloseInvoker, error) {
	if  < 0 {
		return nil, errors.Errorf("invalid max value %d", )
	}
	 := normalizeCDNPoolMax()

	if ,  := .cdnPools.acquire(, );  {
		// Reuse existing pool to avoid extra TCP/MTProto handshakes.
		return , nil
	}

	// Keep shared CDN pools per DC with max-aware reuse.
	,  := .dc(, , , func( context.Context) (transport.Conn, error) {
		return .resolver.CDN(, , .dcList())
	}, manager.ConnModeCDN)
	if  != nil {
		return nil, 
	}

	,  := .cdnPools.publishOrAcquire(, , )
	if  {
		// Lost race: another goroutine already published suitable pool.
		_ = .Close()
		return , nil
	}
	return , nil
}