package dns

import (
	
	
	
	

	
	
)

// IrohTXTName is the DNS record name under which iroh TXT records are published.
const IrohTXTName = "_iroh"

// UserDataMaxLength is the maximum byte length of [UserData]. A DNS TXT
// character-string holds at most 255 bytes; subtracting the "user-data=" prefix
// leaves 245 bytes.
const UserDataMaxLength = 245

// ErrUserDataTooLong is returned when a [UserData] value exceeds
// [UserDataMaxLength].
var ErrUserDataTooLong = errors.New("user data: max length exceeded")

// UserData is application-defined data published and resolved through endpoint
// discovery. It is a UTF-8 string of at most [UserDataMaxLength] bytes. iroh
// neither inspects nor uses it.
type UserData struct {
	s string
}

// NewUserData returns a UserData wrapping s, or [ErrUserDataTooLong] if s
// exceeds [UserDataMaxLength] bytes.
func ( string) (UserData, error) {
	if len() > UserDataMaxLength {
		return UserData{}, ErrUserDataTooLong
	}
	return UserData{s: }, nil
}

// String returns the user data string.
func ( UserData) () string { return .s }

// MarshalText implements encoding.TextMarshaler.
func ( UserData) () ([]byte, error) {
	return []byte(.s), nil
}

// UnmarshalText implements encoding.TextUnmarshaler.
func ( *UserData) ( []byte) error {
	,  := NewUserData(string())
	if  != nil {
		return 
	}
	* = 
	return nil
}

// EndpointData is the addressing and metadata published about an endpoint: a set
// of transport addresses (in priority order) and optional [UserData]. It does
// not include the endpoint's id; see [EndpointInfo].
//
// The zero value is an empty EndpointData and is ready to use.
type EndpointData struct {
	// addrs is the ordered, de-duplicated set of transport addresses. Order is
	// preserved (it encodes priority) while duplicates are removed.
	addrs    []netaddr.TransportAddr
	userData *UserData
}

// NewEndpointData returns an EndpointData with the given addresses. Order is
// preserved; duplicates are removed.
func ( ...netaddr.TransportAddr) EndpointData {
	 := EndpointData{}
	.AddAddrs(...)
	return 
}

// WithRelayURL returns a copy of d with u added to the end of the address list,
// unless already present.
func ( EndpointData) ( netaddr.RelayURL) EndpointData {
	return .WithAddrs(netaddr.RelayAddr{URL: })
}

// AddRelayURL adds a relay URL to the end of the address list, unless already
// present.
func ( *EndpointData) ( netaddr.RelayURL) {
	.AddAddrs(netaddr.RelayAddr{URL: })
}

// WithIPAddrs returns a copy of d with addrs added in order, skipping
// duplicates and existing ones.
func ( EndpointData) ( ...netip.AddrPort) EndpointData {
	 = .clone()
	.AddIPAddrs(...)
	return 
}

// AddIPAddrs adds IP addresses in order, skipping duplicates and existing ones.
func ( *EndpointData) ( ...netip.AddrPort) {
	 := make([]netaddr.TransportAddr, len())
	for ,  := range  {
		[] = netaddr.IPAddr{Addr: }
	}
	.AddAddrs(...)
}

// WithAddrs returns a copy of d with addrs added in order, skipping duplicates
// and existing ones.
func ( EndpointData) ( ...netaddr.TransportAddr) EndpointData {
	 = .clone()
	.AddAddrs(...)
	return 
}

// AddAddrs adds addresses in order, skipping duplicates and ones already
// present. Duplicate filtering preserves the existing order.
func ( *EndpointData) ( ...netaddr.TransportAddr) {
	for ,  := range  {
		if !.contains() {
			.addrs = append(.addrs, )
		}
	}
}

// WithUserData returns a copy of d with the user data set or cleared.
func ( EndpointData) ( *UserData) EndpointData {
	 = .clone()
	.userData = 
	return 
}

// SetUserData sets or clears the user data.
func ( *EndpointData) ( *UserData) { .userData =  }

// WithoutIPAddrs returns a copy of d with all direct IP addresses removed.
func ( EndpointData) () EndpointData {
	 = .clone()
	.ClearIPAddrs()
	return 
}

// ClearIPAddrs removes all direct IP addresses.
func ( *EndpointData) () {
	.addrs = slices.DeleteFunc(.addrs, func( netaddr.TransportAddr) bool {
		,  := .(netaddr.IPAddr)
		return 
	})
}

// WithoutRelayURLs returns a copy of d with all relay addresses removed.
func ( EndpointData) () EndpointData {
	 = .clone()
	.ClearRelayURLs()
	return 
}

// ClearRelayURLs removes all relay addresses.
func ( *EndpointData) () {
	.addrs = slices.DeleteFunc(.addrs, func( netaddr.TransportAddr) bool {
		,  := .(netaddr.RelayAddr)
		return 
	})
}

// Addrs returns the ordered transport addresses.
func ( EndpointData) () []netaddr.TransportAddr { return slices.Clone(.addrs) }

// RelayURLs returns the relay URLs in order.
func ( EndpointData) () []netaddr.RelayURL {
	var  []netaddr.RelayURL
	for ,  := range .addrs {
		if ,  := .(netaddr.RelayAddr);  {
			 = append(, .URL)
		}
	}
	return 
}

// IPAddrs returns the direct IP addresses in order.
func ( EndpointData) () []netip.AddrPort {
	var  []netip.AddrPort
	for ,  := range .addrs {
		if ,  := .(netaddr.IPAddr);  {
			 = append(, .Addr)
		}
	}
	return 
}

// UserData returns the user data, or nil if unset.
func ( EndpointData) () *UserData { return .userData }

// HasAddrs reports whether any addresses are present.
func ( EndpointData) () bool { return len(.addrs) > 0 }

// String returns a diagnostic string for d.
func ( EndpointData) () string {
	var  strings.Builder
	.WriteString("EndpointData{addrs:[")
	for ,  := range .addrs {
		if  > 0 {
			.WriteString(", ")
		}
		.WriteString(.String())
	}
	.WriteString("]")
	if .userData != nil {
		.WriteString(", user-data:")
		.WriteString(.userData.String())
	}
	.WriteString("}")
	return .String()
}

func ( EndpointData) ( netaddr.TransportAddr) bool {
	return slices.ContainsFunc(.addrs, func( netaddr.TransportAddr) bool {
		return .Compare() == 0
	})
}

func ( EndpointData) () EndpointData {
	.addrs = slices.Clone(.addrs)
	return 
}

// EndpointDataFromAddr builds EndpointData from an [netaddr.EndpointAddr], taking
// its (already de-duplicated, sorted) addresses.
func ( netaddr.EndpointAddr) EndpointData {
	return EndpointData{addrs: .Addrs()}
}

// EndpointInfo couples an [key.EndpointID] with the [EndpointData] published
// about it.
type EndpointInfo struct {
	// ID is the endpoint this information is about.
	ID key.EndpointID
	// Data is the addressing and metadata.
	Data EndpointData
}

// String returns a diagnostic string for e.
func ( EndpointInfo) () string {
	return "EndpointInfo{id:" + .ID.String() + ", data:" + .Data.String() + "}"
}

// EndpointInfoFromAddr converts an [netaddr.EndpointAddr] into an EndpointInfo.
func ( netaddr.EndpointAddr) EndpointInfo {
	return EndpointInfo{ID: .ID, Data: EndpointDataFromAddr()}
}

// Addr converts the info into an [netaddr.EndpointAddr].
func ( EndpointInfo) () netaddr.EndpointAddr {
	return netaddr.NewEndpointAddr(.ID, .Data.addrs...)
}

// ToTXTStrings renders the endpoint info as "key=value" TXT record strings.
func ( EndpointInfo) () []string {
	return .toAttrs().toTxtStrings()
}

// ToSignedPacket builds a signed packet for this endpoint info, signed with
// secretKey and using the given record TTL in seconds.
func ( EndpointInfo) ( key.SecretKey,  uint32) (*SignedPacket, error) {
	,  := .toAttrs().toPkarrSignedPacket(, )
	if  != nil {
		return nil, 
	}
	return signedPacketFromInternal(), nil
}

// EndpointInfoFromTXTLookup parses an EndpointInfo from DNS TXT lookup results.
// domainName is the queried name ("_iroh.<z32>.<origin>") and values are the
// TXT record string values.
func ( string,  []string) (EndpointInfo, error) {
	,  := txtAttrsFromTXTLookup(, )
	if  != nil {
		return EndpointInfo{}, 
	}
	return endpointInfoFromAttrs(), nil
}

// EndpointInfoFromSignedPacket parses an EndpointInfo from a signed packet.
func ( *SignedPacket) (EndpointInfo, error) {
	,  := txtAttrsFromPkarrSignedPacket()
	if  != nil {
		return EndpointInfo{}, 
	}
	return endpointInfoFromAttrs(), nil
}