package main

import (
	"bytes"
	"cmp"
	"encoding/binary"
	"fmt"
	"hash/fnv"
	"io"
	"iter"
	"maps"
	"math"
	"net/http"
	"slices"
	"sort"
	"strconv"
	"strings"
)

type Signed interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64
}

type Unsigned interface {
	~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}

type Integer interface {
	Signed | Unsigned
}

type Float interface {
	~float32 | ~float64
}

type Complex interface {
	~complex64 | ~complex128
}

type Number interface {
	Integer | Float
}

func AglBytesEqual(a, b []byte) bool {
	return bytes.Equal(a, b)
}

func AglWrapNative1(err error) Result[AglVoid] {
	if err != nil {
		return MakeResultErr[AglVoid](err)
	}
	return MakeResultOk(AglVoid{})
}

func AglWrapNative2[T any](v1 T, err error) Result[T] {
	if err != nil {
		return MakeResultErr[T](err)
	}
	return MakeResultOk(v1)
}

func AglWrapNativeOpt[T any](v1 T, ok bool) Option[T] {
	if !ok {
		return MakeOptionNone[T]()
	}
	return MakeOptionSome(v1)
}

type AglVoid struct{}

type Option[T any] struct {
	t *T
}

func (o Option[T]) String() string {
	if o.IsNone() {
		return "None"
	}
	return fmt.Sprintf("Some(%v)", *o.t)
}

func (o Option[T]) IsSome() bool {
	return o.t != nil
}

func (o Option[T]) IsNone() bool {
	return o.t == nil
}

func (o Option[T]) Unwrap() T {
	if o.IsNone() {
		panic("unwrap on a None value")
	}
	return *o.t
}

func (o Option[T]) UnwrapOr(d T) T {
	if o.IsNone() {
		return d
	}
	return *o.t
}

func (o Option[T]) UnwrapOrDefault() T {
	var zero T
	if o.IsNone() {
		return zero
	}
	return *o.t
}

func AglOptionMap[T, R any](o Option[T], clb func(T) R) Option[R] {
	if o.IsSome() {
		return MakeOptionSome(clb(o.Unwrap()))
	}
	return MakeOptionNone[R]()
}

func MakeOptionSome[T any](t T) Option[T] {
	return Option[T]{t: &t}
}

func MakeOptionNone[T any]() Option[T] {
	return Option[T]{t: nil}
}

type Result[T any] struct {
	t *T
	e error
}

func (r Result[T]) String() string {
	if r.IsErr() {
		return fmt.Sprintf("Err(%v)", r.e)
	}
	return fmt.Sprintf("Ok(%v)", *r.t)
}

func (r Result[T]) IsErr() bool {
	return r.e != nil
}

func (r Result[T]) IsOk() bool {
	return r.e == nil
}

func (r Result[T]) Unwrap() T {
	if r.IsErr() {
		panic(fmt.Sprintf("unwrap on an Err value: %s", r.e))
	}
	return *r.t
}

func (r Result[T]) NativeUnwrap() (*T, error) {
	return r.t, r.e
}

func (r Result[T]) UnwrapOr(d T) T {
	if r.IsErr() {
		return d
	}
	return *r.t
}

func (r Result[T]) UnwrapOrDefault() T {
	var zero T
	if r.IsErr() {
		return zero
	}
	return *r.t
}

func (r Result[T]) Err() error {
	return r.e
}

func MakeResultOk[T any](t T) Result[T] {
	return Result[T]{t: &t, e: nil}
}

func MakeResultErr[T any](err error) Result[T] {
	return Result[T]{t: nil, e: err}
}

func AglVecMap[T, R any](a []T, f func(T) R) []R {
	var out []R
	for _, v := range a {
		out = append(out, f(v))
	}
	return out
}

func AglVecFilterMap[T, R any](a []T, f func(T) Option[R]) []R {
	var out []R
	for _, v := range a {
		res := f(v)
		if res.IsSome() {
			out = append(out, res.Unwrap())
		}
	}
	return out
}

func AglVec__ADD[T any](a, b []T) []T {
	out := make([]T, 0, len(a)+len(b))
	out = append(out, a...)
	out = append(out, b...)
	return out
}

func AglVecFilter[T any](a []T, f func(T) bool) []T {
	var out []T
	for _, v := range a {
		if f(v) {
			out = append(out, v)
		}
	}
	return out
}

func AglVecFirstIndex[T comparable](a []T, e T) Option[int] {
	for i, v := range a {
		if v == e {
			return MakeOptionSome(i)
		}
	}
	return MakeOptionNone[int]()
}

func AglVecFirstIndexWhere[T any](a []T, p func(T) bool) Option[int] {
	for i, v := range a {
		if p(v) {
			return MakeOptionSome(i)
		}
	}
	return MakeOptionNone[int]()
}

func AglVecAllSatisfy[T any](a []T, f func(T) bool) bool {
	for _, v := range a {
		if !f(v) {
			return false
		}
	}
	return true
}

func AglVecContains[T comparable](a []T, e T) bool {
	for _, v := range a {
		if v == e {
			return true
		}
	}
	return false
}

func AglVecContainsWhere[T comparable](a []T, p func(T) bool) bool {
	for _, v := range a {
		if p(v) {
			return true
		}
	}
	return false
}

func AglVecAny[T any](a []T, f func(T) bool) bool {
	for _, v := range a {
		if f(v) {
			return true
		}
	}
	return false
}

func AglVecReduce[T, R any](a []T, acc R, f func(R, T) R) R {
	for _, v := range a {
		acc = f(acc, v)
	}
	return acc
}

func AglVecReduceInto[T, R any](a []T, acc R, f func(*R, T) AglVoid) R {
	for _, v := range a {
		f(&acc, v)
	}
	return acc
}

func AglMapReduce[K comparable, V, R any](m map[K]V, acc R, f func(R, DictEntry[K, V]) R) R {
	for k, v := range m {
		acc = f(acc, DictEntry[K, V]{Key: k, Value: v})
	}
	return acc
}

func AglMapReduceInto[K comparable, V, R any](m map[K]V, acc R, f func(*R, DictEntry[K, V]) AglVoid) R {
	for k, v := range m {
		f(&acc, DictEntry[K, V]{Key: k, Value: v})
	}
	return acc
}

func AglPrint(a ...any) {
	fmt.Println(a...)
}

func AglPrintln(a ...any) {
	fmt.Println(a...)
}

func AglPrintf(format string, a ...any) {
	fmt.Printf(format+"\n", a...)
}

func AglPrecondition(pred bool, msg ...string) {
	if !pred {
		m := ""
		if len(msg) > 0 {
			m = msg[0]
		}
		panic(m)
	}
}

func AglAssert(pred bool, msg ...string) {
	if !pred {
		m := ""
		if len(msg) > 0 {
			m = msg[0]
		}
		panic(m)
	}
}

func AglAssertEq[T comparable](left, right T, msg ...string) {
	if left != right {
		m := ""
		if len(msg) > 0 {
			m = msg[0]
		}
		m = fmt.Sprintf("%s, left: %v, right: %v", m, left, right)
		panic(m)
	}
}

func AglVecIn[T cmp.Ordered](a []T, v T) bool {
	for _, el := range a {
		if el == v {
			return true
		}
	}
	return false
}

func AglNoop(_ ...any) Result[AglVoid] { return MakeResultOk(AglVoid{}) }

func AglTypeAssert[T any](v any) Option[T] {
	if v, ok := v.(T); ok {
		return MakeOptionSome(v)
	}
	return MakeOptionNone[T]()
}

func AglIdentity[T any](v T) T { return v }

func AglVecFind[T any](a []T, f func(T) bool) Option[T] {
	for _, v := range a {
		if f(v) {
			return MakeOptionSome(v)
		}
	}
	return MakeOptionNone[T]()
}

type VecIter[T any] struct {
	v AglVec[T]
	i int
}

func (v *VecIter[T]) Next() Option[T] {
	if v.i >= len(v.v) {
		return MakeOptionNone[T]()
	}
	res := v.v[v.i]
	v.i++
	return MakeOptionSome(res)
}

type IntoIterator[T any] interface {
	Iter() Iterator[T]
}

// Sequence anything that can be turned into an Iterator
type Sequence[T any] iter.Seq[T]

func (s Sequence[T]) Iter() Sequence[T] {
	return s
}

func AglSequenceSum[T, R Number](s Sequence[T]) (out R) {
	for e := range s {
		out += R(e)
	}
	return out
}

func AglSequenceLen[T any](s Sequence[T]) (out int) {
	for range s {
		out++
	}
	return
}

func AglSequenceFilter[T any](s Sequence[T], f func(T) bool) Sequence[T] {
	return func(yield func(T) bool) {
		for v := range s {
			if f(v) {
				if !yield(v) {
					return
				}
			}
		}
	}
}

func AglSequenceContains[T comparable](s Sequence[T], e T) bool {
	for v := range s {
		if v == e {
			return true
		}
	}
	return false
}

func AglSequenceContainsWhere[T any](s Sequence[T], pred func(T) bool) bool {
	for v := range s {
		if pred(v) {
			return true
		}
	}
	return false
}

func AglSequenceAllSatisfy[T any](s Sequence[T], pred func(T) bool) bool {
	for v := range s {
		if !pred(v) {
			return false
		}
	}
	return true
}

func AglSequenceMap[T, R any](s Sequence[T], f func(T) R) []R {
	var out []R
	for v := range s {
		out = append(out, f(v))
	}
	return out
}

func AglSequenceReduce[T, R any](s Sequence[T], acc R, f func(R, T) R) R {
	for v := range s {
		acc = f(acc, v)
	}
	return acc
}

func AglSequenceReduceInto[T, R any](s Sequence[T], acc R, f func(*R, T) AglVoid) R {
	for v := range s {
		f(&acc, v)
	}
	return acc
}

func AglSequenceForEach[T any](s Sequence[T], f func(T) AglVoid) {
	for v := range s {
		f(v)
	}
}

func AglSequenceSorted[T cmp.Ordered](s Sequence[T]) []T {
	return slices.Sorted(iter.Seq[T](s))
}

func AglSequenceJoined(s Sequence[string], sep string) string {
	return strings.Join(AglBuildArray(s), sep)
}

type AglVec[T any] []T

func (v AglVec[T]) Len() int { return len(v) }

func (v AglVec[T]) Iter() Sequence[T] {
	return func(yield func(T) bool) {
		for _, e := range v {
			if !yield(e) {
				return
			}
		}
	}
}

func AglVecIter[T any](v AglVec[T]) Sequence[T] {
	return v.Iter()
}

type DictEntry[K comparable, V any] struct {
	Key   K
	Value V
}

type AglMap[K comparable, V any] map[K]V

func (m AglMap[K, V]) Iter() Sequence[K] { return AglMapKeys(m) }

func (m AglMap[K, V]) Len() int { return len(m) }

type Hashable[T any] interface {
	Equatable[T]
	Hash() uint64
}

type Equatable[T any] interface {
	__EQ(rhs T) bool
}

type AglInt struct{ int }

func (i AglInt) __EQ(rhs AglInt) bool { return i == rhs }

func (i AglInt) Hash() uint64 {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, uint64(i.int))
	return fnvHash(buf)
}

type AglI8 struct{ int8 }

func (i AglI8) __EQ(rhs AglI8) bool { return i == rhs }

func (i AglI8) Hash() uint64 {
	return fnvHash([]byte{byte(i.int8)})
}

type AglI16 struct{ int16 }

func (i AglI16) __EQ(rhs AglI16) bool { return i == rhs }

func (i AglI16) Hash() uint64 {
	buf := make([]byte, 2)
	binary.BigEndian.PutUint16(buf, uint16(i.int16))
	return fnvHash(buf)
}

type AglI32 struct{ int32 }

func (i AglI32) __EQ(rhs AglI32) bool { return i == rhs }

func (i AglI32) Hash() uint64 {
	buf := make([]byte, 4)
	binary.BigEndian.PutUint32(buf, uint32(i.int32))
	return fnvHash(buf)
}

type AglI64 struct{ int64 }

func (i AglI64) __EQ(rhs AglI64) bool { return i == rhs }

func (i AglI64) Hash() uint64 {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, uint64(i.int64))
	return fnvHash(buf)
}

type AglUint struct{ uint }

func (i AglUint) __EQ(rhs AglUint) bool { return i == rhs }

func (i AglUint) Hash() uint64 {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, uint64(i.uint))
	return fnvHash(buf)
}

type AglU8 struct{ uint8 }

func (i AglU8) __EQ(rhs AglU8) bool { return i == rhs }

func (i AglU8) Hash() uint64 {
	return fnvHash([]byte{i.uint8})
}

type AglU16 struct{ uint16 }

func (i AglU16) __EQ(rhs AglU16) bool { return i == rhs }

func (i AglU16) Hash() uint64 {
	buf := make([]byte, 2)
	binary.BigEndian.PutUint16(buf, i.uint16)
	return fnvHash(buf)
}

type AglU32 struct{ uint32 }

func (i AglU32) __EQ(rhs AglU32) bool { return i == rhs }

func (i AglU32) Hash() uint64 {
	buf := make([]byte, 4)
	binary.BigEndian.PutUint32(buf, i.uint32)
	return fnvHash(buf)
}

type AglU64 struct{ uint64 }

func (i AglU64) __EQ(rhs AglU64) bool { return i == rhs }

func (i AglU64) Hash() uint64 {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, i.uint64)
	return fnvHash(buf)
}

type AglF32 struct{ float32 }

func (f AglF32) __EQ(rhs AglF32) bool { return f == rhs }

func (f AglF32) Hash() uint64 {
	bits := math.Float32bits(f.float32)
	buf := make([]byte, 4)
	binary.BigEndian.PutUint32(buf, bits)
	return fnvHash(buf)
}

type AglF64 struct{ float64 }

func (f AglF64) __EQ(rhs AglF64) bool { return f == rhs }

func (f AglF64) Hash() uint64 {
	bits := math.Float64bits(f.float64)
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, bits)
	return fnvHash(buf)
}

type AglString struct{ string }

func (s AglString) __EQ(rhs AglString) bool { return s == rhs }

func (s AglString) Hash() uint64 {
	return fnvHash([]byte(s.string))
}

func fnvHash(by []byte) uint64 {
	h := fnv.New64a()
	_, _ = h.Write(by)
	return h.Sum64()
}

type DictEntry1[K Hashable[K], V any] struct {
	Key   K
	Value V
}

type AglMap1[K Hashable[K], V any] struct {
	data map[uint64]DictEntry1[K, V]
}

type AglSet1[H Hashable[H]] struct {
	data map[uint64]H
}

func (s *AglSet1[H]) Insert(e H) bool {
	if s.data == nil {
		s.data = make(map[uint64]H)
	}
	eH := e.Hash()
	if _, ok := s.data[eH]; ok {
		return false
	}
	s.data[eH] = e
	return true
}

func (s AglSet1[H]) __EQ(rhs AglSet1[H]) bool {
	if len(s.data) != len(rhs.data) {
		return false
	}
	for _, v := range s.data {
		if !rhs.Contains(v) {
			return false
		}
	}
	return true
}

func (s AglSet1[H]) Hash() uint64 {
	hashes := make([]uint64, len(s.data))
	i := 0
	for k := range s.data {
		hashes[i] = k
		i++
	}
	sort.Slice(hashes, func(i, j int) bool { return hashes[i] < hashes[j] })
	var final uint64
	for _, h := range hashes {
		final ^= h
	}
	return final
}

func (s *AglSet1[H]) Contains(e H) bool {
	_, ok := s.data[e.Hash()]
	return ok
}

type AglSet[T comparable] map[T]struct{}

func (s AglSet[T]) Len() int { return len(s) }

type AglRange[T Integer] struct {
	Start, End T
	IsEq       bool // ".." vs "..="
}

func AglNewRange[T Integer](start, end T, isEq bool) *AglRange[T] {
	r := &AglRange[T]{Start: start, End: end, IsEq: isEq}
	return r
}

func (r *AglRange[T]) AllSatisfy(pred func(T) bool) bool {
	for {
		if el := r.Next(); el.IsSome() {
			if !pred(el.Unwrap()) {
				return false
			}
		} else {
			return true
		}
	}
}

func (r *AglRange[T]) Next() Option[T] {
	if (r.IsEq && r.Start > r.End) || (!r.IsEq && r.Start >= r.End) {
		return MakeOptionNone[T]()
	}
	res := MakeOptionSome(r.Start)
	r.Start++
	return res
}

func (r *AglRange[T]) NextBack() Option[T] {
	if (r.IsEq && r.Start > r.End) || (!r.IsEq && r.Start >= r.End) {
		return MakeOptionNone[T]()
	}
	if r.IsEq {
		res := MakeOptionSome(r.End)
		r.End--
		return res
	} else {
		r.End--
		return MakeOptionSome(r.End)
	}
}

func (r *AglRange[T]) Iter() Sequence[T] {
	return func(yield func(T) bool) {
		for {
			if el := r.Next(); el.IsSome() {
				if !yield(el.Unwrap()) {
					return
				}
			} else {
				return
			}
		}
	}
}

type Iterator[T any] interface {
	Iter() Sequence[T]
	//Next() Option[T]
}

type DoubleEndedIterator[T any] interface {
	Iterator[T]
	Next() Option[T]
	NextBack() Option[T]
}

type Rev[T any] struct {
	it DoubleEndedIterator[T]
}

func (r Rev[T]) Iter() Sequence[T] {
	return func(yield func(T) bool) {
		for {
			if el := r.Next(); el.IsSome() {
				if !yield(el.Unwrap()) {
					return
				}
			} else {
				return
			}
		}
	}
}

func (r *Rev[T]) Next() Option[T] {
	return r.it.NextBack()
}

func (r *Rev[T]) NextBack() Option[T] {
	return r.it.Next()
}

func AglDoubleEndedIteratorRev[T any, I DoubleEndedIterator[T]](it I) *Rev[T] {
	return &Rev[T]{it: it}
}

func AglIteratorAllSatisfy[T any, I Iterator[T]](it I, pred func(T) bool) bool {
	return AglSequenceAllSatisfy(it.Iter(), pred)
}

func AglIteratorContains[T comparable, I Iterator[T]](it I, el T) bool {
	return AglSequenceContains(it.Iter(), el)
}

func (s AglSet[T]) Iter() Sequence[T] {
	return func(yield func(T) bool) {
		for k := range s {
			if !yield(k) {
				return
			}
		}
	}
}

func AglSetIter[T comparable](s AglSet[T]) Sequence[T] {
	return Sequence[T](s.Iter())
}

func (s AglSet[T]) String() string {
	var tmp []string
	for k := range s {
		tmp = append(tmp, fmt.Sprintf("%v", k))
	}
	return fmt.Sprintf("set(%s)", strings.Join(tmp, " "))
}

func (s AglSet[T]) Intersects(other Iterator[T]) bool {
	return AglSetIntersects(s, other)
}

func (s AglSet[T]) Contains(el T) bool {
	return AglSetContains(s, el)
}

func AglSetLen[T comparable](s AglSet[T]) int {
	return len(s)
}

func AglIterMin[T cmp.Ordered](it Sequence[T]) Option[T] {
	var out T
	first := true
	for e := range it {
		if first {
			out = e
			first = false
		} else {
			out = min(out, e)
		}
	}
	if first {
		return MakeOptionNone[T]()
	}
	return MakeOptionSome(out)
}

func AglIterMax[T cmp.Ordered](it Sequence[T]) Option[T] {
	var out T
	first := true
	for e := range it {
		if first {
			out = e
			first = false
		} else {
			out = max(out, e)
		}
	}
	if first {
		return MakeOptionNone[T]()
	}
	return MakeOptionSome(out)
}

func AglSetMin[T cmp.Ordered](s AglSet[T]) Option[T] {
	return AglIterMin(s.Iter())
}

func AglSetMax[T cmp.Ordered](s AglSet[T]) Option[T] {
	return AglIterMax(s.Iter())
}

// AglSetEquals returns a Boolean value indicating whether two sets have equal elements.
func AglSetEquals[T comparable](s, other AglSet[T]) bool {
	if len(s) != len(other) {
		return false
	}
	for k := range s {
		if _, ok := other[k]; !ok {
			return false
		}
	}
	return true
}

func AglSetInsert[T comparable](s AglSet[T], el T) bool {
	if _, ok := s[el]; ok {
		return false
	}
	s[el] = struct{}{}
	return true
}

// AglSetContains returns a Boolean value that indicates whether the given element exists in the set.
func AglSetContains[T comparable](s AglSet[T], el T) bool {
	_, ok := s[el]
	return ok
}

func AglSetContainsWhere[T comparable](s AglSet[T], p func(T) bool) bool {
	for k := range s {
		if p(k) {
			return true
		}
	}
	return false
}

// AglSetRemove removes the specified element from the set.
// Return: The value of the member parameter if it was a member of the set; otherwise, nil.
func AglSetRemove[T comparable](s AglSet[T], el T) Option[T] {
	if _, ok := s[el]; ok {
		delete(s, el)
		return MakeOptionSome(el)
	}
	return MakeOptionNone[T]()
}

func AglSetIsEmpty[T comparable](s AglSet[T]) bool {
	return len(s) == 0
}

func AglSetRemoveFirst[T comparable](s AglSet[T]) T {
	for k := range s {
		delete(s, k)
		return k
	}
	panic("set is empty")
}

func AglSetFirst[T comparable](s AglSet[T]) (out Option[T]) {
	if len(s) > 0 {
		for k := range s {
			return MakeOptionSome(k)
		}
	}
	return MakeOptionNone[T]()
}

func AglSetFirstWhere[T comparable](s AglSet[T], predicate func(T) bool) (out Option[T]) {
	if len(s) == 0 {
		return MakeOptionNone[T]()
	}
	for k := range s {
		if predicate(k) {
			return MakeOptionSome(k)
		}
	}
	return MakeOptionNone[T]()
}

//func AglIteratorEach[T any](it Iterator[T]) aglImportIter.Seq[T] {
//	return func(yield func(T) bool) {
//		for {
//			if el := it.Next(); el.IsSome() {
//				if !yield(el.Unwrap()) {
//					return
//				}
//			} else {
//				return
//			}
//		}
//	}
//}
//
//func AglSequenceEach[T any](seq Sequence[T]) aglImportIter.Seq[T] {
//	return AglIteratorEach(seq.Iter())
//}

func AglSetMap[T comparable, R any](s AglSet[T], f func(T) R) []R {
	var out []R
	for k := range s {
		out = append(out, f(k))
	}
	return out
}

func AglSetFilter[T comparable](s AglSet[T], pred func(T) bool) AglSet[T] {
	newSet := make(AglSet[T])
	for k := range s {
		if pred(k) {
			newSet[k] = struct{}{}
		}
	}
	return newSet
}

// AglSetUnion returns a new set with the elements of both this set and the given sequence.
func AglSetUnion[T comparable](s AglSet[T], other Iterator[T]) AglSet[T] {
	newSet := make(AglSet[T])
	for k := range s {
		newSet[k] = struct{}{}
	}
	for k := range other.Iter() {
		newSet[k] = struct{}{}
	}
	return newSet
}

// AglSetFormUnion inserts the elements of the given sequence into the set.
func AglSetFormUnion[T comparable](s AglSet[T], other Sequence[T]) {
	for k := range other {
		s[k] = struct{}{}
	}
}

func AglSetSubtracting[T comparable](s AglSet[T], other Iterator[T]) AglSet[T] {
	newSet := make(AglSet[T])
	for k := range s {
		newSet[k] = struct{}{}
	}
	for k := range other.Iter() {
		delete(newSet, k)
	}
	return newSet
}

func AglSetSubtract[T comparable](s AglSet[T], other Iterator[T]) {
	for k := range other.Iter() {
		delete(s, k)
	}
}

// AglSetIntersection returns a new set with the elements that are common to both this set and the given sequence.
func AglSetIntersection[T comparable](s, other AglSet[T]) AglSet[T] {
	newSet := make(AglSet[T])
	for k := range s {
		if _, ok := other[k]; ok {
			newSet[k] = struct{}{}
		}
	}
	return newSet
}

// AglSetFormIntersection removes the elements of the set that aren't also in the given sequence.
func AglSetFormIntersection[T comparable](s, other AglSet[T]) {
	for k := range s {
		if _, ok := other[k]; !ok {
			delete(s, k)
		}
	}
}

// AglSetSymmetricDifference returns a new set with the elements that are either in this set or in the given sequence, but not in both.
func AglSetSymmetricDifference[T comparable](s, other AglSet[T]) AglSet[T] {
	newSet := make(AglSet[T])
	for k := range s {
		if _, ok := other[k]; !ok {
			newSet[k] = struct{}{}
		}
	}
	for k := range other {
		if _, ok := s[k]; !ok {
			newSet[k] = struct{}{}
		}
	}
	return newSet
}

// AglSetFormSymmetricDifference removes the elements of the set that are also in the given sequence and adds the members of the sequence that are not already in the set.
func AglSetFormSymmetricDifference[T comparable](s AglSet[T], other Iterator[T]) {
	for k := range other.Iter() {
		if _, ok := s[k]; !ok {
			s[k] = struct{}{}
		} else {
			delete(s, k)
		}
	}
}

// AglSetIsSubset returns a Boolean value that indicates whether the set is a subset of the given sequence.
// Return: true if the set is a subset of possibleSuperset; otherwise, false.
// Set A is a subset of another set B if every member of A is also a member of B.
func AglSetIsSubset[T comparable](s, other AglSet[T]) bool {
	for k := range s {
		if _, ok := other[k]; !ok {
			return false
		}
	}
	return true
}

// AglSetIsStrictSubset returns a Boolean value that indicates whether the set is a strict subset of the given sequence.
// Set A is a strict subset of another set B if every member of A is also a member of B and B contains at least one element that is not a member of A.
func AglSetIsStrictSubset[T comparable](s, other AglSet[T]) bool {
	for k := range s {
		if _, ok := other[k]; !ok {
			return false
		}
	}
	for k := range other {
		if _, ok := s[k]; !ok {
			return true
		}
	}
	return false
}

// AglSetIsSuperset returns a Boolean value that indicates whether this set is a superset of the given set.
// Return: true if the set is a superset of other; otherwise, false.
// Set A is a superset of another set B if every member of B is also a member of A.
func AglSetIsSuperset[T comparable](s AglSet[T], other Iterator[T]) bool {
	for k := range other.Iter() {
		if _, ok := s[k]; !ok {
			return false
		}
	}
	return true
}

// AglSetIsStrictSuperset returns a Boolean value that indicates whether the set is a strict superset of the given sequence.
// Set A is a strict superset of another set B if every member of B is also a member of A and A contains at least one element that is not a member of B.
func AglSetIsStrictSuperset[T comparable](s, other AglSet[T]) bool {
	for k := range other {
		if _, ok := s[k]; !ok {
			return false
		}
	}
	for k := range s {
		if _, ok := other[k]; !ok {
			return true
		}
	}
	return false
}

// AglSetIsDisjoint returns a Boolean value that indicates whether the set has no members in common with the given sequence.
// Return: true if the set has no elements in common with other; otherwise, false.
func AglSetIsDisjoint[T comparable](s AglSet[T], other Iterator[T]) bool {
	var otherSet AglSet[T]
	if v, ok := other.(AglSet[T]); ok {
		otherSet = v
	} else {
		otherSet = make(AglSet[T])
		for e := range other.Iter() {
			otherSet[e] = struct{}{}
		}
	}
	for k := range s {
		if _, ok := otherSet[k]; ok {
			return false
		}
	}
	return true
}

// AglSetIntersects ...
func AglSetIntersects[T comparable](s AglSet[T], other Iterator[T]) bool {
	return !AglSetIsDisjoint(s, other)
}

func AglStringLen(s string) int {
	return len(s)
}

func AglStringEnumerated(s string) []AglTupleStruct_int_RuneType {
	out := make([]AglTupleStruct_int_RuneType, 0, len(s))
	for i, c := range s {
		out = append(out, AglTupleStruct_int_RuneType{Arg0: i, Arg1: c})
	}
	return out
}

func AglStringLines(s string) []string {
	s = strings.ReplaceAll(s, "\r\n", "\n")
	return strings.Split(s, "\n")
}

func AglStringReplace(s string, old, new string, n int) string {
	return strings.Replace(s, old, new, n)
}

func AglStringReplaceAll(s string, old, new string) string {
	return strings.ReplaceAll(s, old, new)
}

func AglStringTrimSpace(s string) string {
	return strings.TrimSpace(s)
}

func AglStringIsEmpty(s string) bool {
	return s == ""
}

func AglStringTrimPrefix(s string, prefix string) string {
	return strings.TrimPrefix(s, prefix)
}

func AglStringTrimSuffix(s string, suffix string) string {
	return strings.TrimSuffix(s, suffix)
}

type AglTupleStruct_string_string struct {
	Arg0 string
	Arg1 string
}

func AglStringCut(s string, sep string) Option[AglTupleStruct_string_string] {
	before, after, found := strings.Cut(s, sep)
	if !found {
		return MakeOptionNone[AglTupleStruct_string_string]()
	}
	return MakeOptionSome(AglTupleStruct_string_string{Arg0: before, Arg1: after})
}

func AglStringCutPrefix(s string, prefix string) Option[string] {
	after, found := strings.CutPrefix(s, prefix)
	if !found {
		return MakeOptionNone[string]()
	}
	return MakeOptionSome(after)
}

func AglStringCutSuffix(s string, suffix string) Option[string] {
	before, found := strings.CutSuffix(s, suffix)
	if !found {
		return MakeOptionNone[string]()
	}
	return MakeOptionSome(before)
}

func AglStringHasPrefix(s string, prefix string) bool {
	return strings.HasPrefix(s, prefix)
}

func AglStringContains(s string, substr string) bool {
	return strings.Contains(s, substr)
}

func AglStringContainsAny(s, chars string) bool {
	return strings.ContainsAny(s, chars)
}

func AglStringIndex(s, substr string) int {
	return strings.Index(s, substr)
}

func AglStringLastIndex(s, substr string) int {
	return strings.LastIndex(s, substr)
}

func AglStringCount(s, substr string) int {
	return strings.Count(s, substr)
}

func AglStringRepeat(s string, count int) string {
	return strings.Repeat(s, count)
}

func AglStringTrim(s, cutset string) string {
	return strings.Trim(s, cutset)
}

func AglStringTrimLeft(s, cutset string) string {
	return strings.TrimLeft(s, cutset)
}

func AglStringTrimRight(s, cutset string) string {
	return strings.TrimRight(s, cutset)
}

func AglStringHasSuffix(s string, suffix string) bool {
	return strings.HasSuffix(s, suffix)
}

func AglStringSplit(s string, sep string) []string {
	return strings.Split(s, sep)
}

func AglStringSplitAfter(s string, sep string) []string {
	return strings.SplitAfter(s, sep)
}

func AglStringLowercased(s string) string {
	return strings.ToLower(s)
}

func AglStringUppercased(s string) string {
	return strings.ToUpper(s)
}

func AglStringAsBytes(s string) []byte {
	return []byte(s)
}

func AglCleanupIntString(s string) (string, int) {
	s = strings.ReplaceAll(s, "_", "")
	var base int
	switch {
	case strings.HasPrefix(s, "0b"):
		s, base = s[2:], 2
	case strings.HasPrefix(s, "0o"):
		s, base = s[2:], 8
	case strings.HasPrefix(s, "0x"):
		s, base = s[2:], 16
	default:
		base = 10
	}
	return s, base
}

func AglStringInt(s string) Option[int] {
	s, base := AglCleanupIntString(s)
	v, err := strconv.ParseInt(s, base, 0)
	if err != nil {
		return MakeOptionNone[int]()
	}
	return MakeOptionSome(int(v))
}

func AglStringI8(s string) Option[int8] {
	s, base := AglCleanupIntString(s)
	v, err := strconv.ParseInt(s, base, 8)
	if err != nil {
		return MakeOptionNone[int8]()
	}
	return MakeOptionSome(int8(v))
}

func AglStringI16(s string) Option[int16] {
	s, base := AglCleanupIntString(s)
	v, err := strconv.ParseInt(s, base, 16)
	if err != nil {
		return MakeOptionNone[int16]()
	}
	return MakeOptionSome(int16(v))
}

func AglStringI32(s string) Option[int32] {
	s, base := AglCleanupIntString(s)
	v, err := strconv.ParseInt(s, base, 32)
	if err != nil {
		return MakeOptionNone[int32]()
	}
	return MakeOptionSome(int32(v))
}

func AglStringI64(s string) Option[int64] {
	s, base := AglCleanupIntString(s)
	v, err := strconv.ParseInt(s, base, 64)
	if err != nil {
		return MakeOptionNone[int64]()
	}
	return MakeOptionSome(v)
}

func AglStringUint(s string) Option[uint] {
	s, base := AglCleanupIntString(s)
	v, err := strconv.ParseUint(s, base, 0)
	if err != nil {
		return MakeOptionNone[uint]()
	}
	return MakeOptionSome(uint(v))
}

func AglStringU8(s string) Option[uint8] {
	s, base := AglCleanupIntString(s)
	v, err := strconv.ParseUint(s, base, 8)
	if err != nil {
		return MakeOptionNone[uint8]()
	}
	return MakeOptionSome(uint8(v))
}

func AglStringU16(s string) Option[uint16] {
	s, base := AglCleanupIntString(s)
	v, err := strconv.ParseUint(s, base, 16)
	if err != nil {
		return MakeOptionNone[uint16]()
	}
	return MakeOptionSome(uint16(v))
}

func AglStringU32(s string) Option[uint32] {
	s, base := AglCleanupIntString(s)
	v, err := strconv.ParseUint(s, base, 32)
	if err != nil {
		return MakeOptionNone[uint32]()
	}
	return MakeOptionSome(uint32(v))
}

func AglStringU64(s string) Option[uint64] {
	s, base := AglCleanupIntString(s)
	v, err := strconv.ParseUint(s, base, 64)
	if err != nil {
		return MakeOptionNone[uint64]()
	}
	return MakeOptionSome(uint64(v))
}

func AglStringF32(s string) Option[float32] {
	v, err := strconv.ParseFloat(s, 32)
	if err != nil {
		return MakeOptionNone[float32]()
	}
	return MakeOptionSome(float32(v))
}

func AglStringF64(s string) Option[float64] {
	v, err := strconv.ParseFloat(s, 64)
	if err != nil {
		return MakeOptionNone[float64]()
	}
	return MakeOptionSome(v)
}

func AglVecSorted[E cmp.Ordered](a []E) []E {
	return slices.Sorted(slices.Values(a))
}

func AglVecSortedBy[E any](a []E, f func(a, b E) bool) []E {
	sort.Slice(a, func(i, j int) bool {
		return f(a[i], a[j])
	})
	return a
}

func AglVecJoined(a []string, s string) string {
	return strings.Join(a, s)
}

func AglVecSum[T cmp.Ordered](a []T) (out T) {
	var zero T
	return AglVecReduce(a, zero, func(acc, el T) T { return acc + el })
}

func AglAbs[T Number](e T) (out T) {
	return T(math.Abs(float64(e)))
}

func AglPow[T, E Number](v T, e E) (out float64) {
	return math.Pow(float64(v), float64(e))
}

func AglVecWith[T any](a *[]T, i int, clb func(*T) AglVoid) {
	el := (*a)[i]
	clb(&el)
	(*a)[i] = el
}

func AglVecPush[T any](a *[]T, els ...T) {
	*a = append(*a, els...)
}

// AglVecPushFront ...
func AglVecPushFront[T any](a *[]T, el T) {
	*a = append([]T{el}, *a...)
}

// AglVecPopFront ...
func AglVecPopFront[T any](a *[]T) Option[T] {
	if len(*a) == 0 {
		return MakeOptionNone[T]()
	}
	var el T
	el, *a = (*a)[0], (*a)[1:]
	return MakeOptionSome(el)
}

func AglVecRemoveFirst[T any](a *[]T) T {
	res := AglVecPopFront(a)
	if res.IsNone() {
		panic("Vec is empty")
	}
	return res.Unwrap()
}

// AglVecSwap ...
func AglVecSwap[T any, I, J Integer](a *[]T, b I, c J) {
	(*a)[b], (*a)[c] = (*a)[c], (*a)[b]
}

// AglVecInsert ...
func AglVecInsert[T any](a *[]T, idx int, el T) {
	*a = append((*a)[:idx], append([]T{el}, (*a)[idx:]...)...)
}

// AglVecRemove ...
func AglVecRemove[T any](a *[]T, idx int) {
	*a = append((*a)[:idx], (*a)[idx+1:]...)
}

// AglVecClone ...
func AglVecClone[S ~[]E, E any](a S) S {
	return slices.Clone(a)
}

// AglVecClear ...
func AglVecClear[T any](a *[]T) {
	*a = (*a)[:0]
}

// AglVecIndices ...
func AglVecIndices[T any](a []T) []int {
	out := make([]int, len(a))
	for i := range a {
		out[i] = i
	}
	return out
}

// AglVecPop removes the last element from a vector and returns it, or None if it is empty.
func AglVecPop[T any](a *[]T) Option[T] {
	if len(*a) == 0 {
		return MakeOptionNone[T]()
	}
	var el T
	el, *a = (*a)[len(*a)-1], (*a)[:len(*a)-1]
	return MakeOptionSome(el)
}

// AglVecPopIf Removes and returns the last element from a vector if the predicate returns true,
// or None if the predicate returns false or the vector is empty (the predicate will not be called in that case).
func AglVecPopIf[T any](a *[]T, pred func() bool) Option[T] {
	if len(*a) == 0 || !pred() {
		return MakeOptionNone[T]()
	}
	var el T
	el, *a = (*a)[len(*a)-1], (*a)[:len(*a)-1]
	return MakeOptionSome(el)
}

func AglMapLen[K comparable, V any](m map[K]V) int {
	return len(m)
}

func AglMapIndex[K comparable, V any](m map[K]V, index K) Option[V] {
	if el, ok := m[index]; ok {
		return MakeOptionSome(el)
	}
	return MakeOptionNone[V]()
}

func AglMapContainsKey[K comparable, V any](m map[K]V, k K) bool {
	_, ok := m[k]
	return ok
}

func AglMapFilter[K comparable, V any](m map[K]V, f func(DictEntry[K, V]) bool) map[K]V {
	out := make(map[K]V)
	for k, v := range m {
		if f(DictEntry[K, V]{Key: k, Value: v}) {
			out[k] = v
		}
	}
	return out
}

func AglMapMap[K comparable, V, R any](m map[K]V, f func(DictEntry[K, V]) R) []R {
	var out []R
	for k, v := range m {
		out = append(out, f(DictEntry[K, V]{Key: k, Value: v}))
	}
	return out
}

func AglMapKeys[K comparable, V any](m map[K]V) Sequence[K] {
	return Sequence[K](maps.Keys(m))
}

func AglMapValues[K comparable, V any](m map[K]V) Sequence[V] {
	return Sequence[V](maps.Values(m))
}

func AglHttpNewRequest(method, url string, b Option[io.Reader]) Result[*http.Request] {
	var body io.Reader
	if b.IsSome() {
		body = b.Unwrap()
	}
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return MakeResultErr[*http.Request](err)
	}
	return MakeResultOk(req)
}

type Set[T comparable] struct {
	values map[T]struct{}
}

func (s *Set[T]) String() string {
	var vals []string
	for k := range s.values {
		vals = append(vals, fmt.Sprintf("%v", k))
	}
	return "{" + strings.Join(vals, " ") + "}"
}

func (s *Set[T]) Len() int {
	return len(s.values)
}

// Insert Adds a value to the set.
//
// Returns whether the value was newly inserted. That is:
//
// - If the set did not previously contain this value, true is returned.
// - If the set already contained this value, false is returned, and the set is not modified: original value is not replaced, and the value passed as argument is dropped.
func (s *Set[T]) Insert(value T) bool {
	if _, ok := s.values[value]; ok {
		return false
	}
	s.values[value] = struct{}{}
	return true
}

func AglNewSet[T comparable](els ...T) *Set[T] {
	s := &Set[T]{values: make(map[T]struct{})}
	for _, el := range els {
		s.values[el] = struct{}{}
	}
	return s
}

func AglIntString(v int) string   { return strconv.FormatInt(int64(v), 10) }
func AglI8String(v int8) string   { return strconv.FormatInt(int64(v), 10) }
func AglI16String(v int16) string { return strconv.FormatInt(int64(v), 10) }
func AglI32String(v int32) string { return strconv.FormatInt(int64(v), 10) }
func AglI64String(v int64) string { return strconv.FormatInt(int64(v), 10) }
func AglUintString(v uint) string { return strconv.FormatInt(int64(v), 10) }

func AglIn[T comparable](e T, it Iterator[T]) bool {
	for el := range it.Iter() {
		if el == e {
			return true
		}
	}
	return false
}

func AglBuildSet[T comparable](it Iterator[T]) (out AglSet[T]) {
	out = make(AglSet[T])
	for el := range it.Iter() {
		AglSetInsert(out, el)
	}
	return
}

func AglBuildArray[T any](it Iterator[T]) (out []T) {
	for el := range it.Iter() {
		out = append(out, el)
	}
	return
}
