// 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"

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

// nodeExpandApplyableResource handles the first layer of resource
// expansion during apply. Even though the resource instances themselves are already expanded from the plan,
// we still need to register resource nodes in instances.Expander with their absolute addresses
// based on the expanded module instances.
//
// "Expand" in the name refers to instances.Expander usage. This node doesn't generate a dynamic subgraph.
type nodeExpandApplyableResource struct {
	*NodeAbstractResource
}

var (
	_ GraphNodeExecutable           = (*nodeExpandApplyableResource)(nil)
	_ GraphNodeReferenceable        = (*nodeExpandApplyableResource)(nil)
	_ GraphNodeReferencer           = (*nodeExpandApplyableResource)(nil)
	_ GraphNodeConfigResource       = (*nodeExpandApplyableResource)(nil)
	_ GraphNodeAttachResourceConfig = (*nodeExpandApplyableResource)(nil)
	// nodeExpandApplyableResource needs to be retained during unused nodes pruning
	// to register the resource for expanded module instances in `instances.Expander`
	_ graphNodeRetainedByPruneUnusedNodesTransformer = (*nodeExpandApplyableResource)(nil)
	_ GraphNodeTargetable                            = (*nodeExpandApplyableResource)(nil)
)

func (n *nodeExpandApplyableResource) retainDuringUnusedPruning() {
}

func (n *nodeExpandApplyableResource) References() []*addrs.Reference {
	refs := n.NodeAbstractResource.References()

	// The expand node needs to connect to the individual resource instances it
	// references, but cannot refer to it's own instances without causing
	// cycles. It would be preferable to entirely disallow self references
	// without the `self` identifier, but those were allowed in provisioners
	// for compatibility with legacy configuration. We also can't always just
	// filter them out for all resource node types, because the only method we
	// have for catching certain invalid configurations are the cycles that
	// result from these inter-instance references.
	return filterSelfRefs(n.Addr.Resource, refs)
}

func (n *nodeExpandApplyableResource) Name() string {
	return n.NodeAbstractResource.Name() + " (expand)"
}

func (n *nodeExpandApplyableResource) Execute(ctx context.Context, evalCtx EvalContext, op walkOperation) tfdiags.Diagnostics {
	var diags tfdiags.Diagnostics
	expander := evalCtx.InstanceExpander()
	moduleInstances := expander.ExpandModule(n.Addr.Module)
	for _, module := range moduleInstances {
		evalCtx = evalCtx.WithPath(module)
		diags = diags.Append(n.writeResourceState(ctx, evalCtx, n.Addr.Resource.Absolute(module)))
	}

	return diags
}
