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

package configschema

import (
	"github.com/zclconf/go-cty/cty"
)

type StringKind int

const (
	StringPlain StringKind = iota
	StringMarkdown
)

// Block represents a configuration block.
//
// "Block" here is a logical grouping construct, though it happens to map
// directly onto the physical block syntax of Terraform's native configuration
// syntax. It may be a more a matter of convention in other syntaxes, such as
// JSON.
//
// When converted to a value, a Block always becomes an instance of an object
// type derived from its defined attributes and nested blocks
type Block struct {
	// Attributes describes any attributes that may appear directly inside
	// the block.
	Attributes map[string]*Attribute

	// BlockTypes describes any nested block types that may appear directly
	// inside the block.
	BlockTypes map[string]*NestedBlock

	Description     string
	DescriptionKind StringKind

	Deprecated bool

	// DeprecationMessage is a human-readable message explaining the deprecation.
	// This is valid only when Deprecated is true.
	DeprecationMessage string

	// Computed, if set to true, specifies that data in this block can be
	// computed by the provider. Since the decoding of HCL always results in
	// block values, blocks are considered optional regardless of Computed.
	Computed bool
}

// AssertNoLegacyBehavior indicates that we are certain the schema is not relying on
// any legacy SDK behavior. We do this by checking for options which did not
// exist at the time of the legacy SDK, like structural attributes, certain
// complex types, or computed blocks.
func (b Block) AssertNoLegacyBehavior() bool {
	if b.Computed {
		return true
	}

	// check for non-legacy attribute types
	for _, attr := range b.Attributes {
		// A nested type cannot come from the SDK
		if attr.NestedType != nil {
			return true
		}
		ty := attr.Type

		// Lists and sets of objects could be generated by
		// SchemaConfigModeAttr, but some other combinations can be ruled out.

		// Tuples and objects could not be generated at all.
		if ty.IsTupleType() || ty.IsObjectType() {
			return true
		}

		// A map of objects was not possible.
		if ty.IsMapType() && ty.ElementType().IsObjectType() {
			return true
		}

		// Nested collections were not really supported, but could be generated
		// with string types (though we conservatively limit this to primitive types)
		if ty.IsCollectionType() {
			ety := ty.ElementType()
			if ety.IsCollectionType() && !ety.ElementType().IsPrimitiveType() {
				return true
			}
		}
	}

	for _, nb := range b.BlockTypes {
		if nb.Block.AssertNoLegacyBehavior() {
			return true
		}
	}

	return false
}

// Contains computed blocks will return true if the argument is computed, or any
// of the NestedBlock are computed recursively.
func (b Block) ContainsComputedBlocks() bool {
	if b.Computed {
		return true
	}

	for _, nb := range b.BlockTypes {
		if nb.ContainsComputedBlocks() {
			return true
		}
	}

	return false
}

// Attribute represents a configuration attribute, within a block.
type Attribute struct {
	// Type is a type specification that the attribute's value must conform to.
	// It conflicts with NestedType.
	Type cty.Type

	// NestedType indicates that the attribute is a NestedBlock-style object.
	// This field conflicts with Type.
	NestedType *Object

	// Description is an English-language description of the purpose and
	// usage of the attribute. A description should be concise and use only
	// one or two sentences, leaving full definition to longer-form
	// documentation defined elsewhere.
	Description     string
	DescriptionKind StringKind

	// Required, if set to true, specifies that an omitted or null value is
	// not permitted.
	Required bool

	// Optional, if set to true, specifies that an omitted or null value is
	// permitted. This field conflicts with Required.
	Optional bool

	// Computed, if set to true, specifies that the value comes from the
	// provider rather than from configuration. If combined with Optional,
	// then the config may optionally provide an overridden value.
	Computed bool

	// Sensitive, if set to true, indicates that an attribute may contain
	// sensitive information.
	//
	// At present nothing is done with this information, but callers are
	// encouraged to set it where appropriate so that it may be used in the
	// future to help Terraform mask sensitive information. (Terraform
	// currently achieves this in a limited sense via other mechanisms.)
	Sensitive bool

	Deprecated bool

	// DeprecationMessage is a human-readable message explaining the deprecation.
	// This is valid only when Deprecated is true.
	DeprecationMessage string

	// WriteOnly, if set to true, indicates that the attribute is not persisted
	// in the state.
	WriteOnly bool
}

// Object represents the embedding of a structural object inside an Attribute.
type Object struct {
	// Attributes describes the nested attributes which may appear inside the
	// Object.
	Attributes map[string]*Attribute

	// Nesting provides the nesting mode for this Object, which determines how
	// many instances of the Object are allowed, how many labels it expects, and
	// how the resulting data will be converted into a data structure.
	Nesting NestingMode
}

// NestedBlock represents the embedding of one block within another.
type NestedBlock struct {
	// Block is the description of the block that's nested.
	Block

	// Nesting provides the nesting mode for the child block, which determines
	// how many instances of the block are allowed, how many labels it expects,
	// and how the resulting data will be converted into a data structure.
	Nesting NestingMode

	// MinItems and MaxItems set, for the NestingList and NestingSet nesting
	// modes, lower and upper limits on the number of child blocks allowed
	// of the given type. If both are left at zero, no limit is applied.
	//
	// As a special case, both values can be set to 1 for NestingSingle in
	// order to indicate that a particular single block is required.
	//
	// These fields are ignored for other nesting modes and must both be left
	// at zero.
	MinItems, MaxItems int
}

// NestingMode is an enumeration of modes for nesting blocks inside other
// blocks.
type NestingMode int

// Object represents the embedding of a NestedBl

//go:generate go tool golang.org/x/tools/cmd/stringer -type=NestingMode

const (
	nestingModeInvalid NestingMode = iota

	// NestingSingle indicates that only a single instance of a given
	// block type is permitted, with no labels, and its content should be
	// provided directly as an object value.
	NestingSingle

	// NestingGroup is similar to NestingSingle in that it calls for only a
	// single instance of a given block type with no labels, but it additionally
	// guarantees that its result will never be null, even if the block is
	// absent, and instead the nested attributes and blocks will be treated as
	// absent in that case. (Any required attributes or blocks within the nested
	// block are not enforced unless the block is explicitly present in the
	// configuration, so they are all effectively optional when the block is not
	// present.)
	//
	// This is useful for the situation where a remote API has a feature that is
	// always enabled but has a group of settings related to that feature that
	// themselves have default values. By using NestingGroup instead of
	// NestingSingle in that case, generated plans will show the block as
	// present even when not present in configuration, thus allowing any default
	// values within to be displayed to the user.
	NestingGroup

	// NestingList indicates that multiple blocks of the given type are
	// permitted, with no labels, and that their corresponding objects should
	// be provided in a list.
	NestingList

	// NestingSet indicates that multiple blocks of the given type are
	// permitted, with no labels, and that their corresponding objects should
	// be provided in a set.
	NestingSet

	// NestingMap indicates that multiple blocks of the given type are
	// permitted, each with a single label, and that their corresponding
	// objects should be provided in a map whose keys are the labels.
	//
	// It's an error, therefore, to use the same label value on multiple
	// blocks.
	NestingMap
)
