// 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:build ignore

// copytermlist.go copies the term list algorithm from GOROOT/src/go/types.
//
// Usage:
//
//	Requires running in GOPATH mode (GO111MODULE=off):to avoid adding
//      the dependency on golang.org/x/tools in go.mod.
//	  1. Check out golang.org/x/tools at $GOPATH/src/golang.org/x/tools.
//	  2. Run: GO111MODULE=off go run copytermlist.go

package main

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"

	"golang.org/x/tools/go/ast/astutil"
)

func main() {
	if err := doCopy(); err != nil {
		fmt.Fprintf(os.Stderr, "error copying from go/types: %v", err)
		os.Exit(1)
	}
}

func doCopy() error {
	dir := filepath.Join(runtime.GOROOT(), "src", "go", "types")
	for _, name := range []string{"typeterm.go", "termlist.go"} {
		path := filepath.Join(dir, name)
		fset := token.NewFileSet()
		file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
		if err != nil {
			return err
		}
		file.Name.Name = "typeparams"
		file.Doc = &ast.CommentGroup{List: []*ast.Comment{&ast.Comment{Text: "DO NOT MODIFY"}}}
		var needImport bool
		selectorType := reflect.TypeOf((*ast.SelectorExpr)(nil))
		astutil.Apply(file, func(c *astutil.Cursor) bool {
			if id, _ := c.Node().(*ast.Ident); id != nil {
				// Check if this ident should be qualified with types. For simplicity,
				// assume the copied files do not themselves contain any exported
				// symbols.

				// As a simple heuristic, just verify that the ident may be replaced by
				// a selector.
				if !token.IsExported(id.Name) {
					return false
				}
				v := reflect.TypeOf(c.Parent()).Elem() // ast nodes are all pointers
				field, ok := v.FieldByName(c.Name())
				if !ok {
					panic("missing field")
				}
				t := field.Type
				if c.Index() > 0 { // => t is a slice
					t = t.Elem()
				}
				if !selectorType.AssignableTo(t) {
					return false
				}
				needImport = true
				c.Replace(&ast.SelectorExpr{
					X:   &ast.Ident{NamePos: id.Pos(), Name: "types"},
					Sel: &ast.Ident{NamePos: id.Pos(), Name: id.Name},
				})
			}
			return true
		}, nil)
		if needImport {
			astutil.AddImport(fset, file, "go/types")
		}

		var b bytes.Buffer
		if err := format.Node(&b, fset, file); err != nil {
			return err
		}

		// Rewrite code gen byline and source info.
		var res bytes.Buffer

		var skippingHeader = true
		var wroteFirstContentLine bool
		for line := range strings.SplitSeq(b.String(), "\n") {
			if skippingHeader {
				trimmed := strings.TrimSpace(line)
				if strings.Contains(trimmed, "DO NOT EDIT") && strings.Contains(trimmed, "Code generated") {
					res.WriteString("// Code generated by copytermlist.go DO NOT EDIT.\n")
					continue
				}
				if strings.HasPrefix(trimmed, "// Source:") {
					res.WriteString("// Source: GOROOT/src/go/types/" + name + "\n\n")
					continue
				}
				if trimmed == "" {
					continue // Skip leading blank lines until we hit the first actual comment/code
				}
				skippingHeader = false
			}

			// Handle line endings to avoid duplicate trailing newlines at the end of the file
			if !wroteFirstContentLine {
				res.WriteString(line)
				wroteFirstContentLine = true
			} else {
				res.WriteString("\n")
				res.WriteString(line)
			}
		}

		if err := os.WriteFile(name, res.Bytes(), 0644); err != nil {
			return err
		}
	}
	return nil
}
