package client

import (
	"context"
	"fmt"

	"github.com/agentregistry-dev/agentregistry/pkg/api/v1alpha1"
)

// GetTyped fetches one resource and materializes its typed v1alpha1 envelope.
// Empty tag resolves the latest tag for taggable resources.
func GetTyped[T v1alpha1.Object](
	ctx context.Context,
	c *Client,
	kind, namespace, name, tag string,
	newObj func() T,
) (T, error) {
	var zero T
	if c == nil {
		return zero, fmt.Errorf("client is nil")
	}

	var (
		raw *v1alpha1.RawObject
		err error
	)
	if tag == "" {
		raw, err = c.GetLatest(ctx, kind, namespace, name)
	} else {
		raw, err = c.Get(ctx, kind, namespace, name, tag)
	}
	if err != nil {
		return zero, err
	}
	return v1alpha1.EnvelopeFromRaw(newObj, raw, kind)
}

// ListTyped lists resources of one kind and materializes each typed envelope.
func ListTyped[T v1alpha1.Object](
	ctx context.Context,
	c *Client,
	kind string,
	opts ListOpts,
	newObj func() T,
) ([]T, string, error) {
	if c == nil {
		return nil, "", fmt.Errorf("client is nil")
	}

	rows, nextCursor, err := c.List(ctx, kind, opts)
	if err != nil {
		return nil, "", err
	}

	out := make([]T, 0, len(rows))
	for i := range rows {
		obj, err := v1alpha1.EnvelopeFromRaw(newObj, &rows[i], kind)
		if err != nil {
			return nil, "", fmt.Errorf("decode %s row %d: %w", kind, i, err)
		}
		out = append(out, obj)
	}
	return out, nextCursor, nil
}

// ListAllTyped follows NextCursor until the full result set has been loaded.
func ListAllTyped[T v1alpha1.Object](
	ctx context.Context,
	c *Client,
	kind string,
	opts ListOpts,
	newObj func() T,
) ([]T, error) {
	var out []T
	cursor := opts.Cursor
	for {
		pageOpts := opts
		pageOpts.Cursor = cursor
		items, nextCursor, err := ListTyped(ctx, c, kind, pageOpts, newObj)
		if err != nil {
			return nil, err
		}
		out = append(out, items...)
		if nextCursor == "" {
			return out, nil
		}
		cursor = nextCursor
	}
}

// ListTagsOfName returns every tag of one named resource with
// the latest tag first. Wraps GET /v0/{plural}/{name}/tags and
// materializes each row into a typed envelope.
func ListTagsOfName[T v1alpha1.Object](
	ctx context.Context,
	c *Client,
	kind, namespace, name string,
	newObj func() T,
) ([]T, error) {
	if c == nil {
		return nil, fmt.Errorf("client is nil")
	}
	rows, err := c.ListTags(ctx, kind, namespace, name)
	if err != nil {
		return nil, err
	}
	out := make([]T, 0, len(rows))
	for i := range rows {
		obj, err := v1alpha1.EnvelopeFromRaw(newObj, &rows[i], kind)
		if err != nil {
			return nil, fmt.Errorf("decode %s row %d: %w", kind, i, err)
		}
		out = append(out, obj)
	}
	return out, nil
}
