package appendorder

import (
	"context"

	"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
	"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
)

// CombineFunc combines values in the source-chunk order supplied by the
// framework. Implementations must not reorder the slice.
type CombineFunc[T any] func([]T) (T, error)

type TypedMerger[T any] struct {
	combine CombineFunc[T]
}

func NewTyped[T any](combine CombineFunc[T]) (*TypedMerger[T], error) {
	if combine == nil {
		return nil, mergerErrorf("combine function must not be nil")
	}
	return &TypedMerger[T]{combine: combine}, nil
}

func (m *TypedMerger[T]) Key() string { return Key }

func (m *TypedMerger[T]) Merge(ctx context.Context, req contracts.TypedMergeRequest[T]) (contracts.TypedMergeResult[T], error) {
	if m == nil || m.combine == nil {
		return contracts.TypedMergeResult[T]{}, mergerErrorf("merger must not be nil")
	}
	if ctx == nil {
		return contracts.TypedMergeResult[T]{}, mergerErrorf("context must not be nil")
	}
	if err := ctx.Err(); err != nil {
		return contracts.TypedMergeResult[T]{}, mergerErrorf("context error before merge: %w", err)
	}
	values := make([]T, len(req.ExtractOutputs))
	for i, output := range req.ExtractOutputs {
		values[i] = output.Value
	}
	value, err := m.combine(values)
	if err != nil {
		return contracts.TypedMergeResult[T]{}, mergerErrorf("combine values: %w", err)
	}
	return contracts.TypedMergeResult[T]{Value: value}, nil
}

func TypedModuleSpec(kind contracts.ArtifactKind) pipeline.ModuleSpec {
	spec := ModuleSpec()
	spec.ArtifactKind = kind
	return spec
}

func RegisterTyped[T any](registry *pipeline.MergerRegistry, kind contracts.ArtifactKind, combine CombineFunc[T]) error {
	validateOptions := func(options map[string]any) error { return pipeline.RejectUnknownOptions(options) }
	return pipeline.RegisterMergerBuilder(registry, TypedModuleSpec(kind), validateOptions, func(request pipeline.BuildRequest) (contracts.Merger[T], error) {
		if err := pipeline.RejectUnknownOptions(request.Options); err != nil {
			return nil, err
		}
		return NewTyped(combine)
	})
}
