package dns

import (
	
	
	
	
	

	
	
	
)

// Errors returned when parsing iroh DNS records.
var (
	// ErrInvalidTXTAttr is returned for a TXT value not of the form key=value.
	ErrInvalidTXTAttr = errors.New("invalid TXT attribute")
	// ErrUnknownAttr is returned for an unrecognized attribute key.
	ErrUnknownAttr = errors.New("could not convert key to attr")
	// ErrNumLabels is returned when a DNS name has too few labels.
	ErrNumLabels = errors.New("expected at least 2 labels")
	// ErrNotIrohRecord is returned when the first label is not "_iroh".
	ErrNotIrohRecord = errors.New("not an iroh record, expected `_iroh`")
)

// irohAttr is an attribute key for iroh TXT records. Keys are kebab-case.
type irohAttr string

const (
	attrRelay    irohAttr = "relay"
	attrAddr     irohAttr = "addr"
	attrUserData irohAttr = "user-data"
)

func parseIrohAttr( string) (irohAttr, bool) {
	switch irohAttr() {
	case attrRelay, attrAddr, attrUserData:
		return irohAttr(), true
	default:
		return "", false
	}
}

// txtAttrs is the set of attributes parsed from "_iroh" TXT records: an endpoint
// id plus a map from attribute key to its ordered values.
type txtAttrs struct {
	endpointID key.EndpointID
	attrs      map[irohAttr][]string
}

// endpointIDFromTxtName parses an EndpointID from an iroh DNS name. The first
// label must be "_iroh" and the second the z-base-32 endpoint id; later labels
// are ignored.
func endpointIDFromTxtName( string) (key.EndpointID, error) {
	 := strings.Split(, ".")
	if len() < 2 {
		return key.EndpointID{}, fmt.Errorf("%w, received %d", ErrNumLabels, len())
	}
	if [0] != IrohTXTName {
		return key.EndpointID{}, fmt.Errorf("%w, got %q", ErrNotIrohRecord, [0])
	}
	return key.ParseEndpointIDZ32([1])
}

// txtAttrsFromStrings builds txtAttrs from an endpoint id and "key=value"
// strings, preserving per-key value order.
func txtAttrsFromStrings( key.EndpointID,  []string) (*txtAttrs, error) {
	 := map[irohAttr][]string{}
	for ,  := range  {
		, ,  := splitAttr()
		if ! {
			return nil, fmt.Errorf("%w: expected key=value, received %q", ErrInvalidTXTAttr, )
		}
		,  := parseIrohAttr()
		if ! {
			return nil, fmt.Errorf("%w: %q", ErrUnknownAttr, )
		}
		[] = append([], )
	}
	return &txtAttrs{endpointID: , attrs: }, nil
}

func splitAttr( string) (,  string,  bool) {
	 := strings.SplitN(, "=", 3)
	if len() < 2 {
		return "", "", false
	}
	return [0], [1], true
}

func txtAttrsFromTXTLookup( string,  []string) (*txtAttrs, error) {
	,  := endpointIDFromTxtName()
	if  != nil {
		return nil, 
	}
	return txtAttrsFromStrings(, )
}

func txtAttrsFromPkarrSignedPacket( *SignedPacket) (*txtAttrs, error) {
	 := .PublicKey().EndpointID()
	return txtAttrsFromStrings(, .TXTRecords(IrohTXTName))
}

// attrOrder is the key emission order. It matches the Rust BTreeMap<IrohAttr>
// iteration, which follows the derived Ord on the IrohAttr enum (declaration
// order: Relay, Addr, UserData) — not lexical key order.
var attrOrder = []irohAttr{attrRelay, attrAddr, attrUserData}

// toTxtStrings renders the attributes as "key=value" strings in the reference's
// BTreeMap order (relay, addr, user-data).
func ( *txtAttrs) () []string {
	var  []string
	for ,  := range attrOrder {
		for ,  := range .attrs[] {
			 = append(, string()+"="+)
		}
	}
	return 
}

func ( *txtAttrs) ( key.SecretKey,  uint32) (*pkarr.SignedPacket, error) {
	return pkarr.FromTxtStrings(, IrohTXTName, .toTxtStrings(), )
}

// toAttrs converts an EndpointInfo into txtAttrs, preserving address order.
func ( EndpointInfo) () *txtAttrs {
	 := map[irohAttr][]string{}
	for ,  := range .Data.addrs {
		switch v := .(type) {
		case netaddr.RelayAddr:
			[attrRelay] = append([attrRelay], .URL.String())
		case netaddr.IPAddr:
			[attrAddr] = append([attrAddr], .Addr.String())
		case netaddr.CustomAddr:
			[attrAddr] = append([attrAddr], .String())
		}
	}
	if .Data.userData != nil {
		[attrUserData] = append([attrUserData], .Data.userData.String())
	}
	return &txtAttrs{endpointID: .ID, attrs: }
}

// endpointInfoFromAttrs converts parsed txtAttrs back into an EndpointInfo. It
// mirrors endpoint_info_from_attrs: relay URLs first, then addr values parsed as
// IP-then-custom, with unparseable values skipped; the first user-data wins.
func endpointInfoFromAttrs( *txtAttrs) EndpointInfo {
	var  []netaddr.TransportAddr
	for ,  := range .attrs[attrRelay] {
		if ,  := url.Parse();  == nil {
			 = append(, netaddr.RelayAddr{URL: netaddr.RelayURLFromURL()})
		}
	}
	for ,  := range .attrs[attrAddr] {
		if ,  := netip.ParseAddrPort();  == nil {
			 = append(, netaddr.IPAddr{Addr: })
		} else if ,  := netaddr.ParseCustomAddr();  == nil {
			 = append(, )
		}
	}
	 := EndpointData{}
	if  := .attrs[attrUserData]; len() > 0 {
		if ,  := NewUserData([0]);  == nil {
			.SetUserData(&)
		}
	}
	.AddAddrs(...)
	return EndpointInfo{ID: .endpointID, Data: }
}