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

package stackconfig

import (
	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/gohcl"
	"github.com/hashicorp/hcl/v2/hclsyntax"
	"github.com/hashicorp/terraform/internal/stacks/stackconfig/typeexpr"
	"github.com/hashicorp/terraform/internal/tfdiags"
	"github.com/zclconf/go-cty/cty"
)

// InputVariable is a declaration of an input variable within a stack
// configuration. Callers must provide the values for these variables.
type InputVariable struct {
	Name string

	Type         TypeConstraint
	DefaultValue cty.Value
	Description  string

	Sensitive bool
	Ephemeral bool
	// Validations contains custom validation rules for this variable.
	// These rules are evaluated at runtime during the plan phase to ensure
	// that provided values meet the specified constraints.
	// Each CheckRule includes a condition expression that must evaluate to true,
	// and an error message to display if the validation fails.
	Validations []*CheckRule

	DeclRange tfdiags.SourceRange
}

// TypeConstraint represents all of the type constraint information for either
// an input variable or an output value.
//
// After initial decoding only Expression is populated, and it has not yet been
// analyzed at all so is not even guaranteed to be a valid type constraint
// expression.
//
// For configurations loaded through the main entry point [LoadConfigDir],
// Constraint is populated with the result of decoding Expression as a type
// constraint only if the expression is a valid type constraint expression.
// When loading through shallower entry points such as [DecodeFileBody],
// Constraint is not populated.
//
// Defaults is populated only if Constraint is, and if not nil represents any
// default values from the type constraint expression.
type TypeConstraint struct {
	Expression hcl.Expression
	Constraint cty.Type
	Defaults   *typeexpr.Defaults
}

func decodeInputVariableBlock(block *hcl.Block) (*InputVariable, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics
	ret := &InputVariable{
		Name:      block.Labels[0],
		DeclRange: tfdiags.SourceRangeFromHCL(block.DefRange),
	}
	if !hclsyntax.ValidIdentifier(ret.Name) {
		diags = diags.Append(invalidNameDiagnostic(
			"Invalid name for input variable",
			block.LabelRanges[0],
		))
		return nil, diags
	}

	content, hclDiags := block.Body.Content(inputVariableBlockSchema)
	diags = diags.Append(hclDiags)

	if attr, ok := content.Attributes["type"]; ok {
		ret.Type.Expression = attr.Expr
	}
	if attr, ok := content.Attributes["default"]; ok {
		val, hclDiags := attr.Expr.Value(nil)
		diags = diags.Append(hclDiags)
		if val == cty.NilVal {
			val = cty.DynamicVal
		}
		ret.DefaultValue = val
	}
	if attr, ok := content.Attributes["description"]; ok {
		hclDiags := gohcl.DecodeExpression(attr.Expr, nil, &ret.Description)
		diags = diags.Append(hclDiags)
	}
	if attr, ok := content.Attributes["sensitive"]; ok {
		hclDiags := gohcl.DecodeExpression(attr.Expr, nil, &ret.Sensitive)
		diags = diags.Append(hclDiags)
	}
	if attr, ok := content.Attributes["ephemeral"]; ok {
		hclDiags := gohcl.DecodeExpression(attr.Expr, nil, &ret.Ephemeral)
		diags = diags.Append(hclDiags)
	}

	// Process any nested blocks (currently only validation blocks are supported)
	for _, block := range content.Blocks {
		switch block.Type {
		case "validation":
			// Decode the validation block into a CheckRule structure.
			// This only validates the syntax and structure of the validation block itself,
			// not the actual runtime validation of input values.
			vv, hclDiags := decodeCheckRuleBlock(block)
			diags = diags.Append(hclDiags)
			// Only add the validation rule if it was successfully parsed.
			// If there were errors (e.g., missing condition or error_message),
			// those errors are already captured in diags above.
			if !hclDiags.HasErrors() {
				ret.Validations = append(ret.Validations, vv)
			}
		default:
			// Should not get here as the schema defines what blocks are allowed
			panic("unhandled block type " + block.Type)
		}
	}

	return ret, diags
}

var inputVariableBlockSchema = &hcl.BodySchema{
	Attributes: []hcl.AttributeSchema{
		{Name: "type", Required: true},
		{Name: "default", Required: false},
		{Name: "description", Required: false},
		{Name: "sensitive", Required: false},
		{Name: "ephemeral", Required: false},
	},
	// Validation blocks allow custom validation rules for input variables.
	// Multiple validation blocks are allowed per variable.
	Blocks: []hcl.BlockHeaderSchema{
		{Type: "validation"},
	},
}
