package flatmap

import (
	"fmt"
	"sort"
	"strconv"
	"strings"

	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim"
)

// Expand takes a map and a key (prefix) and expands that value into
// a more complex structure. This is the reverse of the Flatten operation.
func Expand(m map[string]string, key string) interface{} {
	// If the key is exactly a key in the map, just return it
	if v, ok := m[key]; ok {
		if v == "true" {
			return true
		} else if v == "false" {
			return false
		}

		return v
	}

	// Check if the key is an array, and if so, expand the array
	if v, ok := m[key+".#"]; ok {
		// If the count of the key is unknown, then just put the unknown
		// value in the value itself. This will be detected by Terraform
		// core later.
		if v == hcl2shim.UnknownVariableValue {
			return v
		}

		return expandArray(m, key)
	}

	// Check if this is a prefix in the map
	prefix := key + "."
	for k := range m {
		if strings.HasPrefix(k, prefix) {
			return expandMap(m, prefix)
		}
	}

	return nil
}

func expandArray(m map[string]string, prefix string) []interface{} {
	num, err := strconv.ParseInt(m[prefix+".#"], 0, 0)
	if err != nil {
		panic(err)
	}

	// If the number of elements in this array is 0, then return an
	// empty slice as there is nothing to expand. Trying to expand it
	// anyway could lead to crashes as any child maps, arrays or sets
	// that no longer exist are still shown as empty with a count of 0.
	if num == 0 {
		return []interface{}{}
	}

	// NOTE: "num" is not necessarily accurate, e.g. if a user tampers
	// with state, so the following code should not crash when given a
	// number of items more or less than what's given in num. The
	// num key is mainly just a hint that this is a list or set.

	// The Schema "Set" type stores its values in an array format, but
	// using numeric hash values instead of ordinal keys. Take the set
	// of keys regardless of value, and expand them in numeric order.
	// See GH-11042 for more details.
	keySet := map[int]bool{}
	computed := map[string]bool{}
	for k := range m {
		if !strings.HasPrefix(k, prefix+".") {
			continue
		}

		key := k[len(prefix)+1:]
		idx := strings.Index(key, ".")
		if idx != -1 {
			key = key[:idx]
		}

		// skip the count value
		if key == "#" {
			continue
		}

		// strip the computed flag if there is one
		if strings.HasPrefix(key, "~") {
			key = key[1:]
			computed[key] = true
		}

		k, err := strconv.Atoi(key)
		if err != nil {
			panic(err)
		}
		keySet[int(k)] = true
	}

	keysList := make([]int, 0, num)
	for key := range keySet {
		keysList = append(keysList, key)
	}
	sort.Ints(keysList)

	result := make([]interface{}, len(keysList))
	for i, key := range keysList {
		keyString := strconv.Itoa(key)
		if computed[keyString] {
			keyString = "~" + keyString
		}
		result[i] = Expand(m, fmt.Sprintf("%s.%s", prefix, keyString))
	}

	return result
}

func expandMap(m map[string]string, prefix string) map[string]interface{} {
	// Submaps may not have a '%' key, so we can't count on this value being
	// here. If we don't have a count, just proceed as if we have have a map.
	if count, ok := m[prefix+"%"]; ok && count == "0" {
		return map[string]interface{}{}
	}

	result := make(map[string]interface{})
	for k := range m {
		if !strings.HasPrefix(k, prefix) {
			continue
		}

		key := k[len(prefix):]
		idx := strings.Index(key, ".")
		if idx != -1 {
			key = key[:idx]
		}
		if _, ok := result[key]; ok {
			continue
		}

		// skip the map count value
		if key == "%" {
			continue
		}

		result[key] = Expand(m, k[:len(prefix)+len(key)])
	}

	return result
}
