// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package types

import (
	"fmt"
	"io"
	"time"

	"github.com/pion/rtcp"
	"github.com/pion/webrtc/v4"

	"github.com/livekit/protocol/auth"
	"github.com/livekit/protocol/codecs/mime"
	"github.com/livekit/protocol/livekit"
	"github.com/livekit/protocol/logger"
	"github.com/livekit/protocol/observability/roomobs"
	"github.com/livekit/protocol/utils"

	"github.com/livekit/livekit-server/pkg/routing"
	"github.com/livekit/livekit-server/pkg/rtc/datatrack"
	"github.com/livekit/livekit-server/pkg/sfu"
	"github.com/livekit/livekit-server/pkg/sfu/buffer"
	"github.com/livekit/livekit-server/pkg/sfu/pacer"
	"github.com/livekit/livekit-server/pkg/telemetry"

	"google.golang.org/protobuf/proto"
)

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate

//counterfeiter:generate . WebsocketClient
type WebsocketClient interface {
	ReadMessage() (messageType int, p []byte, err error)
	NextReader() (messageType int, r io.Reader, err error)
	WriteMessage(messageType int, data []byte) error
	WriteControl(messageType int, data []byte, deadline time.Time) error
	SetReadDeadline(deadline time.Time) error
	Close() error
}

type AddSubscriberParams struct {
	AllTracks bool
	TrackIDs  []livekit.TrackID
}

// ---------------------------------------------

type MigrateState int32

const (
	MigrateStateInit MigrateState = iota
	MigrateStateSync
	MigrateStateComplete
)

func (m MigrateState) String() string {
	switch m {
	case MigrateStateInit:
		return "MIGRATE_STATE_INIT"
	case MigrateStateSync:
		return "MIGRATE_STATE_SYNC"
	case MigrateStateComplete:
		return "MIGRATE_STATE_COMPLETE"
	default:
		return fmt.Sprintf("%d", int(m))
	}
}

// ---------------------------------------------

type SubscribedCodecQuality struct {
	CodecMime mime.MimeType
	Quality   livekit.VideoQuality
}

// ---------------------------------------------

type ParticipantCloseReason int

const (
	ParticipantCloseReasonNone ParticipantCloseReason = iota
	ParticipantCloseReasonClientRequestLeave
	ParticipantCloseReasonRoomManagerStop
	ParticipantCloseReasonVerifyFailed
	ParticipantCloseReasonJoinFailed
	ParticipantCloseReasonJoinTimeout
	ParticipantCloseReasonMessageBusFailed
	ParticipantCloseReasonPeerConnectionDisconnected
	ParticipantCloseReasonDuplicateIdentity
	ParticipantCloseReasonMigrationComplete
	ParticipantCloseReasonStale
	ParticipantCloseReasonServiceRequestRemoveParticipant
	ParticipantCloseReasonServiceRequestDeleteRoom
	ParticipantCloseReasonSimulateMigration
	ParticipantCloseReasonSimulateNodeFailure
	ParticipantCloseReasonSimulateServerLeave
	ParticipantCloseReasonSimulateLeaveRequest
	ParticipantCloseReasonNegotiateFailed
	ParticipantCloseReasonMigrationRequested
	ParticipantCloseReasonPublicationError
	ParticipantCloseReasonSubscriptionError
	ParticipantCloseReasonDataChannelError
	ParticipantCloseReasonMigrateCodecMismatch
	ParticipantCloseReasonSignalSourceClose
	ParticipantCloseReasonRoomClosed
	ParticipantCloseReasonUserUnavailable
	ParticipantCloseReasonUserRejected
	ParticipantCloseReasonMoveFailed
	ParticipantCloseReasonAgentError
)

func (p ParticipantCloseReason) String() string {
	switch p {
	case ParticipantCloseReasonNone:
		return "NONE"
	case ParticipantCloseReasonClientRequestLeave:
		return "CLIENT_REQUEST_LEAVE"
	case ParticipantCloseReasonRoomManagerStop:
		return "ROOM_MANAGER_STOP"
	case ParticipantCloseReasonVerifyFailed:
		return "VERIFY_FAILED"
	case ParticipantCloseReasonJoinFailed:
		return "JOIN_FAILED"
	case ParticipantCloseReasonJoinTimeout:
		return "JOIN_TIMEOUT"
	case ParticipantCloseReasonMessageBusFailed:
		return "MESSAGE_BUS_FAILED"
	case ParticipantCloseReasonPeerConnectionDisconnected:
		return "PEER_CONNECTION_DISCONNECTED"
	case ParticipantCloseReasonDuplicateIdentity:
		return "DUPLICATE_IDENTITY"
	case ParticipantCloseReasonMigrationComplete:
		return "MIGRATION_COMPLETE"
	case ParticipantCloseReasonStale:
		return "STALE"
	case ParticipantCloseReasonServiceRequestRemoveParticipant:
		return "SERVICE_REQUEST_REMOVE_PARTICIPANT"
	case ParticipantCloseReasonServiceRequestDeleteRoom:
		return "SERVICE_REQUEST_DELETE_ROOM"
	case ParticipantCloseReasonSimulateMigration:
		return "SIMULATE_MIGRATION"
	case ParticipantCloseReasonSimulateNodeFailure:
		return "SIMULATE_NODE_FAILURE"
	case ParticipantCloseReasonSimulateServerLeave:
		return "SIMULATE_SERVER_LEAVE"
	case ParticipantCloseReasonSimulateLeaveRequest:
		return "SIMULATE_LEAVE_REQUEST"
	case ParticipantCloseReasonNegotiateFailed:
		return "NEGOTIATE_FAILED"
	case ParticipantCloseReasonMigrationRequested:
		return "MIGRATION_REQUESTED"
	case ParticipantCloseReasonPublicationError:
		return "PUBLICATION_ERROR"
	case ParticipantCloseReasonSubscriptionError:
		return "SUBSCRIPTION_ERROR"
	case ParticipantCloseReasonDataChannelError:
		return "DATA_CHANNEL_ERROR"
	case ParticipantCloseReasonMigrateCodecMismatch:
		return "MIGRATE_CODEC_MISMATCH"
	case ParticipantCloseReasonSignalSourceClose:
		return "SIGNAL_SOURCE_CLOSE"
	case ParticipantCloseReasonRoomClosed:
		return "ROOM_CLOSED"
	case ParticipantCloseReasonUserUnavailable:
		return "USER_UNAVAILABLE"
	case ParticipantCloseReasonUserRejected:
		return "USER_REJECTED"
	case ParticipantCloseReasonMoveFailed:
		return "MOVE_FAILED"
	case ParticipantCloseReasonAgentError:
		return "AGENT_ERROR"
	default:
		return fmt.Sprintf("%d", int(p))
	}
}

func (p ParticipantCloseReason) ToDisconnectReason() livekit.DisconnectReason {
	switch p {
	case ParticipantCloseReasonClientRequestLeave, ParticipantCloseReasonSimulateLeaveRequest:
		return livekit.DisconnectReason_CLIENT_INITIATED
	case ParticipantCloseReasonRoomManagerStop:
		return livekit.DisconnectReason_SERVER_SHUTDOWN
	case ParticipantCloseReasonVerifyFailed, ParticipantCloseReasonJoinFailed, ParticipantCloseReasonJoinTimeout, ParticipantCloseReasonMessageBusFailed:
		// expected to be connected but is not
		return livekit.DisconnectReason_JOIN_FAILURE
	case ParticipantCloseReasonPeerConnectionDisconnected:
		return livekit.DisconnectReason_CONNECTION_TIMEOUT
	case ParticipantCloseReasonDuplicateIdentity, ParticipantCloseReasonStale:
		return livekit.DisconnectReason_DUPLICATE_IDENTITY
	case ParticipantCloseReasonMigrationRequested, ParticipantCloseReasonMigrationComplete, ParticipantCloseReasonSimulateMigration:
		return livekit.DisconnectReason_MIGRATION
	case ParticipantCloseReasonServiceRequestRemoveParticipant:
		return livekit.DisconnectReason_PARTICIPANT_REMOVED
	case ParticipantCloseReasonServiceRequestDeleteRoom:
		return livekit.DisconnectReason_ROOM_DELETED
	case ParticipantCloseReasonSimulateNodeFailure, ParticipantCloseReasonSimulateServerLeave:
		return livekit.DisconnectReason_SERVER_SHUTDOWN
	case ParticipantCloseReasonNegotiateFailed, ParticipantCloseReasonPublicationError, ParticipantCloseReasonSubscriptionError,
		ParticipantCloseReasonDataChannelError, ParticipantCloseReasonMigrateCodecMismatch, ParticipantCloseReasonMoveFailed:
		return livekit.DisconnectReason_STATE_MISMATCH
	case ParticipantCloseReasonSignalSourceClose:
		return livekit.DisconnectReason_SIGNAL_CLOSE
	case ParticipantCloseReasonRoomClosed:
		return livekit.DisconnectReason_ROOM_CLOSED
	case ParticipantCloseReasonUserUnavailable:
		return livekit.DisconnectReason_USER_UNAVAILABLE
	case ParticipantCloseReasonUserRejected:
		return livekit.DisconnectReason_USER_REJECTED
	case ParticipantCloseReasonAgentError:
		return livekit.DisconnectReason_AGENT_ERROR
	default:
		// the other types will map to unknown reason
		return livekit.DisconnectReason_UNKNOWN_REASON
	}
}

// IsIntentionalDisconnect reports whether a disconnect reason represents an
// intentional/expected closure (client leaving, admin action, room teardown,
// migration, etc.) as opposed to a connection failure.
func IsIntentionalDisconnect(reason livekit.DisconnectReason) bool {
	switch reason {
	case livekit.DisconnectReason_CLIENT_INITIATED,
		livekit.DisconnectReason_SERVER_SHUTDOWN,
		livekit.DisconnectReason_DUPLICATE_IDENTITY,
		livekit.DisconnectReason_MIGRATION,
		livekit.DisconnectReason_PARTICIPANT_REMOVED,
		livekit.DisconnectReason_ROOM_DELETED,
		livekit.DisconnectReason_ROOM_CLOSED:
		return true
	}
	return false
}

// ---------------------------------------------

type SignallingCloseReason int

const (
	SignallingCloseReasonUnknown SignallingCloseReason = iota
	SignallingCloseReasonMigration
	SignallingCloseReasonResume
	SignallingCloseReasonTransportFailure
	SignallingCloseReasonFullReconnectPublicationError
	SignallingCloseReasonFullReconnectSubscriptionError
	SignallingCloseReasonFullReconnectDataChannelError
	SignallingCloseReasonFullReconnectNegotiateFailed
	SignallingCloseReasonParticipantClose
	SignallingCloseReasonDisconnectOnResume
	SignallingCloseReasonDisconnectOnResumeNoMessages
)

func (s SignallingCloseReason) String() string {
	switch s {
	case SignallingCloseReasonUnknown:
		return "UNKNOWN"
	case SignallingCloseReasonMigration:
		return "MIGRATION"
	case SignallingCloseReasonResume:
		return "RESUME"
	case SignallingCloseReasonTransportFailure:
		return "TRANSPORT_FAILURE"
	case SignallingCloseReasonFullReconnectPublicationError:
		return "FULL_RECONNECT_PUBLICATION_ERROR"
	case SignallingCloseReasonFullReconnectSubscriptionError:
		return "FULL_RECONNECT_SUBSCRIPTION_ERROR"
	case SignallingCloseReasonFullReconnectDataChannelError:
		return "FULL_RECONNECT_DATA_CHANNEL_ERROR"
	case SignallingCloseReasonFullReconnectNegotiateFailed:
		return "FULL_RECONNECT_NEGOTIATE_FAILED"
	case SignallingCloseReasonParticipantClose:
		return "PARTICIPANT_CLOSE"
	case SignallingCloseReasonDisconnectOnResume:
		return "DISCONNECT_ON_RESUME"
	case SignallingCloseReasonDisconnectOnResumeNoMessages:
		return "DISCONNECT_ON_RESUME_NO_MESSAGES"
	default:
		return fmt.Sprintf("%d", int(s))
	}
}

// ---------------------------------------------
const (
	ParticipantCloseKeyNormal = "normal"
	ParticipantCloseKeyWHIP   = "whip"
)

// ---------------------------------------------

//counterfeiter:generate . Participant
type Participant interface {
	ID() livekit.ParticipantID
	Identity() livekit.ParticipantIdentity
	State() livekit.ParticipantInfo_State
	ConnectedAt() time.Time
	CloseReason() ParticipantCloseReason
	Kind() livekit.ParticipantInfo_Kind
	KindDetails() []livekit.ParticipantInfo_KindDetail
	IsRecorder() bool
	IsDependent() bool
	IsAgent() bool

	GetLogger() logger.Logger

	CanSkipBroadcast() bool
	Version() utils.TimedVersion
	ToProto() *livekit.ParticipantInfo
	ToProtoWithVersion() (*livekit.ParticipantInfo, utils.TimedVersion)

	IsPublisher() bool
	GetPublishedTrack(trackID livekit.TrackID) MediaTrack
	GetPublishedTracks() []MediaTrack
	RemovePublishedTrack(track MediaTrack, isExpectedToResume bool)

	GetPublishedDataTracks() []DataTrack
	GetPublishedDataTrack(handle uint16) DataTrack
	RemovePublishedDataTrack(track DataTrack)

	GetAudioLevel() (smoothedLevel float64, active bool)

	// HasPermission checks permission of the subscriber by identity. Returns true if subscriber is allowed to subscribe
	// to the track with trackID
	HasPermission(trackID livekit.TrackID, subIdentity livekit.ParticipantIdentity) bool

	// permissions
	Hidden() bool

	MigrateState() MigrateState

	Close(sendLeave bool, reason ParticipantCloseReason, isExpectedToResume bool) error
	IsClosed() bool
	IsDisconnected() bool

	SubscriptionPermission() (*livekit.SubscriptionPermission, utils.TimedVersion)

	// updates from remotes
	UpdateSubscriptionPermission(
		subscriptionPermission *livekit.SubscriptionPermission,
		timedVersion utils.TimedVersion,
		resolverBySid func(participantID livekit.ParticipantID) LocalParticipant,
	) error

	DebugInfo() map[string]any

	HandleReceivedDataTrackMessage([]byte, *datatrack.Packet, int64)

	GetParticipantListener() ParticipantListener

	AddDataBlob(dataBlob *livekit.DataBlob)
	GetDataBlob(key *livekit.DataBlobKey) *livekit.DataBlob
}

// -------------------------------------------------------

type AddTrackParams struct {
	Stereo bool
	Red    bool
}

type MoveToRoomParams struct {
	RoomName          livekit.RoomName
	ParticipantID     livekit.ParticipantID
	Listener          LocalParticipantListener
	TelemetryListener ParticipantTelemetryListener
	Helper            LocalParticipantHelper
}

type DataMessageCache struct {
	Data           []byte
	SenderID       livekit.ParticipantID
	Seq            uint32
	DestIdentities []livekit.ParticipantIdentity
}

//counterfeiter:generate . LocalParticipantHelper
type LocalParticipantHelper interface {
	ResolveMediaTrack(LocalParticipant, livekit.TrackID) MediaResolverResult
	ResolveDataTrack(LocalParticipant, livekit.TrackID) DataResolverResult
	GetParticipantInfo(pID livekit.ParticipantID) *livekit.ParticipantInfo
	GetRegionSettings(ip string) *livekit.RegionSettings
	GetSubscriberForwarderState(p LocalParticipant) (map[livekit.TrackID]*livekit.RTPForwarderState, error)
	ShouldRegressCodec() bool
	GetCachedReliableDataMessage(seqs map[livekit.ParticipantID]uint32) []*DataMessageCache
}

//counterfeiter:generate . LocalParticipant
type LocalParticipant interface {
	Participant

	TelemetryGuard() *telemetry.ReferenceGuard
	GetTelemetryListener() ParticipantTelemetryListener

	// getters
	GetCountry() string
	GetTrailer() []byte
	GetLoggerResolver() logger.DeferredFieldResolver
	GetReporter() roomobs.ParticipantSessionReporter
	GetReporterResolver() roomobs.ParticipantReporterResolver
	GetAdaptiveStream() bool
	GetEnableStartAtDesiredQuality() bool
	ProtocolVersion() ProtocolVersion
	SupportsSyncStreamID() bool
	SupportsTransceiverReuse(mt MediaTrack) bool
	IsUsingSinglePeerConnection() bool
	IsReady() bool
	ActiveAt() time.Time
	Disconnected() <-chan struct{}
	IsConnectionCanceled(closeReason ParticipantCloseReason) bool
	IsIdle() bool
	SubscriberAsPrimary() bool
	GetClientInfo() *livekit.ClientInfo
	GetClientConfiguration() *livekit.ClientConfiguration
	GetBufferFactory() *buffer.Factory
	GetPlayoutDelayConfig() *livekit.PlayoutDelay
	GetPendingTrack(trackID livekit.TrackID) *livekit.TrackInfo
	GetICEConnectionInfo() []*ICEConnectionInfo
	HasICEConnected() bool
	HasConnected() bool
	GetEnabledPublishCodecs() []*livekit.Codec
	GetPublisherICESessionUfrag() (string, error)
	SupportsMoving() error
	GetLastReliableSequence(migrateOut bool) uint32
	IsWarpEnabled() bool

	SwapResponseSink(sink routing.MessageSink, reason SignallingCloseReason)
	GetResponseSink() routing.MessageSink
	CloseSignalConnection(reason SignallingCloseReason)
	UpdateLastSeenSignal()
	SetSignalSourceValid(valid bool)
	HandleSignalSourceClose()

	// updates
	UpdateMetadata(update *livekit.UpdateParticipantMetadata, fromAdmin bool) error
	SetName(name string)
	SetMetadata(metadata string)
	SetAttributes(attributes map[string]string)
	UpdateAudioTrack(update *livekit.UpdateLocalAudioTrack) error
	UpdateVideoTrack(update *livekit.UpdateLocalVideoTrack) error

	// permissions
	ClaimGrants() *auth.ClaimGrants
	TokenExpiresAt() time.Time
	SetPermission(permission *livekit.ParticipantPermission) bool
	CanPublish() bool
	CanPublishSource(source livekit.TrackSource) bool
	CanSubscribe() bool
	CanPublishData() bool

	// PeerConnection
	HandleICETrickle(trickleRequest *livekit.TrickleRequest)
	HandleOffer(sd *livekit.SessionDescription) error
	GetAnswer() (webrtc.SessionDescription, uint32, error)
	HandleICETrickleSDPFragment(sdpFragment string) error
	HandleICERestartSDPFragment(sdpFragment string) (string, error)
	AddTrack(req *livekit.AddTrackRequest)
	SetTrackMuted(mute *livekit.MuteTrackRequest, fromAdmin bool) *livekit.TrackInfo

	HandleAnswer(sd *livekit.SessionDescription)
	Negotiate(force bool)
	ICERestart(iceConfig *livekit.ICEConfig)
	AddTrackLocal(trackLocal webrtc.TrackLocal, params AddTrackParams) (*webrtc.RTPSender, *webrtc.RTPTransceiver, error)
	AddTransceiverFromTrackLocal(trackLocal webrtc.TrackLocal, params AddTrackParams) (*webrtc.RTPSender, *webrtc.RTPTransceiver, error)
	RemoveTrackLocal(sender *webrtc.RTPSender) error

	WriteSubscriberRTCP(pkts []rtcp.Packet) error

	// subscriptions
	SubscribeToTrack(trackID livekit.TrackID, isSync bool)
	UnsubscribeFromTrack(trackID livekit.TrackID)
	UpdateSubscribedTrackSettings(trackID livekit.TrackID, settings *livekit.UpdateTrackSettings)
	GetSubscribedTracks() []SubscribedTrack
	IsTrackNameSubscribed(publisherIdentity livekit.ParticipantIdentity, trackName string) bool
	SubscribeToDataTrack(trackID livekit.TrackID)
	UnsubscribeFromDataTrack(trackID livekit.TrackID)
	UpdateDataTrackSubscriptionOptions(trackID livekit.TrackID, subscriptionOptions *livekit.DataTrackSubscriptionOptions)
	Verify() bool
	VerifySubscribeParticipantInfo(pID livekit.ParticipantID, version uint32)
	// WaitUntilSubscribed waits until all subscriptions have been settled, or if the timeout
	// has been reached. If the timeout expires, it will return an error.
	WaitUntilSubscribed(timeout time.Duration) error
	StopAndGetSubscribedTracksForwarderState() map[livekit.TrackID]*livekit.RTPForwarderState
	SupportsCodecChange() bool

	// returns list of participant identities that the current participant is subscribed to
	GetSubscribedParticipants() []livekit.ParticipantID
	IsSubscribedTo(sid livekit.ParticipantID) bool

	GetConnectionQuality() *livekit.ConnectionQualityInfo

	// server sent messages
	SendJoinResponse(joinResponse *livekit.JoinResponse) error
	SendParticipantUpdate(participants []*livekit.ParticipantInfo) error
	SendSpeakerUpdate(speakers []*livekit.SpeakerInfo, force bool) error
	SendDataMessage(kind livekit.DataPacket_Kind, data []byte, senderID livekit.ParticipantID, seq uint32) error
	SendDataMessageUnlabeled(data []byte, useRaw bool, sender livekit.ParticipantIdentity) error
	SendRoomUpdate(room *livekit.Room) error
	SendConnectionQualityUpdate(update *livekit.ConnectionQualityUpdate) error
	SendSubscriptionPermissionUpdate(publisherID livekit.ParticipantID, trackID livekit.TrackID, allowed bool) error
	SendRefreshToken(token string) error
	HandleReconnectAndSendResponse(reconnectReason livekit.ReconnectReason, reconnectResponse *livekit.ReconnectResponse) error
	IssueFullReconnect(reason ParticipantCloseReason)
	SendRoomMovedResponse(moved *livekit.RoomMovedResponse) error
	SendDataTrackSubscriberHandles(handles map[uint32]*livekit.DataTrackSubscriberHandles_PublishedDataTrack) error

	AddOnClose(key string, callback func(LocalParticipant))
	OnClaimsChanged(callback func(LocalParticipant))

	HandleReceiverReport(dt *sfu.DownTrack, report *rtcp.ReceiverReport)

	// session migration
	MaybeStartMigration(force bool, onStart func()) bool
	NotifyMigration()
	SetMigrateState(s MigrateState)
	SetMigrateInfo(
		previousOffer *webrtc.SessionDescription,
		previousAnswer *webrtc.SessionDescription,
		mediaTracks []*livekit.TrackPublishedResponse,
		dataChannels []*livekit.DataChannelInfo,
		dataChannelReceiveState []*livekit.DataChannelReceiveState,
		dataTracks []*livekit.PublishDataTrackResponse,
	)
	IsReconnect() bool
	IsMigration() bool
	MoveToRoom(params MoveToRoomParams)

	UpdateMediaRTT(rtt uint32)
	UpdateSignalingRTT(rtt uint32)

	CacheDownTrack(trackID livekit.TrackID, rtpTransceiver *webrtc.RTPTransceiver, downTrackState sfu.DownTrackState)
	UncacheDownTrack(rtpTransceiver *webrtc.RTPTransceiver)
	GetCachedDownTrack(trackID livekit.TrackID) (*webrtc.RTPTransceiver, sfu.DownTrackState)

	SetICEConfig(iceConfig *livekit.ICEConfig)
	GetICEConfig() *livekit.ICEConfig
	OnICEConfigChanged(callback func(participant LocalParticipant, iceConfig *livekit.ICEConfig))

	UpdateSubscribedQuality(nodeID livekit.NodeID, trackID livekit.TrackID, maxQualities []SubscribedCodecQuality) error
	UpdateSubscribedAudioCodecs(nodeID livekit.NodeID, trackID livekit.TrackID, codecs []*livekit.SubscribedAudioCodec) error
	UpdateMediaLoss(nodeID livekit.NodeID, trackID livekit.TrackID, fractionalLoss uint32) error

	// down stream bandwidth management
	SetSubscriberAllowPause(allowPause bool)
	SetSubscriberChannelCapacity(channelCapacity int64)

	GetPacer() pacer.Pacer

	GetDisableSenderReportPassThrough() bool

	HandleMetrics(senderParticipantID livekit.ParticipantID, batch *livekit.MetricsBatch) error
	HandleUpdateSubscriptions(
		[]livekit.TrackID,
		[]*livekit.ParticipantTracks,
		bool,
	)
	HandleUpdateSubscriptionPermission(*livekit.SubscriptionPermission) error
	HandleSyncState(*livekit.SyncState) error
	HandleSimulateScenario(*livekit.SimulateScenario) error
	HandleLeaveRequest(reason ParticipantCloseReason)

	HandlePublishDataTrackRequest(*livekit.PublishDataTrackRequest)
	HandleUnpublishDataTrackRequest(*livekit.UnpublishDataTrackRequest)
	HandleUpdateDataSubscription(*livekit.UpdateDataSubscription)
	HandleStoreDataBlobRequest(*livekit.StoreDataBlobRequest)
	HandleGetDataBlobRequest(*livekit.GetDataBlobRequest)
	ProcessGetDataBlobRequest(*livekit.GetDataBlobRequest, Participant)

	HandleSignalMessage(msg proto.Message) error

	PerformRpc(req *livekit.PerformRpcRequest, resultCh chan string, errorCh chan error)

	GetDataTrackTransport() DataTrackTransport

	ClearParticipantListener()

	GetNextSubscribedDataTrackHandle() uint16

	GetAllDataBlob() []*livekit.DataBlob
}

// ---------------------------------------------

//counterfeiter:generate . ParticipantListener
type ParticipantListener interface {
	OnParticipantUpdate(Participant)
	OnTrackPublished(Participant, MediaTrack)
	OnTrackUpdated(Participant, MediaTrack)
	OnTrackUnpublished(Participant, MediaTrack)
	OnDataTrackPublished(Participant, DataTrack)
	OnDataTrackUnpublished(Participant, DataTrack)
	OnDataTrackMessage(Participant, []byte, *datatrack.Packet)
	OnMetrics(Participant, *livekit.DataPacket)
}

var _ ParticipantListener = (*NullParticipantListener)(nil)

type NullParticipantListener struct{}

func (*NullParticipantListener) OnParticipantUpdate(Participant)                           {}
func (*NullParticipantListener) OnTrackPublished(Participant, MediaTrack)                  {}
func (*NullParticipantListener) OnTrackUpdated(Participant, MediaTrack)                    {}
func (*NullParticipantListener) OnTrackUnpublished(Participant, MediaTrack)                {}
func (*NullParticipantListener) OnDataTrackPublished(Participant, DataTrack)               {}
func (*NullParticipantListener) OnDataTrackUnpublished(Participant, DataTrack)             {}
func (*NullParticipantListener) OnDataTrackMessage(Participant, []byte, *datatrack.Packet) {}
func (*NullParticipantListener) OnMetrics(Participant, *livekit.DataPacket)                {}

// ---------------------------------------------

//counterfeiter:generate . LocalParticipantListener
type LocalParticipantListener interface {
	ParticipantListener

	OnStateChange(LocalParticipant)
	OnSubscriberReady(LocalParticipant)
	OnMigrateStateChange(LocalParticipant, MigrateState)
	OnDataMessage(LocalParticipant, livekit.DataPacket_Kind, *livekit.DataPacket)
	OnDataMessageUnlabeled(LocalParticipant, []byte)
	OnSubscribeStatusChanged(LocalParticipant, livekit.ParticipantID, bool)
	OnUpdateSubscriptions(
		LocalParticipant,
		[]livekit.TrackID,
		[]*livekit.ParticipantTracks,
		bool,
	)
	OnUpdateSubscriptionPermission(LocalParticipant, *livekit.SubscriptionPermission) error
	OnUpdateDataSubscriptions(LocalParticipant, *livekit.UpdateDataSubscription)
	OnStoreDataBlob(LocalParticipant, *livekit.DataBlob)
	OnGetDataBlob(LocalParticipant, *livekit.GetDataBlobRequest)
	OnSyncState(LocalParticipant, *livekit.SyncState) error
	OnSimulateScenario(LocalParticipant, *livekit.SimulateScenario) error
	OnLeave(LocalParticipant, ParticipantCloseReason)
}

var _ LocalParticipantListener = (*NullLocalParticipantListener)(nil)

type NullLocalParticipantListener struct {
	NullParticipantListener
}

func (*NullLocalParticipantListener) OnStateChange(LocalParticipant)                      {}
func (*NullLocalParticipantListener) OnSubscriberReady(LocalParticipant)                  {}
func (*NullLocalParticipantListener) OnMigrateStateChange(LocalParticipant, MigrateState) {}
func (*NullLocalParticipantListener) OnDataMessage(LocalParticipant, livekit.DataPacket_Kind, *livekit.DataPacket) {
}
func (*NullLocalParticipantListener) OnDataMessageUnlabeled(LocalParticipant, []byte) {}
func (*NullLocalParticipantListener) OnSubscribeStatusChanged(LocalParticipant, livekit.ParticipantID, bool) {
}
func (*NullLocalParticipantListener) OnUpdateSubscriptions(
	LocalParticipant,
	[]livekit.TrackID,
	[]*livekit.ParticipantTracks,
	bool,
) {
}
func (*NullLocalParticipantListener) OnUpdateSubscriptionPermission(LocalParticipant, *livekit.SubscriptionPermission) error {
	return nil
}
func (*NullLocalParticipantListener) OnUpdateDataSubscriptions(LocalParticipant, *livekit.UpdateDataSubscription) {
}
func (*NullLocalParticipantListener) OnStoreDataBlob(LocalParticipant, *livekit.DataBlob) {
}
func (*NullLocalParticipantListener) OnGetDataBlob(LocalParticipant, *livekit.GetDataBlobRequest) {
}
func (*NullLocalParticipantListener) OnSyncState(LocalParticipant, *livekit.SyncState) error {
	return nil
}
func (*NullLocalParticipantListener) OnSimulateScenario(LocalParticipant, *livekit.SimulateScenario) error {
	return nil
}
func (*NullLocalParticipantListener) OnLeave(LocalParticipant, ParticipantCloseReason) {}

// ---------------------------------------------

//counterfeiter:generate . ParticipantTelemetryListener
type ParticipantTelemetryListener interface {
	OnTrackPublishRequested(pID livekit.ParticipantID, identity livekit.ParticipantIdentity, ti *livekit.TrackInfo, shouldSendEvent bool)
	OnTrackPublished(pID livekit.ParticipantID, identity livekit.ParticipantIdentity, ti *livekit.TrackInfo, shouldSendEvent bool)
	OnTrackUnpublished(pID livekit.ParticipantID, identity livekit.ParticipantIdentity, ti *livekit.TrackInfo, wasPublishedLocally bool, shouldSendEvent bool)
	OnTrackSubscribeRequested(pID livekit.ParticipantID, ti *livekit.TrackInfo)
	OnTrackSubscribed(pID livekit.ParticipantID, ti *livekit.TrackInfo, publisherInfo *livekit.ParticipantInfo, shouldSendEvent bool)
	OnTrackUnsubscribed(pID livekit.ParticipantID, ti *livekit.TrackInfo, shouldSendEvent bool)
	OnTrackSubscribeFailed(pID livekit.ParticipantID, trackID livekit.TrackID, err error, isUserError bool)
	OnTrackSubscribeStreamStarted(pID livekit.ParticipantID, ti *livekit.TrackInfo, elapsed time.Duration)
	OnTrackMuted(pID livekit.ParticipantID, ti *livekit.TrackInfo)
	OnTrackUnmuted(pID livekit.ParticipantID, ti *livekit.TrackInfo)
	OnTrackPublishedUpdate(pID livekit.ParticipantID, ti *livekit.TrackInfo)
	OnTrackMaxSubscribedVideoQuality(pID livekit.ParticipantID, ti *livekit.TrackInfo, mime mime.MimeType, maxQuality livekit.VideoQuality)
	OnTrackPublishRTPStats(pID livekit.ParticipantID, trackID livekit.TrackID, mimeType mime.MimeType, layer int, stats *livekit.RTPStats)
	OnTrackSubscribeRTPStats(pID livekit.ParticipantID, trackID livekit.TrackID, mimeType mime.MimeType, stats *livekit.RTPStats)

	OnTrackStats(key telemetry.StatsKey, stat *livekit.AnalyticsStat)
}

var _ ParticipantTelemetryListener = (*NullParticipantTelemetryListener)(nil)

type NullParticipantTelemetryListener struct{}

func (NullParticipantTelemetryListener) OnTrackPublishRequested(pID livekit.ParticipantID, identity livekit.ParticipantIdentity, ti *livekit.TrackInfo, shouldSendEvent bool) {
}
func (NullParticipantTelemetryListener) OnTrackPublished(pID livekit.ParticipantID, identity livekit.ParticipantIdentity, ti *livekit.TrackInfo, shouldSendEvent bool) {
}
func (NullParticipantTelemetryListener) OnTrackUnpublished(pID livekit.ParticipantID, identity livekit.ParticipantIdentity, ti *livekit.TrackInfo, wasPublishedLocally bool, shouldSendEvent bool) {
}
func (NullParticipantTelemetryListener) OnTrackSubscribeRequested(pID livekit.ParticipantID, ti *livekit.TrackInfo) {
}
func (NullParticipantTelemetryListener) OnTrackSubscribed(pID livekit.ParticipantID, ti *livekit.TrackInfo, publisherInfo *livekit.ParticipantInfo, shouldSendEvent bool) {
}
func (NullParticipantTelemetryListener) OnTrackUnsubscribed(pID livekit.ParticipantID, ti *livekit.TrackInfo, shouldSendEvent bool) {
}
func (NullParticipantTelemetryListener) OnTrackSubscribeFailed(pID livekit.ParticipantID, trackID livekit.TrackID, err error, isUserError bool) {
}
func (NullParticipantTelemetryListener) OnTrackSubscribeStreamStarted(pID livekit.ParticipantID, ti *livekit.TrackInfo, elapsed time.Duration) {
}
func (NullParticipantTelemetryListener) OnTrackMuted(pID livekit.ParticipantID, ti *livekit.TrackInfo) {
}
func (NullParticipantTelemetryListener) OnTrackUnmuted(pID livekit.ParticipantID, ti *livekit.TrackInfo) {
}
func (NullParticipantTelemetryListener) OnTrackPublishedUpdate(pID livekit.ParticipantID, ti *livekit.TrackInfo) {
}
func (NullParticipantTelemetryListener) OnTrackMaxSubscribedVideoQuality(pID livekit.ParticipantID, ti *livekit.TrackInfo, mime mime.MimeType, maxQuality livekit.VideoQuality) {
}
func (NullParticipantTelemetryListener) OnTrackPublishRTPStats(pID livekit.ParticipantID, trackID livekit.TrackID, mimeType mime.MimeType, layer int, stats *livekit.RTPStats) {
}
func (NullParticipantTelemetryListener) OnTrackSubscribeRTPStats(pID livekit.ParticipantID, trackID livekit.TrackID, mimeType mime.MimeType, stats *livekit.RTPStats) {
}

func (NullParticipantTelemetryListener) OnTrackStats(key telemetry.StatsKey, stat *livekit.AnalyticsStat) {
}

// ---------------------------------------------

// Room is a container of participants, and can provide room-level actions
//
//counterfeiter:generate . Room
type Room interface {
	Name() livekit.RoomName
	ID() livekit.RoomID
	RemoveParticipant(identity livekit.ParticipantIdentity, pID livekit.ParticipantID, reason ParticipantCloseReason)
	UpdateSubscriptions(
		participant LocalParticipant,
		trackIDs []livekit.TrackID,
		participantTracks []*livekit.ParticipantTracks,
		subscribe bool,
	)
	ResolveMediaTrackForSubscriber(sub LocalParticipant, trackID livekit.TrackID) MediaResolverResult
	ResolveDataTrackForSubscriber(sub LocalParticipant, trackID livekit.TrackID) DataResolverResult
	GetLocalParticipants() []LocalParticipant
	IsDataMessageUserPacketDuplicate(ip *livekit.UserPacket) bool
}

// MediaTrack represents a media track
//
//counterfeiter:generate . MediaTrack
type MediaTrack interface {
	ID() livekit.TrackID
	Kind() livekit.TrackType
	Name() string
	Source() livekit.TrackSource
	Stream() string

	UpdateTrackInfo(ti *livekit.TrackInfo)
	UpdateAudioTrack(update *livekit.UpdateLocalAudioTrack)
	UpdateVideoTrack(update *livekit.UpdateLocalVideoTrack)
	ToProto() *livekit.TrackInfo

	PublisherID() livekit.ParticipantID
	PublisherIdentity() livekit.ParticipantIdentity
	PublisherVersion() uint32
	Logger() logger.Logger

	IsMuted() bool
	SetMuted(muted bool)

	GetAudioLevel() (level float64, active bool)

	Close(isExpectedToResume bool)
	IsOpen() bool

	// callbacks
	AddOnClose(func(isExpectedToResume bool))

	// subscribers
	AddSubscriber(participant LocalParticipant) (SubscribedTrack, error)
	RemoveSubscriber(participantID livekit.ParticipantID, isExpectedToResume bool)
	IsSubscriber(subID livekit.ParticipantID) bool
	RevokeDisallowedSubscribers(allowedSubscriberIdentities []livekit.ParticipantIdentity) []livekit.ParticipantIdentity
	GetAllSubscribers() []livekit.ParticipantID
	GetNumSubscribers() int
	OnTrackSubscribed()

	// returns quality information that's appropriate for width & height
	GetQualityForDimension(mimeType mime.MimeType, width, height uint32) livekit.VideoQuality

	// returns temporal layer that's appropriate for fps
	GetTemporalLayerForSpatialFps(mimeType mime.MimeType, spatial int32, fps uint32) int32

	Receivers() []sfu.TrackReceiver
	ClearAllReceivers(isExpectedToResume bool)

	IsEncrypted() bool
	HasPacketTrailer() bool
}

//counterfeiter:generate . LocalMediaTrack
type LocalMediaTrack interface {
	MediaTrack

	Restart()

	HasSignalCid(cid string) bool
	HasSdpCid(cid string) bool

	SetMigrated(bool)
	Migrated() bool
	SetPublished(bool)
	Published() bool

	GetConnectionScoreAndQuality() (float32, livekit.ConnectionQuality)
	GetTrackStats() *livekit.RTPStats

	SetRTT(rtt uint32)

	NotifySubscriberNodeMaxQuality(nodeID livekit.NodeID, qualities []SubscribedCodecQuality)
	NotifySubscriptionNode(nodeID livekit.NodeID, codecs []*livekit.SubscribedAudioCodec)
	ClearSubscriberNodes()
	NotifySubscriberNodeMediaLoss(nodeID livekit.NodeID, fractionalLoss uint8)
}

// DataTrack represents a data track
//
//counterfeiter:generate . DataTrack
type DataTrack interface {
	ID() livekit.TrackID
	PubHandle() uint16
	Name() string
	ToProto() *livekit.DataTrackInfo

	PublisherID() livekit.ParticipantID
	PublisherIdentity() livekit.ParticipantIdentity

	AddSubscriber(sub LocalParticipant) (DataDownTrack, error)
	RemoveSubscriber(participantID livekit.ParticipantID)
	IsSubscriber(subID livekit.ParticipantID) bool
	RevokeDisallowedSubscribers(allowedSubscriberIdentities []livekit.ParticipantIdentity) []livekit.ParticipantIdentity

	AddDataDownTrack(sender DataTrackSender) error
	DeleteDataDownTrack(subscriberID livekit.ParticipantID)

	HandlePacket(data []byte, packet *datatrack.Packet, arrivalTime int64)

	Close()
}

//counterfeiter:generate . DataDownTrack
type DataDownTrack interface {
	Close()
	OnClose(fn func())

	Handle() uint16
	PublishDataTrack() DataTrack

	UpdateSubscriptionOptions(subscriptionOptions *livekit.DataTrackSubscriptionOptions)
}

//counterfeiter:generate . DataTrackSender
type DataTrackSender interface {
	SubscriberID() livekit.ParticipantID

	WritePacket(data []byte, packet *datatrack.Packet, arrivalTime int64)
}

//counterfeiter:generate . DataTrackTransport
type DataTrackTransport interface {
	SendDataTrackMessage(data []byte) error
}

//counterfeiter:generate . SubscribedTrack
type SubscribedTrack interface {
	AddOnBind(f func(error))
	IsBound() bool
	OnSubscribeStreamStarted(f func(elapsed time.Duration))
	Close(isExpectedToResume bool)
	OnClose(f func(isExpectedToResume bool))
	ID() livekit.TrackID
	PublisherID() livekit.ParticipantID
	PublisherIdentity() livekit.ParticipantIdentity
	PublisherVersion() uint32
	SubscriberID() livekit.ParticipantID
	SubscriberIdentity() livekit.ParticipantIdentity
	Subscriber() LocalParticipant
	DownTrack() *sfu.DownTrack
	MediaTrack() MediaTrack
	RTPSender() *webrtc.RTPSender
	IsMuted() bool
	SetPublisherMuted(muted bool)
	UpdateSubscriberSettings(settings *livekit.UpdateTrackSettings, isImmediate bool)
	// selects appropriate video layer according to subscriber preferences
	UpdateVideoLayer()
	NeedsNegotiation() bool
}

type ChangeNotifier interface {
	AddObserver(key string, onChanged func())
	RemoveObserver(key string)
	HasObservers() bool
	NotifyChanged()
}

type MediaResolverResult struct {
	TrackChangedNotifier ChangeNotifier
	TrackRemovedNotifier ChangeNotifier
	Track                MediaTrack
	// is permission given to the requesting participant
	HasPermission     bool
	PublisherID       livekit.ParticipantID
	PublisherIdentity livekit.ParticipantIdentity
}

type DataResolverResult struct {
	TrackChangedNotifier ChangeNotifier
	TrackRemovedNotifier ChangeNotifier
	DataTrack            DataTrack
	// is permission given to the requesting participant
	HasPermission     bool
	PublisherID       livekit.ParticipantID
	PublisherIdentity livekit.ParticipantIdentity
}

// MediaTrackResolver locates a specific media track for a subscriber
type MediaTrackResolver func(LocalParticipant, livekit.TrackID) MediaResolverResult

// DataTrackResolver locates a specific data track for a subscriber
type DataTrackResolver func(LocalParticipant, livekit.TrackID) DataResolverResult

// Supervisor/operation monitor related definitions
type OperationMonitorEvent int

const (
	OperationMonitorEventPublisherPeerConnectionConnected OperationMonitorEvent = iota
	OperationMonitorEventAddPendingPublication
	OperationMonitorEventSetPublicationMute
	OperationMonitorEventSetPublishedTrack
	OperationMonitorEventClearPublishedTrack
)

func (o OperationMonitorEvent) String() string {
	switch o {
	case OperationMonitorEventPublisherPeerConnectionConnected:
		return "PUBLISHER_PEER_CONNECTION_CONNECTED"
	case OperationMonitorEventAddPendingPublication:
		return "ADD_PENDING_PUBLICATION"
	case OperationMonitorEventSetPublicationMute:
		return "SET_PUBLICATION_MUTE"
	case OperationMonitorEventSetPublishedTrack:
		return "SET_PUBLISHED_TRACK"
	case OperationMonitorEventClearPublishedTrack:
		return "CLEAR_PUBLISHED_TRACK"
	default:
		return fmt.Sprintf("%d", int(o))
	}
}

type OperationMonitorData any

type OperationMonitor interface {
	PostEvent(ome OperationMonitorEvent, omd OperationMonitorData)
	Check() error
	IsIdle() bool
}
