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

package tfdiags

import (
	"github.com/hashicorp/hcl/v2"
)

// overriddenDiagnostic implements the Diagnostic interface by wrapping another
// Diagnostic while overriding the severity of the original Diagnostic.
type overriddenDiagnostic struct {
	original Diagnostic
	severity Severity
	extra    interface{}
}

var _ Diagnostic = overriddenDiagnostic{}
var _ contextualFromConfigBody = overriddenDiagnostic{}

// OverrideAll accepts a set of Diagnostics and wraps them with a new severity
// and, optionally, a new ExtraInfo.
func OverrideAll(originals Diagnostics, severity Severity, createExtra func() DiagnosticExtraWrapper) Diagnostics {
	var diags Diagnostics
	for _, diag := range originals {
		diags = diags.Append(Override(diag, severity, createExtra))
	}
	return diags
}

// Override matches OverrideAll except it operates over a single Diagnostic
// rather than multiple Diagnostics.
func Override(original Diagnostic, severity Severity, createExtra func() DiagnosticExtraWrapper) Diagnostic {
	extra := original.ExtraInfo()
	if createExtra != nil {
		nw := createExtra()
		nw.WrapDiagnosticExtra(extra)
		extra = nw
	}

	return overriddenDiagnostic{
		original: original,
		severity: severity,
		extra:    extra,
	}
}

// UndoOverride will return the original diagnostic that was overridden within
// the OverrideAll function.
//
// If the provided Diagnostic was never overridden then it is simply returned
// unchanged.
func UndoOverride(diag Diagnostic) Diagnostic {
	if override, ok := diag.(overriddenDiagnostic); ok {
		return override.original
	}

	// Then it wasn't overridden, so we'll just return the diag unchanged.
	return diag
}

func (o overriddenDiagnostic) Severity() Severity {
	return o.severity
}

func (o overriddenDiagnostic) Description() Description {
	return o.original.Description()
}

func (o overriddenDiagnostic) Source() Source {
	return o.original.Source()
}

func (o overriddenDiagnostic) FromExpr() *FromExpr {
	return o.original.FromExpr()
}

func (o overriddenDiagnostic) ExtraInfo() interface{} {
	return o.extra
}

// ElaborateFromConfigBody implements contextualFromConfigBody.
//
// If the original diagnostic also implements contextualFromConfigBody then this
// delegates to its own ElaborateFromConfigBody implementation and then re-wraps
// the result with the same overrides.
//
// Otherwise, the receiver is returned unchanged.
func (o overriddenDiagnostic) ElaborateFromConfigBody(body hcl.Body, addr string) Diagnostic {
	innerContextual, ok := o.original.(contextualFromConfigBody)
	if !ok {
		return o // inner diagnostic is not contextual, so nothing to do
	}

	newOriginal := innerContextual.ElaborateFromConfigBody(body, addr)
	return overriddenDiagnostic{
		original: newOriginal,
		severity: o.severity,
		extra:    o.extra,
	}
}
