// 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 (
	"context"
	"log"

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

// StateTransformer is a GraphTransformer that adds the elements of
// the state to the graph.
//
// This transform is used for example by the DestroyPlanGraphBuilder to ensure
// that only resources that are in the state are represented in the graph.
type StateTransformer struct {
	// ConcreteCurrent and ConcreteDeposed are used to specialize the abstract
	// resource instance nodes that this transformer will create.
	//
	// If either of these is nil, the objects of that type will be skipped and
	// not added to the graph at all. It doesn't make sense to use this
	// transformer without setting at least one of these, since that would
	// skip everything and thus be a no-op.
	ConcreteCurrent ConcreteResourceInstanceNodeFunc
	ConcreteDeposed ConcreteResourceInstanceDeposedNodeFunc

	State *states.State
}

func (t *StateTransformer) Transform(_ context.Context, g *Graph) error {
	if t.State == nil {
		log.Printf("[TRACE] StateTransformer: state is nil, so nothing to do")
		return nil
	}

	switch {
	case t.ConcreteCurrent != nil && t.ConcreteDeposed != nil:
		log.Printf("[TRACE] StateTransformer: creating nodes for both current and deposed instance objects")
	case t.ConcreteCurrent != nil:
		log.Printf("[TRACE] StateTransformer: creating nodes for current instance objects only")
	case t.ConcreteDeposed != nil:
		log.Printf("[TRACE] StateTransformer: creating nodes for deposed instance objects only")
	default:
		log.Printf("[TRACE] StateTransformer: pointless no-op call, creating no nodes at all")
	}

	for _, ms := range t.State.Modules {
		for _, rs := range ms.Resources {
			resourceAddr := rs.Addr

			for key, is := range rs.Instances {
				addr := resourceAddr.Instance(key)

				if obj := is.Current; obj != nil && t.ConcreteCurrent != nil {
					abstract := NewNodeAbstractResourceInstance(addr)
					node := t.ConcreteCurrent(abstract)
					g.Add(node)
					log.Printf("[TRACE] StateTransformer: added %T for %s current object", node, addr)
				}

				if t.ConcreteDeposed != nil {
					for dk := range is.Deposed {
						abstract := NewNodeAbstractResourceInstance(addr)
						node := t.ConcreteDeposed(abstract, dk)
						g.Add(node)
						log.Printf("[TRACE] StateTransformer: added %T for %s deposed object %s", node, addr, dk)
					}
				}
			}
		}
	}

	return nil
}
