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

package globalref

import (
	"fmt"

	"github.com/opentofu/opentofu/internal/addrs"
	"github.com/opentofu/opentofu/internal/configs"
	"github.com/opentofu/opentofu/internal/providers"
)

// Analyzer is the main component of this package, serving as a container for
// various state that the analysis algorithms depend on either for their core
// functionality or for producing results more quickly.
//
// Global reference analysis is currently intended only for "best effort"
// use-cases related to giving hints to the user or tailoring UI output.
// Avoid using it for anything that would cause changes to the analyzer being
// considered a breaking change under the v1 compatibility promises, because
// we expect to continue to refine and evolve these rules over time in ways
// that may cause us to detect either more or fewer references than today.
// Typically we will conservatively return more references than would be
// necessary dynamically, but that isn't guaranteed for all situations.
//
// In particular, we currently typically don't distinguish between multiple
// instances of the same module, and so we overgeneralize references from
// one instance of a module as references from the same location in all
// instances of that module. We may make this more precise in future, which
// would then remove various detected references from the analysis results.
//
// Each Analyzer works with a particular configs.Config object which it assumes
// represents the root module of a configuration. Config objects are typically
// immutable by convention anyway, but it's particularly important not to
// modify a configuration while it's attached to a live Analyzer, because
// the Analyzer contains caches derived from data in the configuration tree.
type Analyzer struct {
	cfg             *configs.Config
	providerSchemas map[addrs.Provider]providers.ProviderSchema
}

// NewAnalyzer constructs a new analyzer bound to the given configuration and
// provider schemas.
//
// The given object must represent a root module, or this function will panic.
//
// The given provider schemas must cover at least all of the providers used
// in the given configuration. If not then analysis results will be silently
// incomplete for any decision that requires checking schema.
func NewAnalyzer(cfg *configs.Config, providerSchemas map[addrs.Provider]providers.ProviderSchema) *Analyzer {
	if !cfg.Path.IsRoot() {
		panic(fmt.Sprintf("constructing an Analyzer with non-root module %s", cfg.Path))
	}

	ret := &Analyzer{
		cfg:             cfg,
		providerSchemas: providerSchemas,
	}
	return ret
}

// ModuleConfig retrieves a module configuration from the configuration the
// analyzer belongs to, or nil if there is no module with the given address.
func (a *Analyzer) ModuleConfig(addr addrs.ModuleInstance) *configs.Module {
	modCfg := a.cfg.DescendentForInstance(addr)
	if modCfg == nil {
		return nil
	}
	return modCfg.Module
}
