// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package stackaddrs

import (
	"reflect"

	"github.com/hashicorp/terraform/internal/collections"
)

// Referenceable is a type set containing all address types that can be
// the target of an expression-based reference within a particular stack.
type Referenceable interface {
	referenceableSigil()
	String() string
}

var _ Referenceable = Component{}
var _ Referenceable = StackCall{}
var _ Referenceable = InputVariable{}
var _ Referenceable = LocalValue{}
var _ Referenceable = ProviderConfigRef{}
var _ Referenceable = TestOnlyGlobal{}
var _ Referenceable = ContextualRef(0)

// ReferenceableUniqueKey returns a unique key for a dynamically-typed
// referenceable address.
//
// Since the type of the target isn't statically known, the resulting unique
// key is not comparable with unique keys of the specific static types
// that implement [Referenceable].
func ReferenceableUniqueKey(addr Referenceable) collections.UniqueKey[Referenceable] {
	// NOTE: This assumes that all distinct referenceable addresses have
	// distinct and unique string representations.
	return referenceableUniqueKey{
		ty:  reflect.TypeOf(addr),
		str: addr.String(),
	}
}

type referenceableUniqueKey struct {
	ty  reflect.Type
	str string
}

// IsUniqueKey implements collections.UniqueKey.
func (referenceableUniqueKey) IsUniqueKey(Referenceable) {}

type ContextualRef rune

const EachValue = ContextualRef('v')
const EachKey = ContextualRef('k')
const CountIndex = ContextualRef('i')
const Self = ContextualRef('s')

// String implements Referenceable.
func (e ContextualRef) String() string {
	switch e {
	case EachKey:
		return "each.key"
	case EachValue:
		return "each.value"
	case CountIndex:
		return "count.index"
	case Self:
		return "self"
	default:
		// The four constants in this package are the only valid values of this type
		panic("invalid ContextualRef instance")
	}
}

// referenceableSigil implements Referenceable.
func (e ContextualRef) referenceableSigil() {}

// AbsReferenceable is a [Referenceable] combined with the stack it would
// be resolved in.
//
// This type can be used only for [Referenceable] types that have stack-wide
// scope. It's not appropriate for referenceable objects with more specific
// scope, such as [ContextualRef], since describing those would require
// information about which specific block they are to be resolved within.
type AbsReferenceable struct {
	Stack StackInstance
	Item  Referenceable
}

func (r AbsReferenceable) UniqueKey() collections.UniqueKey[AbsReferenceable] {
	return absReferenceableKey{
		stackKey: r.Stack.UniqueKey(),
		itemKey:  ReferenceableUniqueKey(r.Item),
	}
}

type absReferenceableKey struct {
	stackKey collections.UniqueKey[StackInstance]
	itemKey  collections.UniqueKey[Referenceable]
}

// IsUniqueKey implements collections.UniqueKey.
func (absReferenceableKey) IsUniqueKey(AbsReferenceable) {
	panic("unimplemented")
}
