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

package command

import (
	"bytes"
	"fmt"
	"strings"

	"github.com/hashicorp/cli"
	"github.com/hashicorp/terraform/internal/command/arguments"
	"github.com/hashicorp/terraform/internal/command/views"
	"github.com/hashicorp/terraform/internal/tfdiags"
	"github.com/posener/complete"
)

type WorkspaceListCommand struct {
	Meta
	LegacyName bool
}

func (c *WorkspaceListCommand) Run(rawArgs []string) int {
	var diags tfdiags.Diagnostics

	// c.Meta.process removes global flags (-no-color, -compact-warnings) and uses them to configure the Ui and View.
	//
	// Other command implementations remove those arguments via arguments.ParseView, instead. That is only possible if views
	// are used for both human and machine output. This command still uses cli.Ui for human output, so c.Meta.process is necessary.
	rawArgs = c.Meta.process(rawArgs)

	// Parse command-specific arguments.
	args, diags := arguments.ParseWorkspaceList(rawArgs)

	// Prepare the view
	//
	// Note - here the view uses:
	// - cli.Ui for human output
	// - view.View for machine-readable output
	//
	// Note: We don't call c.View.Configure here after obtaining the view because it's already called in c.Meta.process.
	// TODO: When we migrate human output to use views fully instead of cli.Ui we would replace using c.Meta.process with arguments.ParseView.
	// arguments.ParseView returns a 'common' View that can be used as an argument in the c.View.Configure method.
	view := newWorkspaceList(args.ViewType, c.View, c.Ui, &c.Meta)

	// Warn against using `terraform env` commands
	if args.ViewType == arguments.ViewHuman {
		envCommandShowWarning(c.Ui, c.LegacyName)
	} else {
		diags = diags.Append(envCommandWarningDiag(c.LegacyName))
	}

	// Now the view is ready, process any error diagnostics from parsing arguments.
	if diags.HasErrors() {
		view.List("", nil, diags)
		return 1
	}

	// Load the backend
	configPath := c.WorkingDir.RootModuleDir()
	b, bDiags := c.backend(configPath, args.ViewType)
	diags = diags.Append(bDiags)
	if bDiags.HasErrors() {
		view.List("", nil, diags)
		return 1
	}

	// This command will not write state
	c.ignoreRemoteVersionConflict(b)

	states, wDiags := b.Workspaces()
	diags = diags.Append(wDiags)
	if wDiags.HasErrors() {
		view.List("", nil, diags)
		return 1
	}

	env, isOverridden, err := c.WorkspaceOverridden()
	if err != nil {
		diags = diags.Append(err)
		view.List("", nil, diags)
		return 1
	}

	if isOverridden {
		warn := tfdiags.Sourceless(
			tfdiags.Warning,
			envIsOverriddenNote,
			"",
		)
		diags = diags.Append(warn)
	}

	// Print:
	// 1. Diagnostics
	// 2. The list of workspaces, highlighting the current workspace
	view.List(env, states, diags)

	return 0
}

func (c *WorkspaceListCommand) AutocompleteArgs() complete.Predictor {
	return complete.PredictDirs("")
}

func (c *WorkspaceListCommand) AutocompleteFlags() complete.Flags {
	return nil
}

func (c *WorkspaceListCommand) Help() string {
	helpText := `
Usage: terraform [global options] workspace list

  List Terraform workspaces.

Options:

  -json            If specified, machine readable output will be
                   printed in JSON format.
`
	return strings.TrimSpace(helpText)
}

func (c *WorkspaceListCommand) Synopsis() string {
	return "List Workspaces"
}

type workspaceListHuman struct {
	ui   cli.Ui
	meta *Meta
}

// List is used to assemble the list of Workspaces and log it via Output
func (v *workspaceListHuman) List(selected string, list []string, diags tfdiags.Diagnostics) {
	// Print diags above output
	v.meta.showDiagnostics(diags)

	// Print list
	if len(list) > 0 {
		var out bytes.Buffer
		for _, s := range list {
			if s == selected {
				out.WriteString("* ")
			} else {
				out.WriteString("  ")
			}
			out.WriteString(s + "\n")
		}
		v.ui.Output(out.String())
	} else {
		// Warn that no states exist
		v.meta.showDiagnostics(warnNoEnvsExistDiag(selected))
	}
}

// newWorkspaceList returns a views.WorkspaceList interface.
//
// When human-readable output is migrated from cli.Ui to views.View this method should be deleted and
// replaced with using a views.NewWorkspaceList method.
func newWorkspaceList(vt arguments.ViewType, view *views.View, ui cli.Ui, meta *Meta) views.WorkspaceList {
	switch vt {
	case arguments.ViewJSON:
		return views.NewWorkspaceList(vt, view)
	case arguments.ViewHuman:
		return &workspaceListHuman{
			ui:   ui,
			meta: meta,
		}
	default:
		panic(fmt.Sprintf("unknown view type %v", vt))
	}
}
