// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package globalref

import (
	"fmt"

	"github.com/opentofu/opentofu/internal/addrs"
	"github.com/opentofu/opentofu/internal/lang"
)

// ReferencesFromOutputValue returns all of the direct references from the
// value expression of the given output value. It doesn't include any indirect
// references.
func (a *Analyzer) ReferencesFromOutputValue(addr addrs.AbsOutputValue) []Reference {
	mc := a.ModuleConfig(addr.Module)
	if mc == nil {
		return nil
	}
	oc := mc.Outputs[addr.OutputValue.Name]
	if oc == nil {
		return nil
	}
	refs, _ := lang.ReferencesInExpr(addrs.ParseRef, oc.Expr)
	return absoluteRefs(addr.Module, refs)
}

// ReferencesFromResourceInstance returns all of the direct references from the
// definition of the resource instance at the given address. It doesn't include
// any indirect references.
//
// The result doesn't directly include references from a "count" or "for_each"
// expression belonging to the associated resource, but it will include any
// references to count.index, each.key, or each.value that appear in the
// expressions which you can then, if you wish, resolve indirectly using
// Analyzer.MetaReferences. Alternatively, you can use
// Analyzer.ReferencesFromResourceRepetition to get that same result directly.
func (a *Analyzer) ReferencesFromResourceInstance(addr addrs.AbsResourceInstance) []Reference {
	// Using MetaReferences for this is kinda overkill, since
	// lang.ReferencesInBlock would be sufficient really, but
	// this ensures we keep consistent in how we build the
	// resulting absolute references and otherwise aside from
	// some extra overhead this call boils down to a call to
	// lang.ReferencesInBlock anyway.
	fakeRef := Reference{
		ContainerAddr: addr.Module,
		LocalRef: &addrs.Reference{
			Subject: addr.Resource,
		},
	}
	return a.MetaReferences(fakeRef)
}

// ReferencesFromResourceRepetition returns the references from the given
// resource's for_each or count expression, or an empty set if the resource
// doesn't use repetition.
//
// This is a special-case sort of helper for use in situations where an
// expression might refer to count.index, each.key, or each.value, and thus
// we say that it depends indirectly on the repetition expression.
func (a *Analyzer) ReferencesFromResourceRepetition(addr addrs.AbsResource) []Reference {
	modCfg := a.ModuleConfig(addr.Module)
	if modCfg == nil {
		return nil
	}
	rc := modCfg.ResourceByAddr(addr.Resource)
	if rc == nil {
		return nil
	}

	// We're assuming here that resources can either have count or for_each,
	// but never both, because that's a requirement enforced by the language
	// decoder. But we'll assert it just to make sure we catch it if that
	// changes for some reason.
	if rc.ForEach != nil && rc.Count != nil {
		panic(fmt.Sprintf("%s has both for_each and count", addr))
	}

	switch {
	case rc.ForEach != nil:
		refs, _ := lang.ReferencesInExpr(addrs.ParseRef, rc.ForEach)
		return absoluteRefs(addr.Module, refs)
	case rc.Count != nil:
		refs, _ := lang.ReferencesInExpr(addrs.ParseRef, rc.Count)
		return absoluteRefs(addr.Module, refs)
	default:
		return nil
	}
}
