package main

import "fmt"

// section: definition
type Stack[T any] struct {
	items []T
}

func NewStack[T any]() *Stack[T] {
	return &Stack[T]{
		items: make([]T, 0),
	}
}

// section: definition

// section: methods
func (s *Stack[T]) Push(item T) {
	s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
	if len(s.items) == 0 {
		var zero T
		return zero, false
	}

	index := len(s.items) - 1
	item := s.items[index]
	clear(s.items[index:])
	s.items = s.items[:index]
	return item, true
}

func (s *Stack[T]) Peek() (T, bool) {
	if len(s.items) == 0 {
		var zero T
		return zero, false
	}
	return s.items[len(s.items)-1], true
}

func (s *Stack[T]) Size() int {
	return len(s.items)
}

func (s *Stack[T]) IsEmpty() bool {
	return len(s.items) == 0
}

// section: methods

func main() {
	// section: usage
	// Stack of integers
	intStack := NewStack[int]()
	intStack.Push(1)
	intStack.Push(2)
	intStack.Push(3)

	fmt.Println("Int stack size:", intStack.Size())

	if value, ok := intStack.Pop(); ok {
		fmt.Println("Popped from int stack:", value)
	}

	if value, ok := intStack.Peek(); ok {
		fmt.Println("Peeked at int stack:", value)
	}

	// Stack of strings
	stringStack := NewStack[string]()
	stringStack.Push("hello")
	stringStack.Push("world")

	fmt.Println("String stack size:", stringStack.Size())

	for !stringStack.IsEmpty() {
		if value, ok := stringStack.Pop(); ok {
			fmt.Println("Popped from string stack:", value)
		}
	}
	// section: usage
}
