package agent

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"strings"
)

const maxAgentCollectionCursorLength = 512

// encodeAgentCollectionCursor serializes a private, opaque Agent Runtime
// collection cursor. The owning handler defines the cursor fields and binds
// them to its stable ordering/filter semantics.
func encodeAgentCollectionCursor(value any) (string, error) {
	payload, err := json.Marshal(value)
	if err != nil {
		return "", fmt.Errorf("encode collection cursor: %w", err)
	}
	return base64.RawURLEncoding.EncodeToString(payload), nil
}

// decodeAgentCollectionCursor decodes one strict opaque Agent Runtime cursor.
// Cursors are deliberately canonical: malformed base64, non-canonical
// encodings, unknown JSON fields, trailing JSON, and oversized values all fail
// instead of silently restarting from the first page.
func decodeAgentCollectionCursor(raw string, label string, target any) error {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return nil
	}
	if len(trimmed) > maxAgentCollectionCursorLength {
		return fmt.Errorf("invalid %s cursor", label)
	}
	payload, err := base64.RawURLEncoding.Strict().DecodeString(trimmed)
	if err != nil || base64.RawURLEncoding.EncodeToString(payload) != trimmed {
		return fmt.Errorf("invalid %s cursor", label)
	}
	decoder := json.NewDecoder(bytes.NewReader(payload))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(target); err != nil {
		return fmt.Errorf("invalid %s cursor", label)
	}
	var extra any
	if err := decoder.Decode(&extra); err != io.EOF {
		return fmt.Errorf("invalid %s cursor", label)
	}
	return nil
}
