package agl

import (
	"agl/pkg/ast"
	"agl/pkg/parser"
	"agl/pkg/token"
	"agl/pkg/types"
	"agl/pkg/utils"
	"embed"
	_ "embed"
	"fmt"
	goast "go/ast"
	goparser "go/parser"
	gotoken "go/token"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"sync/atomic"
)

//go:embed pkgs/* core/*
var ContentFs embed.FS

var envIDCounter atomic.Int64

type Env struct {
	ID            int64
	structCounter atomic.Int64
	lookupTable   map[string]*Info  // Store constants/variables/functions
	lspTable      map[NodeKey]*Info // Store type for Expr/Stmt
	parent        *Env
	NoIdxUnwrap   bool
	fset          *token.FileSet
	pkgName       string
	inferrer      InferrerErrorCollector // Optional: for collecting errors instead of panicking
}

type InferrerErrorCollector interface {
	AddError(n ast.Node, msg string)
}

func (e *Env) WithPkgName(pkgName string, clb func()) {
	prev := e.pkgName
	e.pkgName = pkgName
	clb()
	e.pkgName = prev
}

func (e *Env) withEnv(clb func(*Env)) {
	clb(e.SubEnv())
}

type Info struct {
	Message     string
	Definition  token.Pos
	Definition1 DefinitionProvider
	Type        types.Type
}

func (i *Info) GetType() types.Type {
	if i != nil {
		return i.Type
	}
	return nil
}

func getGoRecv(e goast.Expr) string {
	recvTyp := e
	switch v := recvTyp.(type) {
	case *goast.Ident:
		if v.IsExported() {
			return v.Name
		}
	case *goast.StarExpr:
		return getGoRecv(v.X)
	case *goast.IndexExpr:
		return getGoRecv(v.X)
	case *goast.IndexListExpr:
		return getGoRecv(v.X)
	default:
		panic(fmt.Sprintf("%v", to(recvTyp)))
	}
	return ""
}

func goFuncDeclTypeToFuncType(name, pkgName string, expr *goast.FuncDecl, env *Env, keepRaw bool) types.FuncType {
	fT := goFuncTypeToFuncType(name, pkgName, expr.Type, env, keepRaw)
	var recvT []types.Type
	if expr.Recv != nil {
		for _, recv := range expr.Recv.List {
			for _, recvName := range recv.Names {
				recvTyp := recv.Type
				n := getGoRecv(recv.Type)
				if n == "" {
					continue
				}
				noop(recvName)
				t := env.GetGoType2(pkgName, recvTyp, keepRaw)
				recvT = append(recvT, t)
			}
		}
	}
	fT.Recv = recvT
	fT.Name = expr.Name.Name
	return fT
}

func goFuncTypeToFuncType(name, pkgName string, expr *goast.FuncType, env *Env, keepRaw bool) types.FuncType {
	native := utils.True()
	var paramsT []types.Type
	if expr.TypeParams != nil {
		for _, typeParam := range expr.TypeParams.List {
			for _, typeParamName := range typeParam.Names {
				typeParamType := typeParam.Type
				t := env.GetGoType2(pkgName, typeParamType, keepRaw)
				t = types.GenericType{W: t, Name: typeParamName.Name, IsType: true}
				env.Define(nil, typeParamName.Name, t)
				paramsT = append(paramsT, t)
			}
		}
	}
	var params []types.Type
	if expr.Params != nil {
		for _, param := range expr.Params.List {
			t := env.GetGoType2(pkgName, param.Type, keepRaw)
			n := max(len(param.Names), 1)
			for i := 0; i < n; i++ {
				params = append(params, t)
			}
		}
	}
	var result types.Type
	var results []types.Type
	if expr.Results != nil {
		for _, resultEl := range expr.Results.List {
			results = append(results, env.GetGoType2(pkgName, resultEl, keepRaw))
		}
	}
	if len(results) > 0 {
		lastResult := results[len(results)-1]
		lastResult = types.Unwrap(lastResult)
		if v, ok := lastResult.(types.InterfaceType); ok && v.Name == "error" {
			results = results[:len(results)-1]
			var inner types.Type = types.TupleType{Elts: results, KeepRaw: keepRaw}
			if len(results) == 0 {
				inner = types.VoidType{}
			} else if len(results) == 1 {
				inner = results[0]
			}
			result = types.ResultType{W: inner, Native: native, KeepRaw: keepRaw}
		} else if len(results) == 1 {
			result = results[0]
		} else {
			result = types.TupleType{Elts: results, KeepRaw: keepRaw}
		}
	} else {
		result = types.VoidType{}
	}
	parts := strings.Split(name, ".")
	name = parts[len(parts)-1]
	ft := types.FuncType{
		Name:       name,
		TypeParams: paramsT,
		Params:     params,
		Return:     result,
		IsNative:   native,
	}
	return ft
}

func funcDeclTypeToFuncType(name string, expr *ast.FuncDecl, env *Env, fset *token.FileSet, native bool) types.FuncType {
	fT := funcTypeToFuncType(name, expr.Type, env, fset, native)
	var recvT []types.Type
	if expr.Recv != nil {
		for _, recv := range expr.Recv.List {
			for _, n := range recv.Names {
				recvTyp := recv.Type
				t := env.GetType2(recvTyp, fset)
				if n.Mutable.IsValid() {
					t = types.MutType{W: t}
				}
				recvT = append(recvT, t)
			}
		}
	}
	fT.Recv = recvT
	fT.Name = expr.Name.Name
	return fT
}

func funcTypeToFuncType(name string, expr *ast.FuncType, env *Env, fset *token.FileSet, native bool) types.FuncType {
	var paramsT []types.Type
	if expr.TypeParams != nil {
		for _, typeParam := range expr.TypeParams.List {
			for _, typeParamName := range typeParam.Names {
				typeParamType := typeParam.Type
				t := env.GetType2(typeParamType, fset)
				t = types.GenericType{W: t, Name: typeParamName.Name, IsType: true}
				env.Define(typeParamName, typeParamName.Name, t)
				paramsT = append(paramsT, t)
			}
		}
	}
	var params []types.Type
	if expr.Params != nil {
		for _, param := range expr.Params.List {
			t := env.GetType2(param.Type, fset)
			n := max(len(param.Names), 1)
			for i := 0; i < n; i++ {
				tt := t
				if len(param.Names) > i && param.Names[i].Mutable.IsValid() {
					tt = types.MutType{W: tt}
				}
				params = append(params, tt)
			}
		}
	}
	var result types.Type
	if expr.Result != nil {
		result = env.GetType2(expr.Result, fset)
		if result == nil {
			panic(fmt.Sprintf("%s: type not found %v %v", fset.Position(expr.Pos()), expr.Result, to(expr.Result)))
		}
		if t, ok := result.(types.ResultType); ok {
			t.Native = native
			result = t
		} else if t1, ok := result.(types.OptionType); ok {
			t1.Native = native
			result = t1
		}
	}
	parts := strings.Split(name, ".")
	name = parts[len(parts)-1]
	if result == nil {
		switch expr.Result.(type) {
		case *ast.ResultExpr:
			result = types.ResultType{W: types.VoidType{}}
		case *ast.OptionExpr:
			result = types.OptionType{W: types.VoidType{}}
		default:
			result = types.VoidType{}
		}
	}
	ft := types.FuncType{
		Name:       name,
		TypeParams: paramsT,
		Params:     params,
		Return:     result,
		IsNative:   native,
	}
	return ft
}

func parseFuncTypeFromString(name, s string, env *Env, fset *token.FileSet) types.FuncType {
	return parseFuncTypeFromStringHelper(name, s, env, fset, false)
}

func parseFuncTypeFromStringNative(name, s string, env *Env, fset *token.FileSet) types.FuncType {
	return parseFuncTypeFromStringHelper(name, s, env, fset, true)
}

func parseFuncTypeFromStringHelper(name, s string, env *Env, fset *token.FileSet, native bool) types.FuncType {
	env = env.SubEnv()
	e := Must(parser.ParseExpr(s))
	expr := e.(*ast.FuncType)
	return funcTypeToFuncType(name, expr, env, fset, native)
}

func parseFuncDeclFromStringHelper(name, s string, env *Env, fset *token.FileSet) types.FuncType {
	s = "package main\n" + s
	env = env.SubEnv()
	e := Must(parser.ParseFile(token.NewFileSet(), "", s, parser.AllErrors))
	expr := e.Decls[0].(*ast.FuncDecl)
	return funcDeclTypeToFuncType(name, expr, env, fset, true)
}

func (e *Env) loadCoreTypes() {
	// Number types
	e.Define(nil, "int", types.TypeType{W: types.IntType{}})
	e.Define(nil, "uint", types.TypeType{W: types.UintType{}})
	e.Define(nil, "i8", types.TypeType{W: types.I8Type{}})
	e.Define(nil, "i16", types.TypeType{W: types.I16Type{}})
	e.Define(nil, "i32", types.TypeType{W: types.I32Type{}})
	e.Define(nil, "i64", types.TypeType{W: types.I64Type{}})
	e.Define(nil, "u8", types.TypeType{W: types.U8Type{}})
	e.Define(nil, "u16", types.TypeType{W: types.U16Type{}})
	e.Define(nil, "u32", types.TypeType{W: types.U32Type{}})
	e.Define(nil, "u64", types.TypeType{W: types.U64Type{}})
	e.Define(nil, "f32", types.TypeType{W: types.F32Type{}})
	e.Define(nil, "f64", types.TypeType{W: types.F64Type{}})

	// Aliases should be avoided
	e.Define(nil, "int8", types.TypeType{W: types.I8Type{}})
	e.Define(nil, "int16", types.TypeType{W: types.I16Type{}})
	e.Define(nil, "int32", types.TypeType{W: types.I32Type{}})
	e.Define(nil, "int64", types.TypeType{W: types.I64Type{}})
	e.Define(nil, "uint8", types.TypeType{W: types.U8Type{}})
	e.Define(nil, "uint16", types.TypeType{W: types.U16Type{}})
	e.Define(nil, "uint32", types.TypeType{W: types.U32Type{}})
	e.Define(nil, "uint64", types.TypeType{W: types.U64Type{}})
	e.Define(nil, "float32", types.TypeType{W: types.F32Type{}})
	e.Define(nil, "float64", types.TypeType{W: types.F64Type{}})

	e.Define(nil, "uintptr", types.TypeType{W: types.UintptrType{}})
	e.Define(nil, "complex128", types.TypeType{W: types.Complex128Type{}})
	e.Define(nil, "complex64", types.TypeType{W: types.Complex64Type{}})
	e.Define(nil, "void", types.TypeType{W: types.VoidType{}})
	e.Define(nil, "any", types.TypeType{W: types.AnyType{}})
	e.Define(nil, "bool", types.TypeType{W: types.BoolType{}})
	e.Define(nil, "string", types.TypeType{W: types.StringType{}})
	e.Define(nil, "byte", types.TypeType{W: types.ByteType{}})
	e.Define(nil, "rune", types.TypeType{W: types.RuneType{}})
	e.Define(nil, "error", types.TypeType{W: types.InterfaceType{Name: "error"}})
	e.Define(nil, "nil", types.TypeType{W: types.NilType{}})
	e.Define(nil, "true", types.BoolType{})
	e.Define(nil, "false", types.BoolType{})

	e.Define(nil, "@LINE", types.StringType{})
	e.Define(nil, "@COLUMN", types.StringType{})
}

func (e *Env) loadCoreFunctions(m *PkgVisited) {
	e.withEnv(func(nenv *Env) {
		_ = e.loadPkgAglStd(0, nil, nenv, "agl1/cmp", "", m)
		e.Define(nil, "comparable", types.InterfaceType{Pkg: "", Name: "comparable"})
		e.Define(nil, "Type", types.InterfaceType{Pkg: "", Name: "Type"})
		e.Define(nil, "Number", types.InterfaceType{Pkg: "agl1", Name: "Number"})
		e.Define(nil, "Integer", types.InterfaceType{Pkg: "agl1", Name: "Integer"})
		e.DefineFn(nenv, "print", "func (args ...any)")
		e.DefineFn(nenv, "println", "func (args ...any)")
		e.DefineFn(nenv, "printf", "func (format string, a ...any)")
		e.DefineFn(nenv, "precondition", "func (pred bool, msg ...string)")
		e.DefineFn(nenv, "assert", "func (pred bool, msg ...string)")
		e.DefineFn(nenv, "assertEq", "func [T comparable](left, right T, msg ...string)")
		e.DefineFn(nenv, "make", "func[T, U any](t T, size ...U) T")
		e.DefineFn(nenv, "recover", "func () any")
		e.DefineFn(nenv, "len", "func [T any](v T) int")
		e.DefineFn(nenv, "cap", "func [T any](v T) int")
		e.DefineFn(nenv, "min", "func [T cmp.Ordered](x T, y ...T) T")
		e.DefineFn(nenv, "max", "func [T cmp.Ordered](x T, y ...T) T")
		e.DefineFn(nenv, "abs", "func [T Number](x T) T", WithDesc("Returns the absolute value of the given number."))
		e.DefineFn(nenv, "pow", "func [T, E Number](v T, e E) T")
		//e.DefineFn("zip", "func [T, U any](x []T, y []U) [](T, U)")
		//e.DefineFn("clear", "func [T ~[]Type | ~map[Type]Type1](t T)")
		e.DefineFn(nenv, "append", "func [T any](slice []T, elems ...T) []T")
		e.DefineFn(nenv, "close", "func (c chan<- Type)")
		e.DefineFn(nenv, "panic", "func ()")
		e.DefineFn(nenv, "panicWith", "func (v any)")
		e.DefineFn(nenv, "new", "func [T any](T) *T")
	})
}

func loadAglImports(fileName string, depth int, t *TreeDrawer, env, nenv *Env, node *ast.File, m *PkgVisited, fset *token.FileSet) {
	var imports [][]string
	for _, d := range node.Imports {
		importName := ""
		if d.Name != nil {
			importName = d.Name.Name
		}
		path := strings.ReplaceAll(d.Path.Value, `"`, ``)
		imports = append(imports, []string{path, importName})
	}
	loadImports(fileName, depth, t, env, nenv, imports, m)
}

func loadGoImports(fileName string, depth int, t *TreeDrawer, env, nenv *Env, node *goast.File, m *PkgVisited) {
	var imports [][]string
	for _, d := range node.Imports {
		importName := ""
		if d.Name != nil {
			importName = d.Name.Name
		}
		path := strings.ReplaceAll(d.Path.Value, `"`, ``)
		imports = append(imports, []string{path, importName})
	}
	loadImports(fileName, depth, t, env, nenv, imports, m)
}

type TreeDrawer struct {
	rows []string
}

func (t *TreeDrawer) Add(depth int, lbl string) {
	t.rows = append(t.rows, fmt.Sprintf("%s├─ %s\n", strings.Repeat("│ ", depth), lbl))
}

func (t *TreeDrawer) Draw() {
	fmt.Print(strings.Join(t.rows, ""))
}

func loadImports(fileName string, depth int, t *TreeDrawer, env, nenv *Env, imports [][]string, m *PkgVisited) {
	if t != nil {
		t.Add(depth, fileName)
	}
	for _, d := range imports {
		path, importName := d[0], d[1]
		if t != nil {
			t.Add(depth+1, path)
		}
		if err := env.loadPkg(depth+2, t, nenv, path, importName, m); err != nil {
			panic(err)
		}
	}
}

func defineFromSrc(depth int, t *TreeDrawer, env, nenv *Env, path, pkgName string, src []byte, m *PkgVisited, fset *token.FileSet) {
	//fset := token.NewFileSet()
	node := Must(parser.ParseFile(fset, "", src, parser.AllErrors|parser.ParseComments))
	origPkgName := filepath.Base(path)
	pkgName = Or(pkgName, node.Name.Name)
	if err := env.DefinePkg(origPkgName, path); err != nil {
		panic(err)
	}
	if err := env.DefinePkg(pkgName, path); err != nil {
		panic(err)
	}
	nenv.WithPkgName(pkgName, func() {
		loadAglImports(path, depth, t, nenv, nenv, node, m, fset)
		loadDecls(env, nenv, node, path, pkgName, fset)
	})
}

func loadDecls(env, nenv *Env, node *ast.File, path, pkgName string, fset *token.FileSet) {
	for _, d := range node.Decls {
		switch decl := d.(type) {
		case *ast.FuncDecl:
			fullName := decl.Name.Name
			if decl.Recv != nil {
				t := decl.Recv.List[0].Type
				var recvName string
				switch v := t.(type) {
				case *ast.Ident:
					recvName = v.Name
				case *ast.StarExpr:
					switch vv := v.X.(type) {
					case *ast.Ident:
						recvName = vv.Name
					case *ast.SelectorExpr:
						recvName = vv.Sel.Name
					default:
						panic(fmt.Sprintf("%v", to(v.X)))
					}
				case *ast.SelectorExpr:
					recvName = v.Sel.Name
				default:
					panic(fmt.Sprintf("%v", to(t)))
				}
				fullName = recvName + "." + fullName
			}
			fullName = pkgName + "." + fullName
			ft := funcDeclTypeToFuncType("", decl, nenv, fset, true)
			if decl.Doc != nil && decl.Doc.List[0].Text == "// agl:wrapped" {
				ft.IsNative = false
				switch v := ft.Return.(type) {
				case types.ResultType:
					v.Native = false
					ft.Return = v
				case types.OptionType:
					v.Native = false
					ft.Return = v
				}
			} else {
				ft.IsNative = true
			}
			var opts []SetTypeOption
			if decl.Doc != nil {
				doc := decl.Doc.List[0].Text
				r := regexp.MustCompile(`// ([^:]+):(\d+):(\d+)$`)
				if r.MatchString(doc) {
					goroot := runtime.GOROOT()
					parts := strings.Split(doc, ":")
					absPath, _ := filepath.Abs(filepath.Join(goroot, "src", path))
					absPath = filepath.Join(absPath, strings.TrimPrefix(parts[0], "// "))
					line, _ := strconv.Atoi(parts[1])
					character, _ := strconv.Atoi(parts[2])
					opts = append(opts, WithDefinition1(DefinitionProvider{URI: absPath, Line: line, Character: character}))
				}
			}
			if err := env.DefineFnNative2(fullName, ft, opts...); err != nil {
				assert(false, err.Error())
			}
		case *ast.GenDecl:
			for _, s := range decl.Specs {
				switch spec := s.(type) {
				case *ast.TypeSpec:
					specName := pkgName + "." + spec.Name.Name
					switch v := spec.Type.(type) {
					case *ast.Ident:
						t := nenv.GetType2(v, fset)
						env.Define(nil, specName, types.CustomType{Pkg: pkgName, Name: spec.Name.Name, W: t})
					case *ast.MapType:
						t := nenv.GetType2(v, fset)
						env.Define(nil, specName, t)
					case *ast.StarExpr:
						t := nenv.GetType2(v, fset)
						env.Define(nil, specName, t)
					case *ast.ArrayType:
						t := nenv.GetType2(v, fset)
						env.Define(nil, specName, t)
					case *ast.FuncType:
						if spec.TypeParams != nil {
							for _, e := range spec.TypeParams.List {
								typ := nenv.GetType2(e.Type, fset)
								for _, n := range e.Names {
									nenv.Define(nil, n.Name, types.GenericType{Name: n.Name, W: typ})
								}
							}
						}
						t := nenv.GetType2(v, fset).(types.FuncType)
						t.Name = specName
						if spec.TypeParams != nil {
							for _, tp := range spec.TypeParams.List {
								tpT := nenv.GetType2(tp.Type, fset)
								for _, n := range tp.Names {
									t.TypeParams = append(t.TypeParams, types.GenericType{Name: n.Name, W: tpT})
								}
							}
						}
						env.Define(nil, specName, t)
					case *ast.InterfaceType:
						var methodsT []types.InterfaceMethod
						if v.Methods != nil {
							for _, m := range v.Methods.List {
								for _, n := range m.Names {
									fullName := pkgName + "." + spec.Name.Name + "." + n.Name
									t := nenv.GetType2(m.Type, fset)
									tmp := t.(types.FuncType)
									tmp.Name = n.Name
									env.Define(nil, fullName, tmp)
									methodsT = append(methodsT, types.InterfaceMethod{Name: n.Name, Typ: tmp})
								}
							}
						}
						env.Define(nil, specName, types.InterfaceType{Pkg: pkgName, Name: spec.Name.Name, Methods: methodsT})
					case *ast.StructType:
						env.Define(nil, specName, types.StructType{Pkg: pkgName, Name: spec.Name.Name})
						if v.Fields != nil {
							for _, field := range v.Fields.List {
								t := nenv.GetType2(field.Type, fset)
								for _, name := range field.Names {
									fieldName := pkgName + "." + spec.Name.Name + "." + name.Name
									switch vv := t.(type) {
									case types.InterfaceType:
										env.Define(nil, fieldName, types.InterfaceType{Pkg: vv.Pkg, Name: vv.Name, Methods: vv.Methods})
									case types.StructType:
										env.Define(nil, fieldName, types.StructType{Pkg: vv.Pkg, Name: vv.Name})
									case types.TypeType:
										env.Define(nil, fieldName, vv.W)
									case types.ArrayType:
										env.Define(nil, fieldName, vv)
									case types.StarType:
										env.Define(nil, fieldName, vv)
									case types.MapType:
										env.Define(nil, fieldName, vv)
									case types.CustomType:
										env.Define(nil, fieldName, vv)
									default:
										panic(fmt.Sprintf("%v", to(t)))
									}
								}
							}
						}
					default:
						panic(fmt.Sprintf("%v", to(spec.Type)))
					}
				case *ast.ImportSpec:
				case *ast.ValueSpec:
					for _, name := range spec.Names {
						fieldName := pkgName + "." + name.Name
						t := nenv.GetType2(spec.Type, fset)
						env.Define(nil, fieldName, t)
					}
				default:
					panic(fmt.Sprintf("%v", to(s)))
				}
			}
		}
	}
}

type Later struct {
	pkgName   string
	s         goast.Spec
	node      *goast.File
	fset      *gotoken.FileSet
	path      string
	entryName string
}

func defineStructsFromGoSrc(path string, depth int, t *TreeDrawer, files []EntryContent, env, nenv *Env, m *PkgVisited, keepRaw bool) {
	var tryLater []Later
	for _, entry := range files {
		env.withEnv(func(nenv *Env) {
			//p("LOADING", fullPath)
			fset := gotoken.NewFileSet()
			node := Must(goparser.ParseFile(fset, "", entry.Content, goparser.AllErrors|goparser.ParseComments))
			pkgName := node.Name.Name
			loadGoImports(filepath.Join(path, entry.Name), depth, t, env, nenv, node, m)
			for _, d := range node.Decls {
				switch decl := d.(type) {
				case *goast.GenDecl:
					for _, s := range decl.Specs {
						processSpec(path, entry.Name, node, fset, s, env, pkgName, &tryLater, keepRaw)
					}
				}
			}
		})
	}
	i := 0
	for len(tryLater) > 0 {
		var d Later
		d, tryLater = tryLater[0], tryLater[1:]
		processSpec(d.path, d.entryName, d.node, d.fset, d.s, env, d.pkgName, &tryLater, keepRaw)
		i++
		if i > 10 {
			break
		}
	}
}

func processSpec(path, entryName string, node *goast.File, fset *gotoken.FileSet, s goast.Spec, env *Env, pkgName string, tryLater *[]Later, keepRaw bool) {
	switch spec := s.(type) {
	//case *goast.ValueSpec:
	//	for _, name := range spec.Names {
	//		fieldName := pkgName + "." + name.Name
	//		t := env.GetGoType2(pkgName, spec.Type, keepRaw)
	//		p(fieldName, spec.Type, t)
	//		env.DefineForce(nil, fieldName, t)
	//	}
	case *goast.TypeSpec:
		if !spec.Name.IsExported() {
			return
		}
		specName := pkgName + "." + spec.Name.Name
		switch v := spec.Type.(type) {
		case *goast.StructType:
			var fields []types.FieldType
			if v.Fields != nil {
				for _, field := range v.Fields.List {
					tmp := func() types.Type {
						t := env.GetGoType2(pkgName, field.Type, keepRaw)
						t = types.Unwrap(t)
						if TryCast[types.VoidType](t) || t == nil {
							fT := field.Type
							if vv, ok := field.Type.(*goast.StarExpr); ok {
								fT = vv.X
							}
							if vv, ok := fT.(*goast.Ident); ok && vv.Name == spec.Name.Name {
								t = types.StructType{Pkg: pkgName, Name: spec.Name.Name}
							} else {
								*tryLater = append(*tryLater, Later{s: s, pkgName: pkgName, node: node, fset: fset, path: path, entryName: entryName})
								//p("DEFSTRUCT1", pkgName, spec.Name.Name)
								env.Define(nil, specName, types.StructType{Pkg: pkgName, Name: spec.Name.Name, Fields: fields})
								return nil
							}
						}
						return t
					}
					if len(field.Names) == 0 {
						fields = append(fields, types.FieldType{Name: "", Typ: tmp()})
					}
					for _, name := range field.Names {
						if !name.IsExported() {
							continue
						}
						fields = append(fields, types.FieldType{Name: name.Name, Typ: tmp()})
					}
				}
			}
			//p("DEFSTRUCT2", pkgName, spec.Name.Name)
			//p(path, entryName, node, fset)
			absPath, _ := filepath.Abs(filepath.Join(path, entryName))
			pos := fset.Position(spec.Pos())
			env.Define(nil, specName, types.StructType{Pkg: pkgName, Name: spec.Name.Name, Fields: fields},
				WithDefinition1(DefinitionProvider{URI: absPath, Line: pos.Line, Character: pos.Column}))
		case *goast.InterfaceType:
			var methodsT []types.InterfaceMethod
			if v.Methods != nil {
				for _, m := range v.Methods.List {
					for _, n := range m.Names {
						if !n.IsExported() {
							continue
						}
						fullName := pkgName + "." + spec.Name.Name + "." + n.Name
						t := env.GetGoType2(pkgName, m.Type, keepRaw)
						env.Define(nil, fullName, t)
						methodsT = append(methodsT, types.InterfaceMethod{Name: n.Name, Typ: t})
					}
				}
			}
			env.Define(nil, specName, types.InterfaceType{Pkg: pkgName, Name: spec.Name.Name, Methods: methodsT})
		case *goast.IndexListExpr:
		case *goast.ArrayType:
		case *goast.MapType:
		case *goast.Ident:
			t := env.GetGoType2(pkgName, v, keepRaw)
			if TryCast[types.VoidType](t) {
				if !v.IsExported() {
					return
				}
				*tryLater = append(*tryLater, Later{s: s, pkgName: pkgName})
				return
			}
			env.Define(nil, specName, t)
		case *goast.FuncType:
		case *goast.SelectorExpr:
		case *goast.IndexExpr:
		default:
			panic(fmt.Sprintf("%v", to(spec.Type)))
		}
	}
}

func defineFromGoSrc(env, nenv *Env, path string, src []byte, keepRaw bool) {
	node := Must(goparser.ParseFile(gotoken.NewFileSet(), "", src, goparser.AllErrors|goparser.ParseComments))
	pkgName := node.Name.Name
	_ = env.DefinePkg(pkgName, path) // Many files have the same "package"
	for _, d := range node.Decls {
		switch decl := d.(type) {
		case *goast.FuncDecl:
			if !decl.Name.IsExported() {
				continue
			}
			fnT := goFuncDeclTypeToFuncType("", pkgName, decl, env, keepRaw)
			fullName := decl.Name.Name
			//p(fnT, "  |  ", fullName)
			if decl.Recv != nil {
				recvName := getGoRecv(decl.Recv.List[0].Type)
				if recvName == "" {
					continue
				}
				fullName = recvName + "." + fullName
			}
			fullName = pkgName + "." + fullName
			if err := env.DefineFnNative2(fullName, fnT); err != nil {
				continue // TODO should not skip errors
			}
		}
	}
}

func (e *Env) loadPkg(depth int, t *TreeDrawer, nenv *Env, pkgPath, pkgName string, m *PkgVisited) error {
	pkgName = Or(pkgName, filepath.Base(pkgPath))
	if m.ContainsAdd(pkgPath + "_" + pkgName + "_" + strconv.FormatInt(e.ID, 10)) {
		return nil
	}
	//p("?LOADPKG", pkgPath, pkgName)
	if err := e.loadPkgLocal(depth, t, nenv, pkgPath, pkgName, m); err != nil {
		if err := e.loadPkgAglStd(depth, t, nenv, pkgPath, pkgName, m); err != nil {
			if err := e.loadPkgVendor(depth, t, nenv, pkgPath, pkgName, m); err != nil {
				return err
			}
		}
	}
	return nil
}

func (e *Env) loadPkgLocal(depth int, t *TreeDrawer, nenv *Env, pkgPath, pkgName string, m *PkgVisited) error {
	pkgFullPath := trimPrefixPath(pkgPath)
	if err := e.DefinePkg(pkgPath, pkgName); err != nil {
		return nil
	}
	if err := e.loadVendor2(depth, t, nenv, pkgFullPath, m); err != nil {
		return err
	}
	return nil
}

func (e *Env) loadPkgAglStd(depth int, t *TreeDrawer, nenv *Env, path, pkgName string, m *PkgVisited) error {
	var prefix string
	if strings.HasPrefix(path, "agl1/") {
		prefix = "agl1/"
	} else {
		prefix = "std/"
		if !strings.HasPrefix(path, "std/") {
			path = filepath.Join("std", path)
		}
	}
	//if path == "std/fmt" {
	//	goroot := runtime.GOROOT()
	//	absPath, _ := filepath.Abs(filepath.Join(goroot, "src", "fmt"))
	//	if err := e.loadVendor2(nenv, absPath, m); err != nil {
	//		return err
	//	}
	//}
	return e.loadAglFile(depth, t, nenv, prefix, path, pkgName, m)
}

func (e *Env) loadPkgVendor(depth int, t *TreeDrawer, nenv *Env, path, pkgName string, m *PkgVisited) error {
	vendorPath := filepath.Join("vendor", path)
	if err := e.loadVendor2(depth, t, nenv, vendorPath, m); err != nil {
		if err := e.loadAglFile(depth, t, nenv, "", path, pkgName, m); err != nil {
			return err
		}
	}
	return nil
}

func (e *Env) loadAglFile(depth int, t *TreeDrawer, nenv *Env, prefix, path, pkgName string, m *PkgVisited) error {
	stdFilePath := filepath.Join("pkgs", path, filepath.Base(path)+".agl")
	by, err := ContentFs.ReadFile(stdFilePath)
	if err != nil {
		return err
	}
	final := filepath.Dir(strings.TrimPrefix(stdFilePath, "pkgs/"+prefix))
	defineFromSrc(depth, t, e, nenv, final, pkgName, by, m, e.fset)
	return nil
}

func (e *Env) loadVendor2(depth int, t *TreeDrawer, nenv *Env, path string, m *PkgVisited) error {
	keepRaw := utils.True()
	files, err := readDir(path, m)
	if err != nil {
		return err
	}
	defineStructsFromGoSrc(path, depth, t, files, e, nenv, m, keepRaw)
	for _, entry := range files {
		defineFromGoSrc(e, nenv, path, entry.Content, keepRaw)
	}
	return nil
}

type EntryContent struct {
	Name    string
	Content []byte
}

// Read `*.go` files from a given directory
func readDir(path string, m *PkgVisited) ([]EntryContent, error) {
	entries, err := os.ReadDir(path)
	if err != nil {
		return nil, err
	}
	files := make([]EntryContent, 0)
	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") {
			continue
		}
		fullPath := filepath.Join(path, entry.Name())
		if m.ContainsAdd(fullPath) {
			continue
		}
		by, err := os.ReadFile(fullPath)
		if err != nil {
			continue
		}
		files = append(files, EntryContent{Name: entry.Name(), Content: by})
	}
	return files, nil
}

func (e *Env) loadPkgAgl(m *PkgVisited) {
	_ = e.DefinePkg("agl1", "agl1")
	e.withEnv(func(nenv *Env) {
		_ = e.loadPkgAglStd(0, nil, nenv, "agl1/cmp", "", m)
		_ = e.loadPkgAglStd(0, nil, nenv, "agl1/iter", "", m)
		e.Define(nil, "Iterator", types.InterfaceType{Pkg: "agl1", Name: "Iterator", TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}})
		e.Define(nil, "Sequence", types.StructType{Pkg: "", Name: "Sequence", TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}})
		e.Define(nil, "DoubleEndedIterator", types.InterfaceType{Pkg: "agl1", Name: "DoubleEndedIterator", TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}})
		e.Define(nil, "DictEntry", types.StructType{Pkg: "", Name: "DictEntry", TypeParams: []types.Type{
			types.GenericType{Name: "K", W: types.AnyType{}},
			types.GenericType{Name: "V", W: types.AnyType{}},
		}})
		e.Define(nil, "DictEntry.Key", types.GenericType{Name: "K", W: types.AnyType{}})
		e.Define(nil, "DictEntry.Value", types.GenericType{Name: "V", W: types.AnyType{}})
		e.Define(nil, "agl1.Rev", types.StructType{Pkg: "agl1", Name: "Rev", TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}})
		e.Define(nil, "agl1.Set", types.SetType{K: types.GenericType{Name: "T", W: types.AnyType{}}})
		e.Define(nil, "agl1.Vec", types.ArrayType{Elt: types.GenericType{Name: "T", W: types.AnyType{}}})
		e.Define(nil, "agl1.Enum", types.EnumType{})
		e.Define(nil, "agl1.String", types.StringType{})
		e.DefineFn(nenv, "agl1.Enum.RawValue", "func [T any](e agl1.Enum[T]) int")
		e.DefineFn(nenv, "agl1.Set.Len", "func [T comparable](s agl1.Set[T]) int", WithDesc("The number of elements in the set."))
		e.DefineFn(nenv, "agl1.Set.Insert", "func [T comparable](mut s agl1.Set[T], el T) bool", WithDesc("Inserts the given element in the set if it is not already present."))
		e.DefineFn(nenv, "agl1.Set.Remove", "func [T comparable](mut s agl1.Set[T], el T) T?")
		e.DefineFn(nenv, "agl1.Set.RemoveFirst", "func [T comparable](mut s agl1.Set[T]) T")
		e.DefineFn(nenv, "agl1.Set.Contains", "func [T comparable](s agl1.Set[T], el T) bool")
		e.DefineFn(nenv, "agl1.Set.ContainsWhere", "func [T comparable](s agl1.Set[T], where: p func(T) bool) bool")
		e.DefineFn(nenv, "agl1.Set.Iter", "func [T comparable](s agl1.Set[T]) Sequence[T]")
		e.DefineFn(nenv, "agl1.Set.Filter", "func [T comparable](s agl1.Set[T], f func(e T) bool) agl1.Set[T]", WithDesc("Returns a new set containing the elements of the set that satisfy the given predicate."))
		e.DefineFn(nenv, "agl1.Set.Union", "func [T comparable](s agl1.Set[T], other Iterator[T]) agl1.Set[T]")
		e.DefineFn(nenv, "agl1.Set.FormUnion", "func [T comparable](mut s agl1.Set[T], other Sequence[T])")
		e.DefineFn(nenv, "agl1.Set.Subtracting", "func [T comparable](s agl1.Set[T], other Iterator[T]) agl1.Set[T]")
		e.DefineFn(nenv, "agl1.Set.Subtract", "func [T comparable](mut s agl1.Set[T], other Iterator[T])")
		e.DefineFn(nenv, "agl1.Set.Intersection", "func [T comparable](s agl1.Set[T], other Iterator[T]) agl1.Set[T]", WithDesc("Returns a new set with the elements that are common to both this set and the given sequence."))
		e.DefineFn(nenv, "agl1.Set.FormIntersection", "func [T comparable](mut s agl1.Set[T], other Iterator[T])")
		e.DefineFn(nenv, "agl1.Set.SymmetricDifference", "func [T comparable](s agl1.Set[T], other Iterator[T]) agl1.Set[T]")
		e.DefineFn(nenv, "agl1.Set.FormSymmetricDifference", "func [T comparable](mut s agl1.Set[T], other Iterator[T])")
		e.DefineFn(nenv, "agl1.Set.IsSubset", "func [T comparable](s agl1.Set[T], other Iterator[T]) bool")
		e.DefineFn(nenv, "agl1.Set.IsStrictSubset", "func [T comparable](s agl1.Set[T], other Iterator[T]) bool")
		e.DefineFn(nenv, "agl1.Set.IsSuperset", "func [T comparable](s agl1.Set[T], other Iterator[T]) bool")
		e.DefineFn(nenv, "agl1.Set.IsStrictSuperset", "func [T comparable](s agl1.Set[T], other Iterator[T]) bool")
		e.DefineFn(nenv, "agl1.Set.IsDisjoint", "func [T comparable](s agl1.Set[T], other Iterator[T]) bool", WithDesc("Returns a Boolean value that indicates whether the set has no members in common with the given sequence."))
		e.DefineFn(nenv, "agl1.Set.Intersects", "func [T comparable](s agl1.Set[T], other Iterator[T]) bool", WithDesc("Returns a Boolean value that indicates whether the set has members in common with the given sequence."))
		e.DefineFn(nenv, "agl1.Set.Equals", "func [T comparable](s, other agl1.Set[T]) bool")
		e.DefineFn(nenv, "agl1.Set.Min", "func [T comparable](s agl1.Set[T]) T?")
		e.DefineFn(nenv, "agl1.Set.Max", "func [T comparable](s agl1.Set[T]) T?")
		e.DefineFn(nenv, "agl1.Set.Map", "func [T comparable, R any](s agl1.Set[T], f func(T) R) []R")
		e.DefineFn(nenv, "agl1.Set.First", "func [T comparable](s agl1.Set[T]) T?")
		e.DefineFn(nenv, "agl1.Set.FirstWhere", "func [T comparable](s agl1.Set[T], where: predicate func(T) bool) T?")
		e.DefineFn(nenv, "agl1.Set.IsEmpty", "func [T comparable](s agl1.Set[T]) bool")
		e.DefineFn(nenv, "agl1.String.IsEmpty", "func (s string) bool")
		e.DefineFn(nenv, "agl1.String.Split", "func (s, sep string) []string", WithDesc("Split slices s into all substrings separated by sep and returns a slice of\nthe substrings between those separators."))
		e.DefineFn(nenv, "agl1.String.SplitAfter", "func (s, sep string) []string")
		e.DefineFn(nenv, "agl1.String.Replace", "func (s, old, new string, n int) string")
		e.DefineFn(nenv, "agl1.String.ReplaceAll", "func (s, old, new string) string")
		e.DefineFn(nenv, "agl1.String.Cut", "func (s, sep string) (string, string)?")
		e.DefineFn(nenv, "agl1.String.CutPrefix", "func (s, prefix string) string?")
		e.DefineFn(nenv, "agl1.String.CutSuffix", "func (s, suffix string) string?")
		e.DefineFn(nenv, "agl1.String.Trim", "func (s, cutset string) string")
		e.DefineFn(nenv, "agl1.String.TrimLeft", "func (s, cutset string) string")
		e.DefineFn(nenv, "agl1.String.TrimRight", "func (s, cutset string) string")
		e.DefineFn(nenv, "agl1.String.TrimPrefix", "func (s, prefix string) string")
		e.DefineFn(nenv, "agl1.String.TrimSuffix", "func (s, suffix string) string")
		e.DefineFn(nenv, "agl1.String.TrimSpace", "func (s string) string")
		e.DefineFn(nenv, "agl1.String.Count", "func (s, substr string) int")
		e.DefineFn(nenv, "agl1.String.Repeat", "func (s string, count int) string")
		e.DefineFn(nenv, "agl1.String.Index", "func (s, substr string) int")
		e.DefineFn(nenv, "agl1.String.LastIndex", "func (s, substr string) int")
		e.DefineFn(nenv, "agl1.String.Contains", "func (s, substr string) bool")
		e.DefineFn(nenv, "agl1.String.ContainsAny", "func (s, chars string) bool")
		e.DefineFn(nenv, "agl1.String.HasPrefix", "func (s, prefix string) bool")
		e.DefineFn(nenv, "agl1.String.HasSuffix", "func (s, prefix string) bool")
		e.DefineFn(nenv, "agl1.String.Lowercased", "func (s string) string")
		e.DefineFn(nenv, "agl1.String.Uppercased", "func (s string) string")
		e.DefineFn(nenv, "agl1.String.Lines", "func (s string) []string")
		e.DefineFn(nenv, "agl1.String.AsBytes", "func (s string) []byte")
		e.DefineFn(nenv, "agl1.String.Len", "func (s string) int")
		e.DefineFn(nenv, "agl1.String.Enumerated", "func (s string) [](int, rune)")
		e.DefineFn(nenv, "agl1.String.Int", "func (s string) int?", WithDesc("Parse the string into an 'int' value.\nReturns None if the string cannot be parsed as an 'int'."))
		e.DefineFn(nenv, "agl1.String.I8", "func (s string) i8?", WithDesc("Parse the string into an 'i8' value.\nReturns None if the string cannot be parsed as an 'i8'."))
		e.DefineFn(nenv, "agl1.String.I16", "func (s string) i16?", WithDesc("Parse the string into an 'i16' value.\nReturns None if the string cannot be parsed as an 'i16'."))
		e.DefineFn(nenv, "agl1.String.I32", "func (s string) i32?", WithDesc("Parse the string into an 'i32' value.\nReturns None if the string cannot be parsed as an 'i32'."))
		e.DefineFn(nenv, "agl1.String.I64", "func (s string) i64?", WithDesc("Parse the string into an 'i64' value.\nReturns None if the string cannot be parsed as an 'i64'."))
		e.DefineFn(nenv, "agl1.String.Uint", "func (s string) uint?", WithDesc("Parse the string into an 'uint' value.\nReturns None if the string cannot be parsed as an 'uint'."))
		e.DefineFn(nenv, "agl1.String.U8", "func (s string) u8?", WithDesc("Parse the string into an 'u8' value.\nReturns None if the string cannot be parsed as an 'u8'."))
		e.DefineFn(nenv, "agl1.String.U16", "func (s string) u16?", WithDesc("Parse the string into an 'u16' value.\nReturns None if the string cannot be parsed as an 'u16'."))
		e.DefineFn(nenv, "agl1.String.U32", "func (s string) u32?", WithDesc("Parse the string into an 'u32' value.\nReturns None if the string cannot be parsed as an 'u32'."))
		e.DefineFn(nenv, "agl1.String.U64", "func (s string) u64?", WithDesc("Parse the string into an 'u64' value.\nReturns None if the string cannot be parsed as an 'u64'."))
		e.DefineFn(nenv, "agl1.String.F32", "func (s string) f32?", WithDesc("Parse the string into an 'f32' value.\nReturns None if the string cannot be parsed as an 'f32'."))
		e.DefineFn(nenv, "agl1.String.F64", "func (s string) f64?", WithDesc("Parse the string into an 'f64' value.\nReturns None if the string cannot be parsed as an 'f64'."))
		e.DefineFn(nenv, "agl1.Int.String", "func (int) string")
		e.DefineFn(nenv, "agl1.I64.String", "func (i64) string")
		e.DefineFn(nenv, "agl1.Uint.String", "func (uint) string")
		e.DefineFn(nenv, "agl1.Vec.AllSatisfy", "func [T any](a []T, f func(T) bool) bool", WithDesc("Returns a Boolean value indicating whether every element of a sequence satisfies a given predicate."))
		e.DefineFn(nenv, "agl1.Vec.Any", "func [T any](a []T, f func(T) bool) bool")
		e.DefineFn(nenv, "agl1.Vec.Clear", "func [T any](mut a []T)")
		e.DefineFn(nenv, "agl1.Vec.Clone", "func [T any](a []T) []T")
		e.DefineFn(nenv, "agl1.Vec.Contains", "func [T comparable](a []T, e T) bool")
		e.DefineFn(nenv, "agl1.Vec.ContainsWhere", "func [T comparable](a []T, where: p func(T) bool) bool")
		e.DefineFn(nenv, "agl1.Vec.Filter", "func [T any](a []T, f func(e T) bool) []T", WithDesc("Returns a new collection of the same type containing, in order, the elements of the original collection that satisfy the given predicate."))
		e.DefineFn(nenv, "agl1.Vec.FilterMap", "func [T, R any](a []T, f func(e T) R?) []R")
		e.DefineFn(nenv, "agl1.Vec.Find", "func [T any](a []T, f func(e T) bool) T?")
		e.DefineFn(nenv, "agl1.Vec.FirstIndex", "func [T any](a []T, of: e T) int?")
		e.DefineFn(nenv, "agl1.Vec.FirstIndexWhere", "func [T any](a []T, where: p func(T) bool) int?")
		e.DefineFn(nenv, "agl1.Vec.With", "func [T any](mut a []T, i int, clb func(*T))")
		e.DefineFn(nenv, "agl1.Vec.Indices", "func [T any](a []T) []int")
		e.DefineFn(nenv, "agl1.Vec.Insert", "func [T any](mut a []T, idx int, el T)")
		//e.DefineFn(nenv, "agl1.Vec.Iter", "func [T any](a []T) iter.Seq[T]")
		e.DefineFn(nenv, "agl1.Vec.Joined", "func (a []string) string", WithDesc("Returns the elements of this sequence of sequences, concatenated."))
		e.DefineFn(nenv, "agl1.Vec.Map", "func [T, R any](a []T, f func(T) R) []R", WithDesc("Returns an array containing the results of mapping the given closure over the sequence’s elements."))
		e.DefineFn(nenv, "agl1.Vec.Pop", "func [T any](mut a []T) T?")
		e.DefineFn(nenv, "agl1.Vec.PopFront", "func [T any](mut a []T) T?")
		e.DefineFn(nenv, "agl1.Vec.PopIf", "func [T any](mut a []T, pred func() bool) T?")
		e.DefineFn(nenv, "agl1.Vec.Push", "func [T any](mut a []T, els ...T)")
		e.DefineFn(nenv, "agl1.Vec.PushFront", "func [T any](mut a []T, el T)")
		e.DefineFn(nenv, "agl1.Vec.Reduce", "func [T any, R cmp.Ordered](a []T, r R, f func(a R, e T) R) R", WithDesc("Returns the result of combining the elements of the sequence using the given closure."))
		e.DefineFn(nenv, "agl1.Vec.ReduceInto", "func [T any, R cmp.Ordered](a []T, into: r R, f func(mut a *R, e T)) R", WithDesc("Returns the result of combining the elements of the sequence using the given closure."))
		e.DefineFn(nenv, "agl1.Vec.Remove", "func [T any](mut a []T, i int)")
		e.DefineFn(nenv, "agl1.Vec.RemoveFirst", "func [T any](mut a []T) T")
		e.DefineFn(nenv, "agl1.Vec.Sorted", "func [E cmp.Ordered](a []E) []E", WithDesc("Returns the elements of the sequence, sorted."))
		e.DefineFn(nenv, "agl1.Vec.SortedBy", "func [E any](a []E, f func(a, b E) bool) []E")
		e.DefineFn(nenv, "agl1.Vec.Sum", "func [T cmp.Ordered](a []T) T")
		e.DefineFn(nenv, "agl1.Vec.Swap", "func [T any](mut a []T, b, c Integer)")
		e.DefineFn(nenv, "agl1.Vec.__ADD", "func [T any](a, b []T) []T")
		e.DefineFn(nenv, "agl1.Map.ContainsKey", "func [K comparable, V any](m map[K]V, k K) bool")
		e.DefineFn(nenv, "agl1.Map.Len", "func [K comparable, V any](m map[K]V) int")
		e.DefineFn(nenv, "agl1.Map.Get", "func [K comparable, V any](m map[K]V) V?")
		e.DefineFn(nenv, "agl1.Map.Keys", "func [K comparable, V any](m map[K]V) Sequence[K]")
		e.DefineFn(nenv, "agl1.Map.Values", "func [K comparable, V any](m map[K]V) Sequence[V]")
		e.DefineFn(nenv, "agl1.Map.Filter", "func [K comparable, V any](m map[K]V, f func(DictEntry[K, V]) bool) map[K]V")
		e.DefineFn(nenv, "agl1.Map.Map", "func [K comparable, V, R any](m map[K]V, f func(DictEntry[K, V]) R) []R")
		e.DefineFn(nenv, "agl1.Map.Reduce", "func [K comparable, V, R any](m map[K]V, R, f func(R, DictEntry[K, V]) R) R")
		e.DefineFn(nenv, "agl1.Map.ReduceInto", "func [K comparable, V, R any](m map[K]V, into: r R, f func(mut a *R, e DictEntry[K, V])) R")
		e.DefineFn(nenv, "agl1.Option.Unwrap", "func [T any]() T", WithDesc("Unwraps an Option value, yielding the content of a Some(x), or panic if None."))
		e.DefineFn(nenv, "agl1.Option.UnwrapOr", "func [T any](t T) T", WithDesc("Unwraps an Option value, yielding the content of a Some(x), or a default if None."))
		e.DefineFn(nenv, "agl1.Option.UnwrapOrDefault", "func [T any]() T", WithDesc("Unwraps an Option value, yielding the content of a Some(x), or the default if None."))
		e.DefineFn(nenv, "agl1.Option.IsSome", "func () bool")
		e.DefineFn(nenv, "agl1.Option.IsNone", "func () bool")
		e.DefineFn(nenv, "agl1.Option.Map", "func [T, R any](o T?, f func(e T) R) R?")
		e.DefineFn(nenv, "agl1.Result.Unwrap", "func [T any]() T")
		e.DefineFn(nenv, "agl1.Result.UnwrapOr", "func [T any](t T) T")
		e.DefineFn(nenv, "agl1.Result.UnwrapOrDefault", "func [T any]() T")
		e.DefineFn(nenv, "agl1.Result.IsOk", "func () bool")
		e.DefineFn(nenv, "agl1.Result.IsErr", "func () bool")
		e.DefineFn(nenv, "Sequence.Sum", "func [T, R Number](a []T) R")
		e.DefineFn(nenv, "Sequence.Sorted", "func [T cmp.Ordered](s Sequence[T]) []T")
		e.DefineFn(nenv, "Sequence.Joined", "func (s Sequence[string], sep string) string")
		e.DefineFn(nenv, "Sequence.Filter", "func [T any](s Sequence[T], f func(e T) bool) Sequence[T]")
		e.DefineFn(nenv, "Sequence.Len", "func [T any](s Sequence[T]) int")
		e.DefineFn(nenv, "Set", "func[T any] (it Iterator[T]) agl1.Set[T]")
		e.DefineFn(nenv, "Array", "func[T any] (it Sequence[T]) []T")
		e.DefineFn(nenv, "agl1.DoubleEndedIterator.Rev", "func[T any, I DoubleEndedIterator[T]] () *agl1.Rev[T]", WithDesc("A double-ended iterator with the direction inverted."))
		e.DefineFn(nenv, "agl1.Iterator.AllSatisfy", "func[T any] (it Iterator[T], pred func(T) bool) bool")
		e.DefineFn(nenv, "agl1.Iterator.Contains", "func[T any] (it Iterator[T], el T) bool")
		//e.DefineFn(nenv, "agl1.Iterator.Map", "func [T, R any](it Iterator[T], f func(T) R) []R")
	})
}

func CoreFns() string {
	return string(Must(ContentFs.ReadFile(filepath.Join("core", "core.agl"))))
}

func (e *Env) loadBaseValues() {
	m := NewPkgVisited()
	e.loadCoreTypes()
	e.loadCoreFunctions(m)
	e.loadPkgAgl(m)
	e.Define(nil, "Option", types.OptionType{})
	e.Define(nil, "comparable", types.TypeType{W: types.CustomType{Name: "comparable", W: types.AnyType{}}})
}

func NewEnv(fset *token.FileSet) *Env {
	env := &Env{
		ID:          envIDCounter.Add(1),
		lookupTable: make(map[string]*Info),
		lspTable:    make(map[NodeKey]*Info),
		fset:        fset,
	}
	env.loadBaseValues()
	return env
}

func (e *Env) SubEnv() *Env {
	env := &Env{
		ID:          envIDCounter.Add(1),
		lookupTable: make(map[string]*Info),
		lspTable:    e.lspTable,
		parent:      e,
		NoIdxUnwrap: e.NoIdxUnwrap,
		fset:        e.fset,
		inferrer:    e.inferrer, // Propagate inferrer reference
	}
	//p("SubEnv", e.ID, env.ID)
	return env
}

func (e *Env) GetDirect(name string) types.Type {
	info := e.lookupTable[name]
	if info == nil {
		return nil
	}
	return info.Type
}

func (e *Env) Get(name string) types.Type {
	res := e.getHelper(name)
	if res == nil && e.parent != nil {
		return e.parent.Get(name)
	}
	return res
}

func (e *Env) getHelper(name string) types.Type {
	if el := e.GetNameInfo(name); el != nil {
		return el.Type
	}
	return nil
}

func (e *Env) GetNameInfoDirect(name string) *Info {
	return e.lookupTable[name]
}

func (e *Env) GetNameInfo(name string) *Info {
	info := e.lookupTable[name]
	if info == nil && e.parent != nil {
		return e.parent.GetNameInfo(name)
	}
	return info
}

func (e *Env) GetOrCreateNameInfo(name string) *Info {
	info := Or(e.GetNameInfoDirect(name), &Info{})
	e.lookupTable[name] = info
	return info
}

func (e *Env) GetFn(name string) types.FuncType {
	return types.Unwrap(e.Get(name)).(types.FuncType)
}

func (e *Env) DefineFn(nenv *Env, name string, fnStr string, opts ...SetTypeOption) {
	fnT := parseFuncTypeFromString(name, fnStr, nenv, nil)
	e.Define(nil, name, fnT, opts...)
}

func (e *Env) DefineFnNative(name string, fnStr string, fset *token.FileSet) {
	fnT := parseFuncDeclFromStringHelper(name, fnStr, e, fset)
	e.Define(nil, name, fnT)
}

func (e *Env) DefineFnNative2(name string, fnT types.FuncType, opts ...SetTypeOption) error {
	return e.Define1(nil, name, fnT, opts...)
}

func (e *Env) DefinePkg(name, path string) error {
	return e.DefineErr(nil, name, types.PackageType{Name: name, Path: path})
}

func (e *Env) DefineForce(n ast.Node, name string, typ types.Type) {
	err := e.defineHelper(n, name, typ, true)
	if err != nil {
		assert(false, err.Error())
	}
}

func (e *Env) Define(n ast.Node, name string, typ types.Type, opts ...SetTypeOption) {
	if err := e.Define1(n, name, typ, opts...); err != nil {
		assert(false, err.Error())
	}
}

func (e *Env) Define1(n ast.Node, name string, typ types.Type, opts ...SetTypeOption) error {
	err := e.defineHelper(n, name, typ, false, opts...)
	if err != nil {
		return err
	}
	return nil
}

func (e *Env) DefineErr(n ast.Node, name string, typ types.Type) error {
	return e.defineHelper(n, name, typ, false)
}

type DefinitionProvider struct {
	URI             string
	Line, Character int
}

func (e *Env) defineHelper(n ast.Node, name string, typ types.Type, force bool, opts ...SetTypeOption) error {
	if name == "_" {
		return nil
	}
	//p("DEF", name, typ, e.ID)
	conf := &SetTypeConf{}
	for _, opt := range opts {
		opt(conf)
	}
	if !force {
		t := e.GetDirect(name)
		if t != nil {
			//p("?", name, t, e.ID)
			//return fmt.Errorf("duplicate declaration of %s", name)
		}
	}
	info := e.GetOrCreateNameInfo(name)
	info.Type = typ
	if conf.definition1 != nil {
		info.Definition1 = *conf.definition1
	}
	if conf.description != "" {
		info.Message = conf.description
	}
	if n != nil {
		info.Definition = n.Pos()
		lookupInfo := e.lspNodeOrCreate(n)
		lookupInfo.Definition = n.Pos()
	}
	return nil
}

func (e *Env) lspNode(n ast.Node) *Info {
	return e.lspTable[e.makeKey(n)]
}

func (e *Env) lspNodeOrCreate(n ast.Node) *Info {
	info := Or(e.lspNode(n), &Info{})
	e.lspSetNode(n, info)
	return info
}

func (e *Env) lspSetNode(n ast.Node, info *Info) {
	e.lspTable[e.makeKey(n)] = info
}

func (e *Env) Assign(parentInfo *Info, n ast.Node, name string, fset *token.FileSet, mutEnforced bool) error {
	if name == "_" {
		return nil
	}
	t := e.Get(name)
	if t == nil {
		return fmt.Errorf("undeclared %s", name)
	}
	if mutEnforced && !TryCast[types.MutType](t) {
		return fmt.Errorf("cannot assign to immutable variable '%s'", name)
	}
	e.lspNodeOrCreate(n).Definition = parentInfo.Definition
	return nil
}

func (e *Env) SetType(p *Info, def1 *DefinitionProvider, x ast.Node, t types.Type, fset *token.FileSet) {
	assertf(t != nil, "%s: try to set type nil, %v %v", fset.Position(x.Pos()), x, to(x))
	info := e.lspNodeOrCreate(x)
	info.Type = t
	if def1 != nil {
		info.Definition1 = *def1
	}
	if p != nil {
		info.Message = p.Message
		info.Definition = p.Definition
	}
}

type NodeKey string

func makeKey(n ast.Node) NodeKey {
	return NodeKey(fmt.Sprintf("%d_%d_%v", n.Pos(), n.End(), reflect.TypeOf(n)))
}

func (e *Env) makeKey(x ast.Node) NodeKey {
	return makeKey(x)
}

func panicIfNil(t types.Type, typ any) {
	if t == nil {
		panic(fmt.Sprintf("type not found %v %v", typ, to(typ)))
	}
}

func (e *Env) GetInfo(x ast.Node) *Info {
	return e.lspNode(x)
}

func (e *Env) GetType(x ast.Node) types.Type {
	if v := e.lspNode(x); v != nil {
		return v.Type
	}
	return nil
}

func (e *Env) GetType2(x ast.Node, fset *token.FileSet) types.Type {
	res := e.getType2Helper(x, fset)
	if res == nil && e.parent != nil {
		return e.parent.GetType2(x, fset)
	}
	return res
}

func (e *Env) getType2Helper(x ast.Node, fset *token.FileSet) types.Type {
	if v := e.lspNode(x); v != nil {
		return v.Type
	}
	switch xx := x.(type) {
	case *ast.Ident:
		if v2 := e.GetNameInfo(xx.Name); v2 != nil {
			return v2.Type
		}
		if e.pkgName != "" {
			if v2 := e.GetNameInfo(e.pkgName + "." + xx.Name); v2 != nil {
				return v2.Type
			}
		}
		return nil
		//panic(fmt.Sprintf("%s: %v", e.fset.Position(xx.Pos()), xx.Name))
	case *ast.FuncType:
		return funcTypeToFuncType("", xx, e, fset, false)
	case *ast.FuncLit:
		return funcTypeToFuncType("", xx.Type, e, fset, false)
	case *ast.Ellipsis:
		return types.EllipsisType{Elt: e.GetType2(xx.Elt, fset)}
	case *ast.ArrayType:
		return types.ArrayType{Elt: e.GetType2(xx.Elt, fset)}
	case *ast.ResultExpr:
		return types.ResultType{W: e.GetType2(xx.X, fset)}
	case *ast.ErrExpr:
		return types.ResultType{W: e.GetType2(&ast.Ident{Name: "error"}, fset)}
	case *ast.OkExpr:
		return types.ResultType{W: e.GetType2(xx.X, fset)}
	case *ast.OptionExpr:
		return types.OptionType{W: e.GetType2(xx.X, fset)}
	case *ast.SomeExpr:
		return types.OptionType{W: e.GetType2(xx.X, fset)}
	case *ast.NoneExpr:
		return types.OptionType{W: nil}
	case *ast.CallExpr:
		if v, ok := xx.Fun.(*ast.Ident); ok {
			if v.Name == "make" {
				return e.GetType2(xx.Args[0], fset)
			}
		}
		t := e.GetType2(xx.Fun, fset)
		if v, ok := t.(types.TypeType); ok {
			return v.W
		}
		t = types.Unwrap(t)
		// Check for error type to avoid cascading panics
		if _, ok := t.(types.ErrorType); ok {
			return types.ErrorType{}
		}
		return t.(types.FuncType).Return
	case *ast.BasicLit:
		switch xx.Kind {
		case token.INT:
			return types.UntypedNumType{}
		case token.STRING:
			return types.UntypedStringType{}
		default:
			panic(fmt.Sprintf("%v", xx.Kind))
		}
	case *ast.SelectorExpr:
		base := e.GetType2(xx.X, fset)
		base = types.Unwrap(base)
		switch v := base.(type) {
		case types.PackageType:
			name := fmt.Sprintf("%s.%s", v.Name, xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.InterfaceType:
			return e.GetType2(&ast.Ident{Name: v.NameStr()}, fset)
		case types.StructType:
			name := v.GetFieldName(xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.TupleType:
			idx, err := strconv.Atoi(xx.Sel.Name)
			if err != nil {
				panic("")
			}
			return v.Elts[idx]
		case types.TypeAssertType:
			return v.X
		case types.ArrayType:
			name := fmt.Sprintf("agl1.Vec.%s", xx.Sel.Name)
			methodType := e.GetType2(&ast.Ident{Name: name}, fset)
			if methodType == nil {
				errMsg := fmt.Sprintf("method '.%s' does not exist on array type", xx.Sel.Name)
				// Add a suggestion if it's a common mistake
				if xx.Sel.Name == "Join" {
					errMsg += "\n    did you mean '.Joined()' instead?"
				}
				// If we have an inferrer, collect the error; otherwise panic
				if e.inferrer != nil {
					e.inferrer.AddError(xx.Sel, errMsg)
					return types.ErrorType{} // Return error type to prevent further panics
				}
				panic(fmt.Sprintf("%s: %s", e.fset.Position(xx.Sel.Pos()), errMsg))
			}
			return methodType
		case types.SetType:
			name := fmt.Sprintf("agl1.Set.%s", xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.OptionType:
			name := fmt.Sprintf("Option.%s", xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.EnumType:
			return v
		case types.CustomType:
			name := fmt.Sprintf("%s.%s.%s", v.Pkg, v.Name, xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.StringType:
			name := fmt.Sprintf("agl1.String.%s", xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.I64Type:
			name := fmt.Sprintf("agl1.I64.%s", xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.ErrorType:
			// Propagate error type to avoid cascading panics
			return types.ErrorType{}
		default:
			//return nil
			panic(fmt.Sprintf("%s: %v %v", e.fset.Position(xx.X.Pos()), xx.X, reflect.TypeOf(base)))
		}
		return nil
	case *ast.IndexExpr:
		t := e.GetType2(xx.X, fset)
		t = types.Unwrap(t)
		if !e.NoIdxUnwrap {
			switch v := t.(type) {
			case types.ArrayType:
				return v.Elt
			}
		}
		switch v := t.(type) {
		case types.StructType:
			t = v.RenameGenericParameter("T", xx.Index.(*ast.Ident).Name)
		case types.FuncType:
			t = v.RenameGenericParameter("V", "T")
		case types.MapType:
			t = v.V
		case types.EnumType:
		case types.SetType:
		case types.InterfaceType:
		case types.ArrayType:
		default:
			panic(fmt.Sprintf("%s: %v %v", e.fset.Position(xx.X.Pos()), t, to(t)))
		}
		return t
	case *ast.ParenExpr:
		return e.GetType2(xx.X, fset)
	case *ast.VoidExpr:
		return types.VoidType{}
	case *ast.StarExpr:
		return types.StarType{X: e.GetType2(xx.X, fset)}
	case *ast.MapType:
		return types.MapType{K: e.GetType2(xx.Key, fset), V: e.GetType2(xx.Value, fset)}
	case *ast.SetType:
		return types.SetType{K: e.GetType2(xx.Key, fset)}
	case *ast.ChanType:
		return types.ChanType{W: e.GetType2(xx.Value, fset)}
	case *ast.TupleExpr:
		elts := make([]types.Type, len(xx.Values))
		for i, v := range xx.Values {
			elts[i] = e.GetType2(v, fset)
		}
		return types.TupleType{Elts: elts}
	case *ast.BinaryExpr:
		switch xx.Op {
		case token.EQL, token.NEQ, token.LOR, token.LAND, token.LEQ, token.LSS, token.GEQ, token.GTR, token.IN:
			return types.BoolType{}
		default:
			return e.GetType2(xx.X, fset)
		}
	case *ast.UnaryExpr:
		if xx.Op == token.SUB || xx.Op == token.ADD {
			if _, ok := xx.X.(*ast.BasicLit); ok {
				return e.GetType2(xx.X, fset)
			}
		}
		return types.UnaryType{X: e.GetType2(xx.X, fset)}
	case *ast.InterfaceType:
		return types.AnyType{}
	case *ast.CompositeLit:
		switch v := e.GetType2(xx.Type, fset).(type) {
		case types.CustomType:
			return types.StructType{Pkg: v.Pkg, Name: v.Name}
		case types.ArrayType:
			return e.GetType2(xx.Type, fset)
		case types.SetType:
			return e.GetType2(xx.Type, fset)
		case types.StructType:
			return types.StructType{Pkg: v.Pkg, Name: v.Name}
		case types.MapType:
			return e.GetType2(xx.Type, fset)
		case types.TypeType:
			return v.W
		default:
			//return nil
			panic(fmt.Sprintf("%s: %v %v", e.fset.Position(xx.Pos()), xx.Type, reflect.TypeOf(v)))
		}
	case *ast.TypeAssertExpr:
		xT := e.GetType2(xx.X, fset)
		var typeT types.Type
		if xx.Type != nil {
			typeT = e.GetType2(xx.Type, fset)
		}
		n := types.TypeAssertType{X: xT, Type: typeT}
		info := e.lspNodeOrCreate(xx)
		info.Type = types.OptionType{W: xT} // TODO ensure xT is the right thing
		return n
	case *ast.BubbleOptionExpr:
		return e.GetType2(xx.X, fset).(types.OptionType).W
	case *ast.BubbleResultExpr:
		return e.GetType2(xx.X, fset).(types.ResultType).W
	case *ast.SliceExpr:
		return e.GetType2(xx.X, fset) // TODO
	case *ast.IndexListExpr:
		return e.GetType2(xx.X, fset) // TODO
	case *ast.StructType:
		return types.StructType{}
	case *ast.LabelledArg:
		return e.GetType2(xx.X, fset)
	case *ast.IfExpr:
		return e.GetType2(xx.Body, fset)
	case *ast.BlockStmt:
		if len(xx.List) == 0 {
			return types.VoidType{}
		}
		return e.GetType2(xx.List[len(xx.List)-1], fset)
	case *ast.ExprStmt:
		return e.GetType2(xx.X, fset)
	case *ast.ReturnStmt:
		if xx.Result == nil {
			return types.VoidType{}
		}
		return e.GetType2(xx.Result, fset)
	default:
		//return nil
		panic(fmt.Sprintf("unhandled type %v %v", xx, reflect.TypeOf(xx)))
	}
}

func (e *Env) GetGoType2(pkgName string, x goast.Node, keepRaw bool) types.Type {
	res := e.getGoType2Helper(pkgName, x, keepRaw)
	if res == nil && e.parent != nil {
		return e.parent.GetGoType2(pkgName, x, keepRaw)
	}
	return res
}

func (e *Env) getGoType2Helper(pkgName string, x goast.Node, keepRaw bool) types.Type {
	switch xx := x.(type) {
	case *goast.Ident:
		if v2 := e.GetNameInfo(xx.Name); v2 != nil {
			return v2.Type
		}
		if v2 := e.GetNameInfo(pkgName + "." + xx.Name); v2 != nil {
			return v2.Type
		}
		return nil
	case *goast.FuncType:
		return goFuncTypeToFuncType("", pkgName, xx, e, true)
	case *goast.Ellipsis:
		t := e.GetGoType2(pkgName, xx.Elt, keepRaw)
		if t == nil {
			return nil
		}
		return types.EllipsisType{Elt: t}
	case *goast.ArrayType:
		t := e.GetGoType2(pkgName, xx.Elt, keepRaw)
		if t == nil {
			return nil
		}
		return types.ArrayType{Elt: t}
	case *goast.CallExpr:
		return nil
	case *goast.BasicLit:
		switch xx.Kind {
		case gotoken.INT:
			return types.UntypedNumType{}
		case gotoken.STRING:
			return types.UntypedStringType{}
		default:
			panic(fmt.Sprintf("%v", xx.Kind))
		}
	case *goast.SelectorExpr:
		base := e.GetGoType2(pkgName, xx.X, keepRaw)
		base = types.Unwrap(base)
		switch v := base.(type) {
		case types.PackageType:
			name := fmt.Sprintf("%s.%s", v.Name, xx.Sel.Name)
			return e.GetGoType2(pkgName, &goast.Ident{Name: name}, keepRaw)
		case types.InterfaceType:
			return e.GetGoType2(pkgName, &goast.Ident{Name: v.NameStr()}, keepRaw)
		case types.StructType:
			name := v.GetFieldName(xx.Sel.Name)
			return e.GetGoType2(pkgName, &goast.Ident{Name: name}, keepRaw)
		case types.TypeAssertType:
			return v.X
		default:
			//return types.VoidType{}
			panic(fmt.Sprintf("%v %v %v", reflect.TypeOf(base), xx, xx.X))
		}
		return nil
	case *goast.IndexExpr:
		t := e.GetGoType2(pkgName, xx.X, keepRaw)
		if !e.NoIdxUnwrap {
			switch v := t.(type) {
			case types.ArrayType:
				return v.Elt
			}
		}
		return t
	case *goast.ParenExpr:
		return e.GetGoType2(pkgName, xx.X, keepRaw)
	case *goast.StarExpr:
		return types.StarType{X: e.GetGoType2(pkgName, xx.X, keepRaw)}
	case *goast.MapType:
		return types.MapType{K: e.GetGoType2(pkgName, xx.Key, keepRaw), V: e.GetGoType2(pkgName, xx.Value, keepRaw)}
	case *goast.ChanType:
		return types.ChanType{W: e.GetGoType2(pkgName, xx.Value, keepRaw)}
	case *goast.BinaryExpr:
		return types.BinaryType{X: e.GetGoType2(pkgName, xx.X, keepRaw), Y: e.GetGoType2(pkgName, xx.Y, keepRaw)}
	case *goast.UnaryExpr:
		return types.UnaryType{X: e.GetGoType2(pkgName, xx.X, keepRaw)}
	case *goast.InterfaceType:
		return types.AnyType{}
	case *goast.CompositeLit:
		ct := e.GetGoType2(pkgName, xx.Type, keepRaw).(types.CustomType)
		return types.StructType{Pkg: ct.Pkg, Name: ct.Name}
	case *goast.SliceExpr:
		return e.GetGoType2(pkgName, xx.X, keepRaw) // TODO
	case *goast.IndexListExpr:
		return e.GetGoType2(pkgName, xx.X, keepRaw) // TODO
	case *goast.StructType:
		return types.StructType{}
	case *goast.Field:
		return e.GetGoType2(pkgName, xx.Type, keepRaw)
	default:
		panic(fmt.Sprintf("unhandled type %v %v", xx, reflect.TypeOf(xx)))
	}
}
