package shared

type SymbolTable[T any] struct {
	scopes []map[string]T
}

func NewSymbolTable[T any](_initial ...map[string]T) *SymbolTable[T] {
	initial := map[string]T{}
	if len(_initial) > 0 {
		initial = _initial[0]
	}
	return &SymbolTable[T]{scopes: []map[string]T{initial}}
}

func (st *SymbolTable[T]) EnterScope() {
	st.scopes = append(st.scopes, make(map[string]T))
}

func (st *SymbolTable[T]) ExitScope() {
	if len(st.scopes) > 1 {
		st.scopes = st.scopes[:len(st.scopes)-1]
	} else {
		panic("cannot exit global scope")
	}
}

func (st *SymbolTable[T]) Define(name string, value T) bool {
	currentScope := st.scopes[len(st.scopes)-1]
	if _, ok := st.Lookup(name); ok {
		return false
	}
	currentScope[name] = value

	return true
}

func (st *SymbolTable[T]) Lookup(name string) (T, bool) {
	for i := len(st.scopes) - 1; i >= 0; i-- {
		if value, found := st.scopes[i][name]; found {
			return value, true
		}
	}
	var zeroVal T
	return zeroVal, false
}