// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package dynblock

import (
	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/hcldec"
	"github.com/zclconf/go-cty/cty"
)

// unknownBody is a funny body that just reports everything inside it as
// unknown. It uses a given other body as a sort of template for what attributes
// and blocks are inside -- including source location information -- but
// subsitutes unknown values of unknown type for all attributes.
//
// This rather odd process is used to handle expansion of dynamic blocks whose
// for_each expression is unknown. Since a block cannot itself be unknown,
// we instead arrange for everything _inside_ the block to be unknown instead,
// to give the best possible approximation.
type unknownBody struct {
	template   hcl.Body
	valueMarks cty.ValueMarks
}

var _ hcl.Body = unknownBody{}

// hcldec.UnknownBody impl
func (b unknownBody) Unknown() bool {
	return true
}

// hcldec.MarkedBody impl
func (b unknownBody) BodyValueMarks() cty.ValueMarks {
	// We'll pass through to our template if it is a MarkedBody
	if t, ok := b.template.(hcldec.MarkedBody); ok {
		return t.BodyValueMarks()
	}
	return nil
}

func (b unknownBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
	content, diags := b.template.Content(schema)
	content = b.fixupContent(content)

	// We're intentionally preserving the diagnostics reported from the
	// inner body so that we can still report where the template body doesn't
	// match the requested schema.
	return content, diags
}

func (b unknownBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
	content, remain, diags := b.template.PartialContent(schema)
	content = b.fixupContent(content)
	remain = unknownBody{template: remain, valueMarks: b.valueMarks} // remaining content must also be wrapped

	// We're intentionally preserving the diagnostics reported from the
	// inner body so that we can still report where the template body doesn't
	// match the requested schema.
	return content, remain, diags
}

func (b unknownBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
	attrs, diags := b.template.JustAttributes()
	attrs = b.fixupAttrs(attrs)

	// We're intentionally preserving the diagnostics reported from the
	// inner body so that we can still report where the template body doesn't
	// match the requested schema.
	return attrs, diags
}

func (b unknownBody) MissingItemRange() hcl.Range {
	return b.template.MissingItemRange()
}

func (b unknownBody) fixupContent(got *hcl.BodyContent) *hcl.BodyContent {
	ret := &hcl.BodyContent{}
	ret.Attributes = b.fixupAttrs(got.Attributes)
	if len(got.Blocks) > 0 {
		ret.Blocks = make(hcl.Blocks, 0, len(got.Blocks))
		for _, gotBlock := range got.Blocks {
			new := *gotBlock                                                          // shallow copy
			new.Body = unknownBody{template: gotBlock.Body, valueMarks: b.valueMarks} // nested content must also be marked unknown
			ret.Blocks = append(ret.Blocks, &new)
		}
	}

	return ret
}

func (b unknownBody) fixupAttrs(got hcl.Attributes) hcl.Attributes {
	if len(got) == 0 {
		return nil
	}
	ret := make(hcl.Attributes, len(got))
	for name, gotAttr := range got {
		new := *gotAttr // shallow copy
		new.Expr = hcl.StaticExpr(cty.DynamicVal.WithMarks(b.valueMarks), gotAttr.Expr.Range())
		ret[name] = &new
	}
	return ret
}
