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

package tofu

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

import (
	"log"

	"github.com/opentofu/opentofu/internal/lang/marks"
	"github.com/opentofu/opentofu/internal/tfdiags"
)

// DeprecationWarningLevel defines different levels of deprecation warnings that can be used by the user to control
// what type of warnings it wants to see
type DeprecationWarningLevel uint8

const (
	// DeprecationWarningLevelAll shows all deprecation warnings for outputs and variables, where no filtering is applied.
	DeprecationWarningLevelAll DeprecationWarningLevel = iota
	// DeprecationWarningLevelLocal shows only the deprecation warnings for the outputs and variables that are coming from
	// modules that are referenced with a relative path (aka local module).
	DeprecationWarningLevelLocal
	// DeprecationWarningLevelNone disables any deprecation warnings, filtering out any diagnostic that was generated about
	// this.
	DeprecationWarningLevelNone
)

// Used to track duplicate diagnostic entries in DeprecationDiagnosticAllowed()
type DeprecationDiagnosticAllowedSeen map[any]any

// DeprecationDiagnosticAllowed is used to determine if a diagnostic generated by a deprecation check against an
// output or a variable should be allowed to be shown to the user based on the given DeprecationWarningLevel.
// This is making use of tfdiags.Diagnostic#ExtraInfo() to figure out if the diagnostic is generated by a deprecation check.
func DeprecationDiagnosticAllowed(lvl DeprecationWarningLevel, diagnostic tfdiags.Diagnostic, seen DeprecationDiagnosticAllowedSeen) bool {
	outputCause, outputOk := marks.DiagnosticOutputDeprecationCause(diagnostic)
	variableCause, variableOk := DiagnosticVariableDeprecationCause(diagnostic)

	if !outputOk && !variableOk {
		// If it's not a deprecation warning diagnostic, always allow it to not filter out diagnostics unrelated with deprecation
		return true
	}

	// Check for dupes
	if outputOk {
		if _, outputSeen := seen[outputCause]; outputSeen {
			// Already processed
			return false
		}
		seen[outputCause] = outputCause
	}
	if variableOk {
		if _, variableSeen := seen[variableCause]; variableSeen {
			// Already processed
			return false
		}
		seen[variableCause] = variableCause
	}

	switch lvl {
	case DeprecationWarningLevelAll:
		return true
	case DeprecationWarningLevelLocal:
		switch {
		case outputOk && outputCause.IsFromRemoteModule: // do not allow deprecation warnings for outputs from remote module calls
			return false
		case variableOk && variableCause.IsFromRemoteModule: // do not allow deprecation warnings for variables from remote module calls
			return false
		}
	case DeprecationWarningLevelNone:
		return false
	}
	// unreachable
	return true
}

// ParseDeprecatedWarningLevel gets in a string and returns a DeprecationWarningLevel.
// Since these warnings are not critical to the system, this method is returning no error when the
// warn level identifier is missing a mapping. Instead, it falls back on returning the level that
// will write all the deprecation warnings.
func ParseDeprecatedWarningLevel(s string) DeprecationWarningLevel {
	switch s {
	// Adding also the empty string just to make it clear that empty string will result in DeprecationWarningLevelAll. Useful also for skipping the warn log in the default branch.
	case "all", "":
		return DeprecationWarningLevelAll
	case "local":
		return DeprecationWarningLevelLocal
	case "none":
		return DeprecationWarningLevelNone
	default:
		log.Printf(
			"[WARN] ParseDeprecatedWarningLevel: returning %s deprecation warn level since the given value is unknown: %s",
			DeprecationWarningLevelAll,
			s,
		)
		return DeprecationWarningLevelAll
	}
}
