package hcl

import (
	"fmt"

	"github.com/zclconf/go-cty/cty"
	"github.com/zclconf/go-cty/cty/convert"
)

// Index is a helper function that performs the same operation as the index
// operator in the HCL expression language. That is, the result is the
// same as it would be for collection[key] in a configuration expression.
//
// This is exported so that applications can perform indexing in a manner
// consistent with how the language does it, including handling of null and
// unknown values, etc.
//
// Diagnostics are produced if the given combination of values is not valid.
// Therefore a pointer to a source range must be provided to use in diagnostics,
// though nil can be provided if the calling application is going to
// ignore the subject of the returned diagnostics anyway.
func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics) {
	if collection.IsNull() {
		return cty.DynamicVal, Diagnostics{
			{
				Severity: DiagError,
				Summary:  "Attempt to index null value",
				Detail:   "This value is null, so it does not have any indices.",
				Subject:  srcRange,
			},
		}
	}
	if key.IsNull() {
		return cty.DynamicVal, Diagnostics{
			{
				Severity: DiagError,
				Summary:  "Invalid index",
				Detail:   "Can't use a null value as an indexing key.",
				Subject:  srcRange,
			},
		}
	}
	ty := collection.Type()
	kty := key.Type()
	if kty == cty.DynamicPseudoType || ty == cty.DynamicPseudoType {
		return cty.DynamicVal, nil
	}

	switch {

	case ty.IsListType() || ty.IsTupleType() || ty.IsMapType():
		var wantType cty.Type
		switch {
		case ty.IsListType() || ty.IsTupleType():
			wantType = cty.Number
		case ty.IsMapType():
			wantType = cty.String
		default:
			// should never happen
			panic("don't know what key type we want")
		}

		key, keyErr := convert.Convert(key, wantType)
		if keyErr != nil {
			return cty.DynamicVal, Diagnostics{
				{
					Severity: DiagError,
					Summary:  "Invalid index",
					Detail: fmt.Sprintf(
						"The given key does not identify an element in this collection value: %s.",
						keyErr.Error(),
					),
					Subject: srcRange,
				},
			}
		}

		has := collection.HasIndex(key)
		if !has.IsKnown() {
			if ty.IsTupleType() {
				return cty.DynamicVal, nil
			} else {
				return cty.UnknownVal(ty.ElementType()), nil
			}
		}
		if has.False() {
			return cty.DynamicVal, Diagnostics{
				{
					Severity: DiagError,
					Summary:  "Invalid index",
					Detail:   "The given key does not identify an element in this collection value.",
					Subject:  srcRange,
				},
			}
		}

		return collection.Index(key), nil

	case ty.IsObjectType():
		key, keyErr := convert.Convert(key, cty.String)
		if keyErr != nil {
			return cty.DynamicVal, Diagnostics{
				{
					Severity: DiagError,
					Summary:  "Invalid index",
					Detail: fmt.Sprintf(
						"The given key does not identify an element in this collection value: %s.",
						keyErr.Error(),
					),
					Subject: srcRange,
				},
			}
		}
		if !collection.IsKnown() {
			return cty.DynamicVal, nil
		}
		if !key.IsKnown() {
			return cty.DynamicVal, nil
		}

		attrName := key.AsString()

		if !ty.HasAttribute(attrName) {
			return cty.DynamicVal, Diagnostics{
				{
					Severity: DiagError,
					Summary:  "Invalid index",
					Detail:   "The given key does not identify an element in this collection value.",
					Subject:  srcRange,
				},
			}
		}

		return collection.GetAttr(attrName), nil

	default:
		return cty.DynamicVal, Diagnostics{
			{
				Severity: DiagError,
				Summary:  "Invalid index",
				Detail:   "This value does not have any indices.",
				Subject:  srcRange,
			},
		}
	}

}
