package stack

import (
	"sort"

	"github.com/buildpacks/pack/internal/stringset"
	"github.com/buildpacks/pack/pkg/dist"
)

const WildcardStack = "*"

// MergeCompatible determines the allowable set of stacks that a combination of buildpacks may run on, given each
// buildpack's set of stacks. Compatibility between the two sets of buildpack stacks is defined by the following rules:
//
//  1. The stack must be supported by both buildpacks. That is, any resulting stack ID must appear in both input sets.
//  2. For each supported stack ID, all required mixins for all buildpacks must be provided by the result. That is,
//     mixins for the stack ID in both input sets are unioned.
//  3. If there is a wildcard stack in either of the stack list, the stack list not having the wild card stack is returned.
//  4. If both the stack lists contain a wildcard stack, a list containing just the wildcard stack is returned.
//
// ---
//
// Examples:
//
//	stacksA = [{ID: "stack1", mixins: ["build:mixinA", "mixinB", "run:mixinC"]}}]
//	stacksB = [{ID: "stack1", mixins: ["build:mixinA", "run:mixinC"]}}]
//	result = [{ID: "stack1", mixins: ["build:mixinA", "mixinB", "run:mixinC"]}}]
//
//	stacksA = [{ID: "stack1", mixins: ["build:mixinA"]}}, {ID: "stack2", mixins: ["mixinA"]}}]
//	stacksB = [{ID: "stack1", mixins: ["run:mixinC"]}}, {ID: "stack2", mixins: ["mixinA"]}}]
//	result = [{ID: "stack1", mixins: ["build:mixinA", "run:mixinC"]}}, {ID: "stack2", mixins: ["mixinA"]}}]
//
//	stacksA = [{ID: "stack1", mixins: ["build:mixinA"]}}, {ID: "stack2", mixins: ["mixinA"]}}]
//	stacksB = [{ID: "stack2", mixins: ["mixinA", "run:mixinB"]}}]
//	result = [{ID: "stack2", mixins: ["mixinA", "run:mixinB"]}}]
//
//	stacksA = [{ID: "stack1", mixins: ["build:mixinA"]}}]
//	stacksB = [{ID: "stack2", mixins: ["mixinA", "run:mixinB"]}}]
//	result = []
//
//	stacksA = [{ID: "*"}, {ID: "stack1", mixins: ["build:mixinC"]}]
//	stacksB = [{ID: "stack1", mixins: ["build:mixinA"]}, {ID: "stack2", mixins: ["mixinA", "run:mixinB"]}]
//	result = [{ID: "stack1", mixins: ["build:mixinA"]}, {ID: "stack2", mixins: ["mixinA", "run:mixinB"]}]
//
//	stacksA = [{ID: "stack1", mixins: ["build:mixinA"]}, {ID: "stack2", mixins: ["mixinA", "run:mixinB"]}]
//	stacksB = [{ID: "*"}, {ID: "stack1", mixins: ["build:mixinC"]}]
//	result = [{ID: "stack1", mixins: ["build:mixinA"]}, {ID: "stack2", mixins: ["mixinA", "run:mixinB"]}]
//
//	stacksA = [{ID: "*"}, {ID: "stack1", mixins: ["build:mixinA"]}, {ID: "stack2", mixins: ["mixinA", "run:mixinB"]}]
//	stacksB = [{ID: "*"}, {ID: "stack1", mixins: ["build:mixinC"]}]
//	result = [{ID: "*"}]
func MergeCompatible(stacksA []dist.Stack, stacksB []dist.Stack) []dist.Stack {
	set := map[string][]string{}
	AHasWildcardStack, BHasWildcardStack := false, false

	for _, s := range stacksA {
		set[s.ID] = s.Mixins
		if s.ID == WildcardStack {
			AHasWildcardStack = true
		}
	}

	for _, s := range stacksB {
		if s.ID == WildcardStack {
			BHasWildcardStack = true
		}
	}

	if AHasWildcardStack && BHasWildcardStack {
		return []dist.Stack{{ID: WildcardStack}}
	}

	if AHasWildcardStack {
		return stacksB
	}

	if BHasWildcardStack {
		return stacksA
	}

	var results []dist.Stack

	for _, s := range stacksB {
		if stackMixins, ok := set[s.ID]; ok {
			mixinsSet := stringset.FromSlice(append(stackMixins, s.Mixins...))
			var mixins []string
			for m := range mixinsSet {
				mixins = append(mixins, m)
			}
			sort.Strings(mixins)

			results = append(results, dist.Stack{
				ID:     s.ID,
				Mixins: mixins,
			})
		}
	}

	return results
}
