// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1

package stackstate

import (
	"iter"

	"github.com/zclconf/go-cty/cty"
	"google.golang.org/protobuf/types/known/anypb"

	"github.com/hashicorp/terraform/internal/addrs"
	"github.com/hashicorp/terraform/internal/collections"
	"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
	"github.com/hashicorp/terraform/internal/stacks/stackstate/statekeys"
	"github.com/hashicorp/terraform/internal/states"
)

// State represents a previous run's state snapshot.
//
// Unlike [states.State] and its associates, State is an immutable data
// structure constructed to represent only the previous run state. It should
// not be modified after it's been constructed; results of planning or applying
// changes are represented in other ways inside the stacks language runtime.
type State struct {
	root    *stackInstanceState
	outputs map[stackaddrs.OutputValue]cty.Value
	inputs  map[stackaddrs.InputVariable]cty.Value

	// discardUnsupportedKeys is the set of state keys that we encountered
	// during decoding which are of types that are not supported by this
	// version of Terraform, if and only if they are of a type which is
	// specified as being discarded when unrecognized. We should emit
	// events during the apply phase to delete the objects associated with
	// these keys.
	discardUnsupportedKeys collections.Set[statekeys.Key]

	inputRaw map[string]*anypb.Any
}

// NewState constructs a new, empty state.
func NewState() *State {
	return &State{
		root:                   newStackInstanceState(stackaddrs.RootStackInstance),
		outputs:                make(map[stackaddrs.OutputValue]cty.Value),
		inputs:                 make(map[stackaddrs.InputVariable]cty.Value),
		discardUnsupportedKeys: statekeys.NewKeySet(),
		inputRaw:               nil,
	}
}

// RootInputVariables returns the values for the input variables currently in
// the state. An address that is in the map and maps to cty.NilVal is an
// ephemeral input, so it was present during the last operation but the value
// in unknown. Compared to an input variable not in the map at all, which
// indicates a new input variable that wasn't in the configuration during the
// last operation.
func (s *State) RootInputVariables() map[stackaddrs.InputVariable]cty.Value {
	return s.inputs
}

// RootInputVariable returns the input variable defined at the given address.
// If the second return value is true, then the value is present but is
// ephemeral and not known. If the first returned value is cty.NilVal and the
// second is false then the value isn't present in the state.
func (s *State) RootInputVariable(addr stackaddrs.InputVariable) cty.Value {
	return s.inputs[addr]
}

func (s *State) RootOutputValues() map[stackaddrs.OutputValue]cty.Value {
	return s.outputs
}

func (s *State) RootOutputValue(addr stackaddrs.OutputValue) cty.Value {
	return s.outputs[addr]
}

func (s *State) HasComponentInstance(addr stackaddrs.AbsComponentInstance) bool {
	stack := s.root.getDescendent(addr.Stack)
	if stack == nil {
		return false
	}
	return stack.getComponentInstance(addr.Item) != nil
}

func (s *State) HasStackInstance(addr stackaddrs.StackInstance) bool {
	stack := s.root.getDescendent(addr)
	return stack != nil
}

// AllComponentInstances returns a set of addresses for all of the component
// instances that are tracked in the state.
//
// This includes both instances that were explicitly represented in the source
// raw state _and_ any that were missing but implied by a resource instance
// existing inside them. There should typically be an explicit component
// instance record tracked in raw state, but it can potentially be absent in
// exceptional cases such as if Terraform Core crashed partway through the
// previous run.
func (s *State) AllComponentInstances() iter.Seq[stackaddrs.AbsComponentInstance] {
	return func(yield func(stackaddrs.AbsComponentInstance) bool) {
		s.root.iterate(yield)
	}
}

// ComponentInstances returns the set of component instances that belong to the
// given component, or an empty set if no such component is tracked in the
// state.
//
// This will always be a subset of AllComponentInstances.
func (s *State) ComponentInstances(addr stackaddrs.AbsComponent) iter.Seq[stackaddrs.ComponentInstance] {
	return func(yield func(stackaddrs.ComponentInstance) bool) {
		target := s.root.getDescendent(addr.Stack)
		if target == nil {
			return
		}

		for key := range target.components[addr.Item] {
			yield(stackaddrs.ComponentInstance{
				Component: addr.Item,
				Key:       key,
			})
		}
	}
}

// StackInstances returns the set of known stack instances for the given stack
// call.
func (s *State) StackInstances(call stackaddrs.AbsStackCall) iter.Seq[stackaddrs.StackInstance] {
	return func(yield func(stackaddrs.StackInstance) bool) {
		target := s.root.getDescendent(call.Stack)
		if target == nil {
			return
		}

		for _, stack := range target.children[call.Item.Name] {
			yield(stack.address)
		}
	}
}

func (s *State) componentInstanceState(addr stackaddrs.AbsComponentInstance) *componentInstanceState {
	target := s.root.getDescendent(addr.Stack)
	if target == nil {
		return nil
	}
	return target.getComponentInstance(addr.Item)
}

// DependenciesForComponent returns the list of components that are required by
// the given component instance, or an empty set if no such component instance
// is tracked in the state.
func (s *State) DependenciesForComponent(addr stackaddrs.AbsComponentInstance) collections.Set[stackaddrs.AbsComponent] {
	cs := s.componentInstanceState(addr)
	if cs == nil {
		return collections.NewSet[stackaddrs.AbsComponent]()
	}
	return cs.dependencies
}

// DependentsForComponent returns the list of components that are require the
// given component instance, or an empty set if no such component instance is
// tracked in the state.
func (s *State) DependentsForComponent(addr stackaddrs.AbsComponentInstance) collections.Set[stackaddrs.AbsComponent] {
	cs := s.componentInstanceState(addr)
	if cs == nil {
		return collections.NewSet[stackaddrs.AbsComponent]()
	}
	return cs.dependents
}

// ResultsForComponent returns the output values for the given component
// instance, or nil if no such component instance is tracked in the state.
func (s *State) ResultsForComponent(addr stackaddrs.AbsComponentInstance) map[addrs.OutputValue]cty.Value {
	cs := s.componentInstanceState(addr)
	if cs == nil {
		return nil
	}
	return cs.outputValues
}

// InputsForComponent returns the input values for the given component
// instance, or nil if no such component instance is tracked in the state.
func (s *State) InputsForComponent(addr stackaddrs.AbsComponentInstance) map[addrs.InputVariable]cty.Value {
	cs := s.componentInstanceState(addr)
	if cs == nil {
		return nil
	}
	return cs.inputVariables
}

type IdentitySrc struct {
	IdentitySchemaVersion uint64
	IdentityJSON          []byte
}

// IdentitiesForComponent returns the identity values for the given component
// instance, or nil if no such component instance is tracked in the state.
func (s *State) IdentitiesForComponent(addr stackaddrs.AbsComponentInstance) map[*addrs.AbsResourceInstanceObject]IdentitySrc {
	cs := s.componentInstanceState(addr)
	if cs == nil {
		return nil
	}

	res := make(map[*addrs.AbsResourceInstanceObject]IdentitySrc)
	for _, rio := range cs.resourceInstanceObjects.Elements() {
		res[&rio.Key] = IdentitySrc{
			IdentitySchemaVersion: rio.Value.src.IdentitySchemaVersion,
			IdentityJSON:          rio.Value.src.IdentityJSON,
		}
	}

	return res
}

// ComponentInstanceResourceInstanceObjects returns a set of addresses for
// all of the resource instance objects belonging to the component instance
// with the given address.
func (s *State) ComponentInstanceResourceInstanceObjects(addr stackaddrs.AbsComponentInstance) collections.Set[stackaddrs.AbsResourceInstanceObject] {
	var ret collections.Set[stackaddrs.AbsResourceInstanceObject]
	cs := s.componentInstanceState(addr)
	if cs == nil {
		return ret
	}
	ret = collections.NewSet[stackaddrs.AbsResourceInstanceObject]()
	for _, elem := range cs.resourceInstanceObjects.Elems {
		objKey := stackaddrs.AbsResourceInstanceObject{
			Component: addr,
			Item:      elem.Key,
		}
		ret.Add(objKey)
	}
	return ret
}

// ResourceInstanceObjectSrc returns the source (i.e. still encoded) version of
// the resource instance object for the given address, or nil if no such
// object is tracked in the state.
func (s *State) ResourceInstanceObjectSrc(addr stackaddrs.AbsResourceInstanceObject) *states.ResourceInstanceObjectSrc {
	rios := s.resourceInstanceObjectState(addr)
	if rios == nil {
		return nil
	}
	return rios.src
}

// RequiredProviderInstances returns a description of all of the provider
// instance slots that are required to satisfy the resource instances
// belonging to the given component instance.
//
// See also stackeval.ComponentConfig.RequiredProviderInstances for a similar
// function that operates on the configuration of a component instance rather
// than the state of one.
func (s *State) RequiredProviderInstances(component stackaddrs.AbsComponentInstance) addrs.Set[addrs.RootProviderConfig] {
	state := s.componentInstanceState(component)
	if state == nil {
		// Then we have no state for this component, which is fine.
		return addrs.MakeSet[addrs.RootProviderConfig]()
	}

	providerInstances := addrs.MakeSet[addrs.RootProviderConfig]()
	for _, elem := range state.resourceInstanceObjects.Elems {
		providerInstances.Add(addrs.RootProviderConfig{
			Provider: elem.Value.providerConfigAddr.Provider,
			Alias:    elem.Value.providerConfigAddr.Alias,
		})
	}
	return providerInstances
}

func (s *State) resourceInstanceObjectState(addr stackaddrs.AbsResourceInstanceObject) *resourceInstanceObjectState {
	cs := s.componentInstanceState(addr.Component)
	if cs == nil {
		return nil
	}
	return cs.resourceInstanceObjects.Get(addr.Item)
}

// ComponentInstanceStateForModulesRuntime returns a [states.State]
// representation of the objects tracked for the given component instance.
//
// This produces only a very bare-bones [states.State] that should be
// sufficient for use as a prior state for the modules runtime's plan function
// to consider, but likely won't be of much other use.
func (s *State) ComponentInstanceStateForModulesRuntime(addr stackaddrs.AbsComponentInstance) *states.State {
	return states.BuildState(func(ss *states.SyncState) {
		objAddrs := s.ComponentInstanceResourceInstanceObjects(addr)
		for objAddr := range objAddrs.All() {
			rios := s.resourceInstanceObjectState(objAddr)

			if objAddr.Item.IsCurrent() {
				ss.SetResourceInstanceCurrent(
					objAddr.Item.ResourceInstance,
					rios.src, rios.providerConfigAddr,
				)
			} else {
				ss.SetResourceInstanceDeposed(
					objAddr.Item.ResourceInstance, objAddr.Item.DeposedKey,
					rios.src, rios.providerConfigAddr,
				)
			}
		}
	})
}

// RawKeysToDiscard returns a set of raw state keys that the apply phase should
// emit "delete" events for to remove objects from the raw state map that
// will no longer be relevant or meaningful after this plan is applied.
//
// Do not modify the returned set.
func (s *State) RawKeysToDiscard() collections.Set[statekeys.Key] {
	return s.discardUnsupportedKeys
}

// InputRaw returns the raw representation of state that this object was built
// from, or nil if this object wasn't constructed by decoding a protocol buffers
// representation.
//
// All callers of this method get the same map, so callers must not modify
// the map or anything reachable through it.
func (s *State) InputRaw() map[string]*anypb.Any {
	return s.inputRaw
}

func (s *State) addOutputValue(addr stackaddrs.OutputValue, value cty.Value) {
	s.outputs[addr] = value
}

func (s *State) addInputVariable(addr stackaddrs.InputVariable, value cty.Value) {
	s.inputs[addr] = value
}

func (s *State) ensureComponentInstanceState(addr stackaddrs.AbsComponentInstance) *componentInstanceState {
	current := s.root
	for _, step := range addr.Stack {
		next := current.getChild(step)
		if next == nil {
			next = newStackInstanceState(append(current.address, step))

			children, ok := current.children[step.Name]
			if !ok {
				children = make(map[addrs.InstanceKey]*stackInstanceState)
			}
			children[step.Key] = next
			current.children[step.Name] = children
		}
		current = next
	}

	component := current.getComponentInstance(addr.Item)
	if component == nil {
		component = &componentInstanceState{
			dependencies:            collections.NewSet[stackaddrs.AbsComponent](),
			dependents:              collections.NewSet[stackaddrs.AbsComponent](),
			outputValues:            make(map[addrs.OutputValue]cty.Value),
			inputVariables:          make(map[addrs.InputVariable]cty.Value),
			resourceInstanceObjects: addrs.MakeMap[addrs.AbsResourceInstanceObject, *resourceInstanceObjectState](),
		}

		components, ok := current.components[addr.Item.Component]
		if !ok {
			components = make(map[addrs.InstanceKey]*componentInstanceState)
		}
		components[addr.Item.Key] = component
		current.components[addr.Item.Component] = components
	}
	return component
}

func (s *State) addResourceInstanceObject(addr stackaddrs.AbsResourceInstanceObject, src *states.ResourceInstanceObjectSrc, providerConfigAddr addrs.AbsProviderConfig) {
	cs := s.ensureComponentInstanceState(addr.Component)

	cs.resourceInstanceObjects.Put(addr.Item, &resourceInstanceObjectState{
		src:                src,
		providerConfigAddr: providerConfigAddr,
	})
}

type componentInstanceState struct {
	// dependencies is the set of component instances that this component
	// depended on the last time it was updated.
	dependencies collections.Set[stackaddrs.AbsComponent]

	// dependents is a set of component instances that depended on this
	// component the last time it was updated.
	dependents collections.Set[stackaddrs.AbsComponent]

	// outputValues is a map from output value addresses to their values at
	// completion of the last apply operation.
	outputValues map[addrs.OutputValue]cty.Value

	// inputVariables is a map from input variable addresses to their values at
	// completion of the last apply operation.
	inputVariables map[addrs.InputVariable]cty.Value

	// resourceInstanceObjects is a map from resource instance object addresses
	// to their state.
	resourceInstanceObjects addrs.Map[addrs.AbsResourceInstanceObject, *resourceInstanceObjectState]
}

type resourceInstanceObjectState struct {
	src                *states.ResourceInstanceObjectSrc
	providerConfigAddr addrs.AbsProviderConfig
}

type stackInstanceState struct {
	address    stackaddrs.StackInstance
	components map[stackaddrs.Component]map[addrs.InstanceKey]*componentInstanceState
	children   map[string]map[addrs.InstanceKey]*stackInstanceState
}

func newStackInstanceState(address stackaddrs.StackInstance) *stackInstanceState {
	return &stackInstanceState{
		address:    address,
		components: make(map[stackaddrs.Component]map[addrs.InstanceKey]*componentInstanceState),
		children:   make(map[string]map[addrs.InstanceKey]*stackInstanceState),
	}
}

func (s *stackInstanceState) getDescendent(stack stackaddrs.StackInstance) *stackInstanceState {
	if len(stack) == 0 {
		return s
	}

	next := s.getChild(stack[0])
	if next == nil {
		return nil
	}
	return next.getDescendent(stack[1:])
}

func (s *stackInstanceState) getChild(step stackaddrs.StackInstanceStep) *stackInstanceState {
	stacks, ok := s.children[step.Name]
	if !ok {
		return nil
	}
	return stacks[step.Key]
}

func (s *stackInstanceState) getComponentInstance(component stackaddrs.ComponentInstance) *componentInstanceState {
	components, ok := s.components[component.Component]
	if !ok {
		return nil
	}
	return components[component.Key]
}

func (s *stackInstanceState) iterate(yield func(stackaddrs.AbsComponentInstance) bool) bool {
	for component, components := range s.components {
		for key := range components {
			proceed := yield(stackaddrs.AbsComponentInstance{
				Stack: s.address,
				Item: stackaddrs.ComponentInstance{
					Component: component,
					Key:       key,
				},
			})
			if !proceed {
				return false
			}
		}
	}

	for _, children := range s.children {
		for _, child := range children {
			if !child.iterate(yield) {
				return false
			}
		}
	}

	return true
}
