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

package addrs

import "github.com/opentofu/opentofu/internal/tfdiags"

// DiagnosticExtraCheckRule provides an interface for diagnostic ExtraInfo to
// retrieve an embedded CheckRule from within a tfdiags.Diagnostic.
type DiagnosticExtraCheckRule interface {
	// DiagnosticOriginatesFromCheckRule returns the CheckRule that the
	// surrounding diagnostic originated from.
	DiagnosticOriginatesFromCheckRule() CheckRule
}

// DiagnosticOriginatesFromCheckRule checks if the provided diagnostic contains
// a CheckRule as ExtraInfo and returns that CheckRule and true if it does. This
// function returns an empty CheckRule and false if the diagnostic does not
// contain a CheckRule.
func DiagnosticOriginatesFromCheckRule(diag tfdiags.Diagnostic) (CheckRule, bool) {
	maybe := tfdiags.ExtraInfo[DiagnosticExtraCheckRule](diag)
	if maybe == nil {
		return CheckRule{}, false
	}
	return maybe.DiagnosticOriginatesFromCheckRule(), true
}

// CheckRuleDiagnosticExtra is an object that can be attached to diagnostics
// that originate from check rules.
//
// It implements the DiagnosticExtraCheckRule interface for retrieving the
// concrete CheckRule that spawned the diagnostic.
//
// It also implements the tfdiags.DiagnosticExtraDoNotConsolidate interface, to
// stop diagnostics created by check blocks being consolidated.
//
// It also implements the tfdiags.DiagnosticExtraUnwrapper interface, as nested
// data blocks will attach this struct but do want to lose any extra info
// embedded in the original diagnostic.
type CheckRuleDiagnosticExtra struct {
	CheckRule CheckRule

	wrapped interface{}
}

var (
	_ DiagnosticExtraCheckRule                = (*CheckRuleDiagnosticExtra)(nil)
	_ tfdiags.DiagnosticExtraDoNotConsolidate = (*CheckRuleDiagnosticExtra)(nil)
	_ tfdiags.DiagnosticExtraUnwrapper        = (*CheckRuleDiagnosticExtra)(nil)
	_ tfdiags.DiagnosticExtraWrapper          = (*CheckRuleDiagnosticExtra)(nil)
)

func (c *CheckRuleDiagnosticExtra) UnwrapDiagnosticExtra() interface{} {
	return c.wrapped
}

func (c *CheckRuleDiagnosticExtra) WrapDiagnosticExtra(inner interface{}) {
	if c.wrapped != nil {
		// This is a logical inconsistency, the caller should know whether they
		// have already wrapped an extra or not.
		panic("Attempted to wrap a diagnostic extra into a CheckRuleDiagnosticExtra that is already wrapping a different extra. This is a bug in OpenTofu, please report it.")
	}
	c.wrapped = inner
}

func (c *CheckRuleDiagnosticExtra) DoNotConsolidateDiagnostic() bool {
	// Do not consolidate warnings from check blocks.
	return c.CheckRule.Container.CheckableKind() == CheckableCheck
}

func (c *CheckRuleDiagnosticExtra) DiagnosticOriginatesFromCheckRule() CheckRule {
	return c.CheckRule
}
