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

package graph

import (
	"fmt"

	"github.com/hashicorp/hcl/v2"
	"github.com/zclconf/go-cty/cty"

	"github.com/hashicorp/terraform/internal/addrs"
	"github.com/hashicorp/terraform/internal/configs"
	"github.com/hashicorp/terraform/internal/lang/langrefs"
	"github.com/hashicorp/terraform/internal/moduletest"
	"github.com/hashicorp/terraform/internal/terraform"
	"github.com/hashicorp/terraform/internal/tfdiags"
)

type TestInputValue struct {
	Value *terraform.InputValue
}

var (
	_ GraphNodeExecutable    = (*NodeVariableDefinition)(nil)
	_ GraphNodeReferenceable = (*NodeVariableDefinition)(nil)
)

type NodeVariableDefinition struct {
	Address string
	Config  *configs.Variable
	File    *moduletest.File
}

func (n *NodeVariableDefinition) Name() string {
	return fmt.Sprintf("var.%s", n.Address)
}

func (n *NodeVariableDefinition) Referenceable() addrs.Referenceable {
	return &addrs.InputVariable{Name: n.Address}
}

func (n *NodeVariableDefinition) Execute(ctx *EvalContext) {
	if ctx.Stopped() || ctx.Cancelled() {
		return // don't evaluate anything when stopped or cancelled
	}

	input, diags := ctx.EvaluateUnparsedVariable(n.Address, n.Config)
	if input != nil {
		n.File.AppendDiagnostics(diags)
		if diags.HasErrors() {
			ctx.SetVariableStatus(n.Address, moduletest.Error)
			return
		}
	} else {
		input = &terraform.InputValue{
			Value: cty.NilVal,
		}
	}

	value, diags := terraform.PrepareFinalInputVariableValue(addrs.AbsInputVariableInstance{
		Module: addrs.RootModuleInstance,
		Variable: addrs.InputVariable{
			Name: n.Address,
		},
	}, input, n.Config)
	n.File.AppendDiagnostics(diags)
	if diags.HasErrors() {
		ctx.SetVariableStatus(n.Address, moduletest.Error)
		return
	}

	ctx.SetVariable(n.Address, &terraform.InputValue{
		Value:       value,
		SourceType:  terraform.ValueFromConfig,
		SourceRange: tfdiags.SourceRangeFromHCL(n.Config.DeclRange),
	})
}

var (
	_ GraphNodeExecutable    = (*NodeVariableExpression)(nil)
	_ GraphNodeReferencer    = (*NodeVariableExpression)(nil)
	_ GraphNodeReferenceable = (*NodeVariableExpression)(nil)
)

type NodeVariableExpression struct {
	Address string
	Expr    hcl.Expression
	File    *moduletest.File
}

func (n *NodeVariableExpression) Name() string {
	return fmt.Sprintf("var.%s", n.Address)
}

func (n *NodeVariableExpression) Referenceable() addrs.Referenceable {
	return &addrs.InputVariable{Name: n.Address}
}

func (n *NodeVariableExpression) References() []*addrs.Reference {
	refs, _ := langrefs.ReferencesInExpr(addrs.ParseRefFromTestingScope, n.Expr)
	return refs
}

func (n *NodeVariableExpression) Execute(ctx *EvalContext) {
	refs, refDiags := langrefs.ReferencesInExpr(addrs.ParseRefFromTestingScope, n.Expr)

	if !ctx.ReferencesCompleted(refs) {

		// before we do anything with the diags, we're going to check that all
		// of the references we know about were successfully parsed. If not,
		// then we'll skip actually assessing this node.

		ctx.SetVariableStatus(n.Address, moduletest.Skip)
		return
	}

	if ctx.Stopped() || ctx.Cancelled() {
		return // don't evaluate anything when stopped or cancelled
	}

	n.File.AppendDiagnostics(refDiags)
	if refDiags.HasErrors() {
		ctx.SetVariableStatus(n.Address, moduletest.Error)
		return
	}

	evalContext, moreDiags := ctx.HclContext(refs)
	n.File.AppendDiagnostics(moreDiags)
	if moreDiags.HasErrors() {
		ctx.SetVariableStatus(n.Address, moduletest.Error)
		return
	}

	var diags tfdiags.Diagnostics
	value, valueDiags := n.Expr.Value(evalContext)
	n.File.AppendDiagnostics(diags.Append(valueDiags))
	if valueDiags.HasErrors() {
		ctx.SetVariableStatus(n.Address, moduletest.Error)
		return
	}

	ctx.SetVariable(n.Address, &terraform.InputValue{
		Value:       value,
		SourceType:  terraform.ValueFromConfig,
		SourceRange: tfdiags.SourceRangeFromHCL(n.Expr.Range()),
	})
}
