// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:generate go run -tags=generate generate.go

// Package command defines the interface provided by gopls for the
// workspace/executeCommand LSP request.
//
// This interface is fully specified by the Interface type, provided it
// conforms to the restrictions outlined in its doc string.
//
// Bindings for server-side command dispatch and client-side serialization are
// also provided by this package, via code generation.
package command

import (
	"context"

	"golang.org/x/tools/gopls/internal/govulncheck"
	"golang.org/x/tools/gopls/internal/lsp/protocol"
)

// Interface defines the interface gopls exposes for the
// workspace/executeCommand request.
//
// This interface is used to generate marshaling/unmarshaling code, dispatch,
// and documentation, and so has some additional restrictions:
//  1. All method arguments must be JSON serializable.
//  2. Methods must return either error or (T, error), where T is a
//     JSON serializable type.
//  3. The first line of the doc string is special. Everything after the colon
//     is considered the command 'Title'.
//     TODO(rFindley): reconsider this -- Title may be unnecessary.
type Interface interface {
	// ApplyFix: Apply a fix
	//
	// Applies a fix to a region of source code.
	ApplyFix(context.Context, ApplyFixArgs) error
	// Test: Run test(s) (legacy)
	//
	// Runs `go test` for a specific set of test or benchmark functions.
	Test(context.Context, protocol.DocumentURI, []string, []string) error

	// TODO: deprecate Test in favor of RunTests below.

	// Test: Run test(s)
	//
	// Runs `go test` for a specific set of test or benchmark functions.
	RunTests(context.Context, RunTestsArgs) error

	// Generate: Run go generate
	//
	// Runs `go generate` for a given directory.
	Generate(context.Context, GenerateArgs) error

	// RegenerateCgo: Regenerate cgo
	//
	// Regenerates cgo definitions.
	RegenerateCgo(context.Context, URIArg) error

	// Tidy: Run go mod tidy
	//
	// Runs `go mod tidy` for a module.
	Tidy(context.Context, URIArgs) error

	// Vendor: Run go mod vendor
	//
	// Runs `go mod vendor` for a module.
	Vendor(context.Context, URIArg) error

	// EditGoDirective: Run go mod edit -go=version
	//
	// Runs `go mod edit -go=version` for a module.
	EditGoDirective(context.Context, EditGoDirectiveArgs) error

	// UpdateGoSum: Update go.sum
	//
	// Updates the go.sum file for a module.
	UpdateGoSum(context.Context, URIArgs) error

	// CheckUpgrades: Check for upgrades
	//
	// Checks for module upgrades.
	CheckUpgrades(context.Context, CheckUpgradesArgs) error

	// AddDependency: Add a dependency
	//
	// Adds a dependency to the go.mod file for a module.
	AddDependency(context.Context, DependencyArgs) error

	// UpgradeDependency: Upgrade a dependency
	//
	// Upgrades a dependency in the go.mod file for a module.
	UpgradeDependency(context.Context, DependencyArgs) error

	// RemoveDependency: Remove a dependency
	//
	// Removes a dependency from the go.mod file of a module.
	RemoveDependency(context.Context, RemoveDependencyArgs) error

	// ResetGoModDiagnostics: Reset go.mod diagnostics
	//
	// Reset diagnostics in the go.mod file of a module.
	ResetGoModDiagnostics(context.Context, ResetGoModDiagnosticsArgs) error

	// GoGetPackage: go get a package
	//
	// Runs `go get` to fetch a package.
	GoGetPackage(context.Context, GoGetPackageArgs) error

	// GCDetails: Toggle gc_details
	//
	// Toggle the calculation of gc annotations.
	GCDetails(context.Context, protocol.DocumentURI) error

	// TODO: deprecate GCDetails in favor of ToggleGCDetails below.

	// ToggleGCDetails: Toggle gc_details
	//
	// Toggle the calculation of gc annotations.
	ToggleGCDetails(context.Context, URIArg) error

	// ListKnownPackages: List known packages
	//
	// Retrieve a list of packages that are importable from the given URI.
	ListKnownPackages(context.Context, URIArg) (ListKnownPackagesResult, error)

	// ListImports: List imports of a file and its package
	//
	// Retrieve a list of imports in the given Go file, and the package it
	// belongs to.
	ListImports(context.Context, URIArg) (ListImportsResult, error)

	// AddImport: Add an import
	//
	// Ask the server to add an import path to a given Go file.  The method will
	// call applyEdit on the client so that clients don't have to apply the edit
	// themselves.
	AddImport(context.Context, AddImportArgs) error

	// StartDebugging: Start the gopls debug server
	//
	// Start the gopls debug server if it isn't running, and return the debug
	// address.
	StartDebugging(context.Context, DebuggingArgs) (DebuggingResult, error)

	// RunGovulncheck: Run govulncheck.
	//
	// Run vulnerability check (`govulncheck`).
	RunGovulncheck(context.Context, VulncheckArgs) (RunVulncheckResult, error)

	// FetchVulncheckResult: Get known vulncheck result
	//
	// Fetch the result of latest vulnerability check (`govulncheck`).
	FetchVulncheckResult(context.Context, URIArg) (map[protocol.DocumentURI]*govulncheck.Result, error)

	// MemStats: fetch memory statistics
	//
	// Call runtime.GC multiple times and return memory statistics as reported by
	// runtime.MemStats.
	//
	// This command is used for benchmarking, and may change in the future.
	MemStats(context.Context) (MemStatsResult, error)

	// WorkspaceStats: fetch workspace statistics
	//
	// Query statistics about workspace builds, modules, packages, and files.
	//
	// This command is intended for internal use only, by the gopls stats
	// command.
	WorkspaceStats(context.Context) (WorkspaceStatsResult, error)

	// RunGoWorkCommand: run `go work [args...]`, and apply the resulting go.work
	// edits to the current go.work file.
	RunGoWorkCommand(context.Context, RunGoWorkArgs) error
}

type RunTestsArgs struct {
	// The test file containing the tests to run.
	URI protocol.DocumentURI

	// Specific test names to run, e.g. TestFoo.
	Tests []string

	// Specific benchmarks to run, e.g. BenchmarkFoo.
	Benchmarks []string
}

type GenerateArgs struct {
	// URI for the directory to generate.
	Dir protocol.DocumentURI

	// Whether to generate recursively (go generate ./...)
	Recursive bool
}

// TODO(rFindley): document the rest of these once the docgen is fleshed out.

type ApplyFixArgs struct {
	// The fix to apply.
	Fix string
	// The file URI for the document to fix.
	URI protocol.DocumentURI
	// The document range to scan for fixes.
	Range protocol.Range
}

type URIArg struct {
	// The file URI.
	URI protocol.DocumentURI
}

type URIArgs struct {
	// The file URIs.
	URIs []protocol.DocumentURI
}

type CheckUpgradesArgs struct {
	// The go.mod file URI.
	URI protocol.DocumentURI
	// The modules to check.
	Modules []string
}

type DependencyArgs struct {
	// The go.mod file URI.
	URI protocol.DocumentURI
	// Additional args to pass to the go command.
	GoCmdArgs []string
	// Whether to add a require directive.
	AddRequire bool
}

type RemoveDependencyArgs struct {
	// The go.mod file URI.
	URI protocol.DocumentURI
	// The module path to remove.
	ModulePath string
	// If the module is tidied apart from the one unused diagnostic, we can
	// run `go get module@none`, and then run `go mod tidy`. Otherwise, we
	// must make textual edits.
	OnlyDiagnostic bool
}

type EditGoDirectiveArgs struct {
	// Any document URI within the relevant module.
	URI protocol.DocumentURI
	// The version to pass to `go mod edit -go`.
	Version string
}

type GoGetPackageArgs struct {
	// Any document URI within the relevant module.
	URI protocol.DocumentURI
	// The package to go get.
	Pkg        string
	AddRequire bool
}

type AddImportArgs struct {
	// ImportPath is the target import path that should
	// be added to the URI file
	ImportPath string
	// URI is the file that the ImportPath should be
	// added to
	URI protocol.DocumentURI
}

type ListKnownPackagesResult struct {
	// Packages is a list of packages relative
	// to the URIArg passed by the command request.
	// In other words, it omits paths that are already
	// imported or cannot be imported due to compiler
	// restrictions.
	Packages []string
}

type ListImportsResult struct {
	// Imports is a list of imports in the requested file.
	Imports []FileImport

	// PackageImports is a list of all imports in the requested file's package.
	PackageImports []PackageImport
}

type FileImport struct {
	// Path is the import path of the import.
	Path string
	// Name is the name of the import, e.g. `foo` in `import foo "strings"`.
	Name string
}

type PackageImport struct {
	// Path is the import path of the import.
	Path string
}

type DebuggingArgs struct {
	// Optional: the address (including port) for the debug server to listen on.
	// If not provided, the debug server will bind to "localhost:0", and the
	// full debug URL will be contained in the result.
	//
	// If there is more than one gopls instance along the serving path (i.e. you
	// are using a daemon), each gopls instance will attempt to start debugging.
	// If Addr specifies a port, only the daemon will be able to bind to that
	// port, and each intermediate gopls instance will fail to start debugging.
	// For this reason it is recommended not to specify a port (or equivalently,
	// to specify ":0").
	//
	// If the server was already debugging this field has no effect, and the
	// result will contain the previously configured debug URL(s).
	Addr string
}

type DebuggingResult struct {
	// The URLs to use to access the debug servers, for all gopls instances in
	// the serving path. For the common case of a single gopls instance (i.e. no
	// daemon), this will be exactly one address.
	//
	// In the case of one or more gopls instances forwarding the LSP to a daemon,
	// URLs will contain debug addresses for each server in the serving path, in
	// serving order. The daemon debug address will be the last entry in the
	// slice. If any intermediate gopls instance fails to start debugging, no
	// error will be returned but the debug URL for that server in the URLs slice
	// will be empty.
	URLs []string
}

type ResetGoModDiagnosticsArgs struct {
	URIArg

	// Optional: source of the diagnostics to reset.
	// If not set, all resettable go.mod diagnostics will be cleared.
	DiagnosticSource string
}

type VulncheckArgs struct {
	// Any document in the directory from which govulncheck will run.
	URI protocol.DocumentURI

	// Package pattern. E.g. "", ".", "./...".
	Pattern string

	// TODO: -tests
}

// RunVulncheckResult holds the result of asynchronously starting the vulncheck
// command.
type RunVulncheckResult struct {
	// Token holds the progress token for LSP workDone reporting of the vulncheck
	// invocation.
	Token protocol.ProgressToken
}

type VulncheckResult struct {
	Vuln []Vuln

	// TODO: Text string format output?
}

// CallStack models a trace of function calls starting
// with a client function or method and ending with a
// call to a vulnerable symbol.
type CallStack []StackEntry

// StackEntry models an element of a call stack.
type StackEntry struct {
	// See golang.org/x/exp/vulncheck.StackEntry.

	// User-friendly representation of function/method names.
	// e.g. package.funcName, package.(recvType).methodName, ...
	Name string
	URI  protocol.DocumentURI
	Pos  protocol.Position // Start position. (0-based. Column is always 0)
}

// Vuln models an osv.Entry and representative call stacks.
// TODO: deprecate
type Vuln struct {
	// ID is the vulnerability ID (osv.Entry.ID).
	// https://ossf.github.io/osv-schema/#id-modified-fields
	ID string
	// Details is the description of the vulnerability (osv.Entry.Details).
	// https://ossf.github.io/osv-schema/#summary-details-fields
	Details string `json:",omitempty"`
	// Aliases are alternative IDs of the vulnerability.
	// https://ossf.github.io/osv-schema/#aliases-field
	Aliases []string `json:",omitempty"`

	// Symbol is the name of the detected vulnerable function or method.
	// Can be empty if the vulnerability exists in required modules, but no vulnerable symbols are used.
	Symbol string `json:",omitempty"`
	// PkgPath is the package path of the detected Symbol.
	// Can be empty if the vulnerability exists in required modules, but no vulnerable packages are used.
	PkgPath string `json:",omitempty"`
	// ModPath is the module path corresponding to PkgPath.
	// TODO: how do we specify standard library's vulnerability?
	ModPath string `json:",omitempty"`

	// URL is the URL for more info about the information.
	// Either the database specific URL or the one of the URLs
	// included in osv.Entry.References.
	URL string `json:",omitempty"`

	// Current is the current module version.
	CurrentVersion string `json:",omitempty"`

	// Fixed is the minimum module version that contains the fix.
	FixedVersion string `json:",omitempty"`

	// Example call stacks.
	CallStacks []CallStack `json:",omitempty"`

	// Short description of each call stack in CallStacks.
	CallStackSummaries []string `json:",omitempty"`

	// TODO: import graph & module graph.
}

// MemStatsResult holds selected fields from runtime.MemStats.
type MemStatsResult struct {
	HeapAlloc  uint64
	HeapInUse  uint64
	TotalAlloc uint64
}

// WorkspaceStatsResult returns information about the size and shape of the
// workspace.
type WorkspaceStatsResult struct {
	Files FileStats   // file stats for the cache
	Views []ViewStats // stats for each view in the session
}

// FileStats holds information about a set of files.
type FileStats struct {
	Total   int // total number of files
	Largest int // number of bytes in the largest file
	Errs    int // number of files that could not be read
}

// ViewStats holds information about a single View in the session.
type ViewStats struct {
	GoCommandVersion  string       // version of the Go command resolved for this view
	AllPackages       PackageStats // package info for all packages (incl. dependencies)
	WorkspacePackages PackageStats // package info for workspace packages
	Diagnostics       int          // total number of diagnostics in the workspace
}

// PackageStats holds information about a collection of packages.
type PackageStats struct {
	Packages        int // total number of packages
	LargestPackage  int // number of files in the largest package
	CompiledGoFiles int // total number of compiled Go files across all packages
	Modules         int // total number of unique modules
}

type RunGoWorkArgs struct {
	ViewID    string   // ID of the view to run the command from
	InitFirst bool     // Whether to run `go work init` first
	Args      []string // Args to pass to `go work`
}
