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

package structured

import (
	"github.com/opentofu/opentofu/internal/command/jsonformat/computed"
	"github.com/opentofu/opentofu/internal/plans"
)

type ProcessSensitiveInner func(change Change) computed.Diff
type CreateSensitiveDiff func(inner computed.Diff, beforeSensitive, afterSensitive bool, action plans.Action) computed.Diff

func (change Change) IsBeforeSensitive() bool {
	if sensitive, ok := change.BeforeSensitive.(bool); ok {
		return sensitive
	}
	return false
}

func (change Change) IsAfterSensitive() bool {
	if sensitive, ok := change.AfterSensitive.(bool); ok {
		return sensitive
	}
	return false
}

// CheckForSensitive is a helper function that handles all common functionality
// for processing a sensitive value.
//
// It returns the computed sensitive diff and true if this value was sensitive
// and needs to be rendered as such, otherwise it returns the second return
// value as false and the first value can be discarded.
//
// The actual processing of sensitive values happens within the
// ProcessSensitiveInner and CreateSensitiveDiff functions. Callers should
// implement these functions as appropriate when using this function.
//
// The ProcessSensitiveInner function should simply return a computed.Diff for
// the provided Change. The provided Change will be the same as the original
// change but with the sensitive metadata removed. The new inner diff is then
// passed into the actual CreateSensitiveDiff function which should return the
// actual sensitive diff.
//
// We include the inner change into the sensitive diff as a way to let the
// sensitive renderer have as much information as possible, while still letting
// it do the actual rendering.
func (change Change) CheckForSensitive(processInner ProcessSensitiveInner, createDiff CreateSensitiveDiff) (computed.Diff, bool) {
	beforeSensitive := change.IsBeforeSensitive()
	afterSensitive := change.IsAfterSensitive()

	if !beforeSensitive && !afterSensitive {
		return computed.Diff{}, false
	}

	// We are still going to give the change the contents of the actual change.
	// So we create a new Change with everything matching the current value,
	// except for the sensitivity.
	//
	// The change can choose what to do with this information, in most cases
	// it will just be ignored in favour of printing `(sensitive value)`.

	value := Change{
		BeforeExplicit:     change.BeforeExplicit,
		AfterExplicit:      change.AfterExplicit,
		Before:             change.Before,
		After:              change.After,
		Unknown:            change.Unknown,
		BeforeSensitive:    false,
		AfterSensitive:     false,
		ReplacePaths:       change.ReplacePaths,
		RelevantAttributes: change.RelevantAttributes,
	}

	inner := processInner(value)

	action := inner.Action
	sensitiveStatusChanged := beforeSensitive != afterSensitive

	// nullNoOp is a stronger NoOp, where not only is there no change happening
	// but the before and after values are not explicitly set and are both
	// null. This will override even the sensitive state changing.
	nullNoOp := change.Before == nil && !change.BeforeExplicit && change.After == nil && !change.AfterExplicit

	if action == plans.NoOp && sensitiveStatusChanged && !nullNoOp {
		// Let's override this, since it means the sensitive status has changed
		// rather than the actual content of the value.
		action = plans.Update
	}

	return createDiff(inner, beforeSensitive, afterSensitive, action), true
}
