// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"fmt"
	"strings"
)

// A CompoundPacket is a collection of RTCP packets transmitted as a single packet with
// the underlying protocol (for example UDP).
//
// To maximize the resolution of receiption statistics, the first Packet in a CompoundPacket
// must always be either a SenderReport or a ReceiverReport.  This is true even if no data
// has been sent or received, in which case an empty ReceiverReport must be sent, and even
// if the only other RTCP packet in the compound packet is a Goodbye.
//
// Next, a SourceDescription containing a CNAME item must be included in each CompoundPacket
// to identify the source and to begin associating media for purposes such as lip-sync.
//
// Other RTCP packet types may follow in any order. Packet types may appear more than once.
type CompoundPacket []Packet

// Validate returns an error if this is not an RFC-compliant CompoundPacket.
//
//nolint:cyclop
func (c CompoundPacket) Validate() error {
	if len(c) == 0 {
		return errEmptyCompound
	}

	// SenderReport and ReceiverReport are the only types that
	// are allowed to be the first packet in a compound datagram
	switch c[0].(type) {
	case *SenderReport, *ReceiverReport:
		// ok
	default:
		return errBadFirstPacket
	}

	for _, pkt := range c[1:] {
		switch p := pkt.(type) {
		// If the number of RecetpionReports exceeds 31 additional ReceiverReports
		// can be included here.
		case *ReceiverReport:
			continue

		// A SourceDescription containing a CNAME must be included in every
		// CompoundPacket.
		case *SourceDescription:
			var hasCNAME bool
			for _, c := range p.Chunks {
				for _, it := range c.Items {
					if it.Type == SDESCNAME {
						hasCNAME = true
					}
				}
			}

			if !hasCNAME {
				return errMissingCNAME
			}

			return nil

		// Other packets are not permitted before the CNAME
		default:
			return errPacketBeforeCNAME
		}
	}

	// CNAME never reached
	return errMissingCNAME
}

// CNAME returns the CNAME that *must* be present in every CompoundPacket.
func (c CompoundPacket) CNAME() (string, error) {
	var err error

	if len(c) < 1 {
		return "", errEmptyCompound
	}

	for _, pkt := range c[1:] {
		sdes, ok := pkt.(*SourceDescription)
		if ok {
			for _, c := range sdes.Chunks {
				for _, it := range c.Items {
					if it.Type == SDESCNAME {
						return it.Text, err
					}
				}
			}
		} else {
			_, ok := pkt.(*ReceiverReport)
			if !ok {
				err = errPacketBeforeCNAME
			}
		}
	}

	return "", errMissingCNAME
}

// Marshal encodes the CompoundPacket as binary.
func (c CompoundPacket) Marshal() ([]byte, error) {
	if err := c.Validate(); err != nil {
		return nil, err
	}

	p := []Packet(c)

	return Marshal(p)
}

// MarshalSize returns the size of the packet once marshaled.
func (c CompoundPacket) MarshalSize() int {
	l := 0
	for _, p := range c {
		l += p.MarshalSize()
	}

	return l
}

// Unmarshal decodes a CompoundPacket from binary.
func (c *CompoundPacket) Unmarshal(rawData []byte) error {
	out := make(CompoundPacket, 0)
	for len(rawData) != 0 {
		p, processed, err := unmarshal(rawData)
		if err != nil {
			return err
		}

		out = append(out, p)
		rawData = rawData[processed:]
	}
	*c = out

	return c.Validate()
}

// DestinationSSRC returns the synchronization sources associated with this
// CompoundPacket's reception report.
func (c CompoundPacket) DestinationSSRC() []uint32 {
	if len(c) == 0 {
		return nil
	}

	return c[0].DestinationSSRC()
}

func (c CompoundPacket) String() string {
	out := "CompoundPacket\n"
	for _, p := range c {
		stringer, canString := p.(fmt.Stringer)
		if canString {
			out += stringer.String()
		} else {
			out += stringify(p)
		}
	}
	out = strings.TrimSuffix(strings.ReplaceAll(out, "\n", "\n\t"), "\t")

	return out
}
