// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1

package command

import (
	"errors"
	"fmt"

	"github.com/hashicorp/terraform/internal/command/arguments"
	"github.com/hashicorp/terraform/internal/command/views"
	"github.com/hashicorp/terraform/internal/modsdir"
	"github.com/hashicorp/terraform/internal/moduleref"
	"github.com/hashicorp/terraform/internal/terraform"
	"github.com/hashicorp/terraform/internal/tfdiags"
)

// ModulesCommand is a Command implementation that prints out information
// about the modules declared by the current configuration.
type ModulesCommand struct {
	Meta
	viewType arguments.ViewType
}

func (c *ModulesCommand) Help() string {
	return modulesCommandHelp
}

func (c *ModulesCommand) Synopsis() string {
	return "Show all declared modules in a working directory"
}

func (c *ModulesCommand) Run(rawArgs []string) int {
	// Parse global view arguments
	rawArgs = c.Meta.process(rawArgs)
	common, rawArgs := arguments.ParseView(rawArgs)
	c.View.Configure(common)

	// Parse command specific flags
	args, diags := arguments.ParseModules(rawArgs)
	if diags.HasErrors() {
		c.View.Diagnostics(diags)
		c.View.HelpPrompt("modules")
		return 1
	}
	c.viewType = args.ViewType

	// Set up the command's view
	view := views.NewModules(c.viewType, c.View)

	loader, err := c.initConfigLoader()
	if err != nil {
		diags = diags.Append(err)
		view.Diagnostics(diags)
		return 1
	}

	var varDiags tfdiags.Diagnostics
	c.VariableValues, varDiags = args.Vars.CollectValues(func(filename string, src []byte) {
		loader.Parser().ForceFileSource(filename, src)
	})
	diags = diags.Append(varDiags)
	if diags.HasErrors() {
		view.Diagnostics(diags)
		return 1
	}

	rootModPath, err := ModulePath([]string{})
	if err != nil {
		diags = diags.Append(err)
		view.Diagnostics(diags)
		return 1
	}

	// Read the root module path so we can then traverse the tree
	rootModEarly, earlyConfDiags := c.loadSingleModule(rootModPath)
	if rootModEarly == nil {
		diags = diags.Append(errors.New("root module not found. Please run terraform init"), earlyConfDiags)
		view.Diagnostics(diags)
		return 1
	}

	diags = diags.Append(c.resolveConstVariables(rootModPath, args.ViewType))
	if diags.HasErrors() {
		view.Diagnostics(diags)
		return 1
	}

	config, confDiags := c.loadConfig(rootModPath)
	// Here we check if there are any uninstalled dependencies
	versionDiags := terraform.CheckCoreVersionRequirements(config)
	if versionDiags.HasErrors() {
		view.Diagnostics(versionDiags)
		return 1
	}

	diags = diags.Append(earlyConfDiags)
	if earlyConfDiags.HasErrors() {
		view.Diagnostics(diags)
		return 1
	}

	diags = diags.Append(confDiags)
	if confDiags.HasErrors() {
		view.Diagnostics(diags)
		return 1
	}

	// Fetch the module manifest
	internalManifest, diags := c.internalManifest()
	if diags.HasErrors() {
		view.Diagnostics(diags)
		return 1
	}

	// Create a module reference resolver
	resolver := moduleref.NewResolver(internalManifest)

	// Crawl the Terraform config and find entries with references
	manifestWithRef := resolver.Resolve(config)

	// Render the new manifest with references
	return view.Display(*manifestWithRef)
}

// internalManifest will use the configuration loader to refresh and load the
// internal manifest.
func (c *ModulesCommand) internalManifest() (modsdir.Manifest, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics

	loader, err := c.initConfigLoader()
	if err != nil {
		diags = diags.Append(fmt.Errorf("Failed to initialize config loader: %w", err))
		return nil, diags
	}

	if err = loader.RefreshModules(); err != nil {
		diags = diags.Append(fmt.Errorf("Failed to refresh module manifest: %w", err))
		return nil, diags
	}

	return loader.ModuleManifest(), diags
}

const modulesCommandHelp = `
Usage: terraform [global options] modules [options]

  Prints out a list of all declared Terraform modules and their resolved versions
  in a Terraform working directory.

Options:

  -json               If specified, output declared Terraform modules and
                      their resolved versions in a machine-readable format.

  -var 'foo=bar'      Set a value for one of the input variables in the root
                      module of the configuration. Use this option more than
                      once to set more than one variable.

  -var-file=filename  Load variable values from the given file, in addition
                      to the default files terraform.tfvars and *.auto.tfvars.
                      Use this option more than once to include more than one
                      variables file.
`
