// # Build from project root:
// go build -o tools/parse_go_ast tools/parse_go_ast.go

package main

import (
	"encoding/json"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"strings"
)

type GoDecl struct {
	Name       string   `json:"name"`
	Type       string   `json:"type"`               // "func", "method", "type", "struct", "interface", "var", "package"
	Receiver   string   `json:"receiver,omitempty"` // e.g., "*User"
	FullName   string   `json:"full_name,omitempty"`
	Fields     []string `json:"fields,omitempty"`  // for structs
	Methods    []string `json:"methods,omitempty"` // for interfaces
	DocComment string   `json:"doc_comment,omitempty"`
	StartLine  int      `json:"start_line"`
	EndLine    int      `json:"end_line"`
}

func astTypeToString(expr ast.Expr) string {
	switch t := expr.(type) {
	case *ast.Ident:
		return t.Name
	case *ast.StarExpr:
		return "*" + astTypeToString(t.X)
	case *ast.SelectorExpr:
		return astTypeToString(t.X) + "." + t.Sel.Name
	case *ast.ArrayType:
		return "[]" + astTypeToString(t.Elt)
	case *ast.MapType:
		return "map[" + astTypeToString(t.Key) + "]" + astTypeToString(t.Value)
	default:
		return "unknown"
	}
}

func extractDocComment(comments []*ast.CommentGroup, pos token.Pos, fset *token.FileSet) string {
	if len(comments) == 0 {
		return ""
	}
	line := fset.Position(pos).Line
	for i := len(comments) - 1; i >= 0; i-- {
		cg := comments[i]
		cgLine := fset.Position(cg.End()).Line
		if cgLine < line && line-cgLine <= 5 {
			return strings.TrimSpace(cg.Text())
		}
	}
	return ""
}

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage: parse_go_ast <file.go>")
		os.Exit(1)
	}
	filename := os.Args[1]
	src, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Printf("ERR: %v\n", err)
		os.Exit(2)
	}

	fset := token.NewFileSet()
	fileNode, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
	if err != nil {
		fmt.Printf("ERR: %v\n", err)
		os.Exit(3)
	}

	decls := []GoDecl{}

	// Package comment (first comment group before package decl)
	pkgComment := ""
	for _, cg := range fileNode.Comments {
		if cg.Pos() < fileNode.Package {
			pkgComment = strings.TrimSpace(cg.Text())
		} else {
			break
		}
	}
	if pkgComment != "" {
		decls = append(decls, GoDecl{
			Name:       "package",
			Type:       "package",
			DocComment: pkgComment,
			StartLine:  1,
			EndLine:    1,
		})
	}

	// Process declarations
	for _, d := range fileNode.Decls {
		switch d := d.(type) {
		case *ast.FuncDecl:
			name := d.Name.Name
			recvType := ""
			if d.Recv != nil && len(d.Recv.List) > 0 {
				recvType = astTypeToString(d.Recv.List[0].Type)
			}
			typ := "func"
			fullName := name
			if recvType != "" {
				typ = "method"
				fullName = fmt.Sprintf("(%s).%s", recvType, name)
			}
			doc := extractDocComment(fileNode.Comments, d.Pos(), fset)
			decls = append(decls, GoDecl{
				Name:       name,
				Type:       typ,
				Receiver:   recvType,
				FullName:   fullName,
				DocComment: doc,
				StartLine:  fset.Position(d.Pos()).Line,
				EndLine:    fset.Position(d.End()).Line,
			})

		case *ast.GenDecl:
			for _, spec := range d.Specs {
				switch s := spec.(type) {
				case *ast.TypeSpec:
					typName := s.Name.Name
					declType := "type"
					var fields []string
					var methods []string

					switch t := s.Type.(type) {
					case *ast.StructType:
						declType = "struct"
						for _, f := range t.Fields.List {
							for _, n := range f.Names {
								field := n.Name
								if f.Tag != nil {
									field += " " + f.Tag.Value
								}
								fields = append(fields, field)
							}
							if f.Names == nil {
								// Embedded field
								fields = append(fields, astTypeToString(f.Type))
							}
						}
					case *ast.InterfaceType:
						declType = "interface"
						for _, m := range t.Methods.List {
							if len(m.Names) > 0 {
								methods = append(methods, m.Names[0].Name)
							}
						}
					}

					doc := extractDocComment(fileNode.Comments, s.Pos(), fset)
					decls = append(decls, GoDecl{
						Name:       typName,
						Type:       declType,
						Fields:     fields,
						Methods:    methods,
						DocComment: doc,
						StartLine:  fset.Position(d.Pos()).Line,
						EndLine:    fset.Position(d.End()).Line,
					})

				case *ast.ValueSpec:
					for _, name := range s.Names {
						doc := extractDocComment(fileNode.Comments, name.Pos(), fset)
						// Use the individual identifier's position, not the declaration's position
						startLine := fset.Position(name.Pos()).Line
						endLine := fset.Position(name.End()).Line
						
						decls = append(decls, GoDecl{
							Name:       name.Name,
							Type:       "var",
							DocComment: doc,
							StartLine:  startLine,
							EndLine:    endLine,
						})
					}
				}
			}
		}
	}

	out, _ := json.Marshal(decls)
	fmt.Println(string(out))
}
