// Copyright 2022 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:build go1.20
// +build go1.20

package settings

import (
	"context"
	"fmt"

	"mvdan.cc/gofumpt/format"
)

const GofumptSupported = true

// GofumptFormat allows the gopls module to wire in a call to
// gofumpt/format.Source. langVersion and modulePath are used for some
// Gofumpt formatting rules -- see the Gofumpt documentation for details.
var GofumptFormat = func(ctx context.Context, langVersion, modulePath string, src []byte) ([]byte, error) {
	fixedVersion, err := fixLangVersion(langVersion)
	if err != nil {
		return nil, err
	}
	return format.Source(src, format.Options{
		LangVersion: fixedVersion,
		ModulePath:  modulePath,
	})
}

// fixLangVersion function cleans the input so that gofumpt doesn't panic. It is
// rather permissive, and accepts version strings that aren't technically valid
// in a go.mod file.
//
// More specifically, it looks for an optional 'v' followed by 1-3
// '.'-separated numbers. The resulting string is stripped of any suffix beyond
// this expected version number pattern.
//
// See also golang/go#61692: gofumpt does not accept the new language versions
// appearing in go.mod files (e.g. go1.21rc3).
func fixLangVersion(input string) (string, error) {
	bad := func() (string, error) {
		return "", fmt.Errorf("invalid language version syntax %q", input)
	}
	if input == "" {
		return input, nil
	}
	i := 0
	if input[0] == 'v' { // be flexible about 'v'
		i++
	}
	// takeDigits consumes ascii numerals 0-9 and reports if at least one was
	// consumed.
	takeDigits := func() bool {
		found := false
		for ; i < len(input) && '0' <= input[i] && input[i] <= '9'; i++ {
			found = true
		}
		return found
	}
	if !takeDigits() { // versions must start with at least one number
		return bad()
	}

	// Accept optional minor and patch versions.
	for n := 0; n < 2; n++ {
		if i < len(input) && input[i] == '.' {
			// Look for minor/patch version.
			i++
			if !takeDigits() {
				i--
				break
			}
		}
	}
	// Accept any suffix.
	return input[:i], nil
}
