package engine

import (
	"fmt"
	"slices"
	"strings"
	"unicode"

	"tangled.org/core/api/tangled"
	"tangled.org/core/spindle/models"
)

// PlacementPlanner derives the engine capabilities a workflow needs. Planners
// run in the trusted mill process and must not inspect executor-local state.
type PlacementPlanner interface {
	Requirements(twf tangled.Pipeline_Workflow, tpl tangled.Pipeline) ([]string, error)
}

// CapabilityProvider reports the cached placement capabilities of one local
// engine instance. Capabilities are opaque to the mill.
type CapabilityProvider interface {
	Capabilities() []string
}

// WorkflowPlacementValidator performs the definitive immutable compatibility
// checks before an executor accepts and holds a remote lease.
type WorkflowPlacementValidator interface {
	ValidateWorkflowPlacement(wf *models.Workflow) error
}

// NormalizeCapabilities validates, sorts, and deduplicates an opaque
// capability set so snapshots and workflow requirements compare deterministically.
func NormalizeCapabilities(capabilities []string) ([]string, error) {
	seen := make(map[string]struct{}, len(capabilities))
	normalized := make([]string, 0, len(capabilities))
	for _, capability := range capabilities {
		capability = strings.TrimSpace(capability)
		if capability == "" || strings.ContainsFunc(capability, unicode.IsSpace) {
			return nil, fmt.Errorf("invalid empty or whitespace-containing engine capability %q", capability)
		}
		if _, ok := seen[capability]; ok {
			continue
		}
		seen[capability] = struct{}{}
		normalized = append(normalized, capability)
	}
	slices.Sort(normalized)
	return normalized, nil
}
