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

package tofu

import (
	"log"

	"github.com/opentofu/opentofu/internal/addrs"
	"github.com/opentofu/opentofu/internal/dag"
	"github.com/opentofu/opentofu/internal/states"
)

// OrphanResourceInstanceCountTransformer is a GraphTransformer that adds orphans
// for an expanded count to the graph. The determination of this depends
// on the count argument given.
//
// Orphans are found by comparing the count to what is found in the state.
// This transform assumes that if an element in the state is within the count
// bounds given, that it is not an orphan.
type OrphanResourceInstanceCountTransformer struct {
	Concrete ConcreteResourceInstanceNodeFunc

	Addr          addrs.AbsResource           // Addr of the resource to look for orphans
	InstanceAddrs []addrs.AbsResourceInstance // Addresses that currently exist in config
	State         *states.State               // Full global state
}

func (t *OrphanResourceInstanceCountTransformer) Transform(g *Graph) error {
	rs := t.State.Resource(t.Addr)
	if rs == nil {
		return nil // Resource doesn't exist in state, so nothing to do!
	}

	// This is an O(n*m) analysis, which we accept for now because the
	// number of instances of a single resource ought to always be small in any
	// reasonable OpenTofu configuration.
Have:
	for key, inst := range rs.Instances {
		// Instances which have no current objects (only one or more
		// deposed objects) will be taken care of separately
		if inst.Current == nil {
			continue
		}

		thisAddr := rs.Addr.Instance(key)
		for _, wantAddr := range t.InstanceAddrs {
			if wantAddr.Equal(thisAddr) {
				continue Have
			}
		}
		// If thisAddr is not in t.InstanceAddrs then we've found an "orphan"

		abstract := NewNodeAbstractResourceInstance(thisAddr)
		var node dag.Vertex = abstract
		if f := t.Concrete; f != nil {
			node = f(abstract)
		}
		log.Printf("[TRACE] OrphanResourceInstanceCountTransformer: adding %s as %T", thisAddr, node)
		g.Add(node)
	}

	return nil
}
