// Copyright 2020-2025 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package bufstyle defines lint analyzers that help enforce Buf's Go code standards.
package bufstyle

import (
	"go/token"
	"path/filepath"
	"strings"

	"golang.org/x/tools/go/analysis"
)

// we don't store this as a global because we modify these in the analyzerProvider.
func newAnalyzers() []*analysis.Analyzer {
	return []*analysis.Analyzer{
		{
			Name: "PACKAGE_FILENAME",
			Doc:  "Verifies that every package has a file with the same name as the package.",
			Run: func(pass *analysis.Pass) (any, error) {
				if len(pass.Files) == 0 {
					// Nothing to do. We can't report the error anywhere because
					// this package doesn't have any files.
					return nil, nil
				}
				packageName := pass.Pkg.Name()
				if strings.HasSuffix(packageName, "_test") {
					// Ignore test packages.
					return nil, nil
				}
				var found bool
				pass.Fset.Iterate(
					func(file *token.File) bool {
						filename := filepath.Base(file.Name())
						if strings.TrimSuffix(filename, ".go") == packageName {
							found = true
							return false
						}
						return true
					},
				)
				if !found {
					// The package is guaranteed to have at least one
					// file with a package declaration, so we report the failure there.
					// We checked that len(pass.Files) > 0 above.
					pass.Reportf(pass.Files[0].Package, "Package %q does not have a %s.go", packageName, packageName)
				}
				return nil, nil
			},
		},
		{
			Name: "NO_SYNC_POOL",
			Doc:  "Verifies that sync.Pool is not used.",
			Run: func(pass *analysis.Pass) (any, error) {
				if typesInfo := pass.TypesInfo; typesInfo != nil {
					for expr, typeAndValue := range pass.TypesInfo.Types {
						if t := typeAndValue.Type; t != nil {
							if t.String() == "sync.Pool" {
								pass.Reportf(expr.Pos(), "sync.Pool cannot be used")
							}
						}
					}
				}
				return nil, nil
			},
		},
		{
			Name: "BEHAVIOUR",
			Doc:  "Verifies that the word \"behaviour\" is not used in any comment.",
			Run: func(pass *analysis.Pass) (any, error) {
				for _, file := range pass.Files {
					for _, commentGroup := range file.Comments {
						for _, comment := range commentGroup.List {
							if strings.Contains(strings.ToLower(comment.Text), "behaviour") {
								pass.Reportf(comment.Slash, `It is spelled "behavior" not "behaviour"`)
							}
						}
					}
				}
				return nil, nil
			},
		},
		{
			Name: "FILEPATH_CASING",
			Doc:  "Verifies filePath or FilePath is used, not filepath or Filepath.",
			Run: func(pass *analysis.Pass) (any, error) {
				if typesInfo := pass.TypesInfo; typesInfo != nil {
					for _, object := range pass.TypesInfo.Defs {
						if object != nil {
							if strings.Contains(object.Name(), "Filepath") {
								pass.Reportf(object.Pos(), `Use "FilePath" instead of "Filepath" in name %q`, object.Name())
							}
							if strings.Contains(object.Name(), "filepath") {
								pass.Reportf(object.Pos(), `Use "filePath" instead of "filepath" in name %q`, object.Name())
							}
						}
					}
				}
				return nil, nil
			},
		},
		//{
		//Name: "FILENAME_CASING",
		//Doc:  "Verifies fileName or FileName is used, not filename or Filename.",
		//Run: func(pass *analysis.Pass) (interface{}, error) {
		//if typesInfo := pass.TypesInfo; typesInfo != nil {
		//for _, object := range pass.TypesInfo.Defs {
		//if object != nil {
		//if strings.Contains(object.Name(), "Filename") {
		//pass.Reportf(object.Pos(), `Use "FileName" instead of "Filename" in name %q`, object.Name())
		//}
		//if strings.Contains(object.Name(), "filename") {
		//pass.Reportf(object.Pos(), `Use "fileName" instead of "filename" in name %q`, object.Name())
		//}
		//}
		//}
		//}
		//return nil, nil
		//},
		//},
		//{
		//Name: "DIRPATH_CASING",
		//Doc:  "Verifies dirPath or DirPath is used, not dirpath or Dirpath.",
		//Run: func(pass *analysis.Pass) (interface{}, error) {
		//if typesInfo := pass.TypesInfo; typesInfo != nil {
		//for _, object := range pass.TypesInfo.Defs {
		//if object != nil {
		//if strings.Contains(object.Name(), "Dirpath") {
		//pass.Reportf(object.Pos(), `Use "DirPath" instead of "Dirpath" in name %q`, object.Name())
		//}
		//if strings.Contains(object.Name(), "dirpath") {
		//pass.Reportf(object.Pos(), `Use "dirPath" instead of "dirpath" in name %q`, object.Name())
		//}
		//}
		//}
		//}
		//return nil, nil
		//},
		//},
	}
}
