struct Abc {
mut:
	val string
}

struct Xyz {
	name string
}

type Alphabet = Abc | Xyz

fn test_if_smartcast() {
	x := Alphabet(Abc{'test'})
	if x is Abc {
		assert x.val == 'test'
	}
}

fn test_mutable() {
	mut x := Alphabet(Abc{'original'})
	if mut x is Abc {
		assert x.val == 'original'
		x.val = 'changed'
		assert x.val == 'changed'
	}
	if mut x is Abc {
		assert x.val == 'changed'
	}
}

fn test_nested_if_smartcast() {
	x := Alphabet(Abc{'test'})
	y := Alphabet(Xyz{'foo'})
	if x is Abc {
		if y is Xyz {
			assert y.name == 'foo'
		}
	}
}

type Bar = Test | string
type Xya = int | string

struct Test {
	x   string
	xya Xya
}

struct BarWrapper {
	y Bar
}

fn test_nested_selector_smartcast() {
	f := BarWrapper{
		y: Bar(Test{
			x:   'Hi'
			xya: Xya(int(5))
		})
	}

	if f.y is Test {
		z := f.y.x
		assert f.y.x == 'Hi'
		assert z == 'Hi'
		if f.y.xya is int {
			assert f.y.xya == 5
		}
	}
}

type Inner = int | string

struct InnerStruct {
	x Inner
}

type Outer = InnerStruct | string

fn test_nested_if_is() {
	b := Outer(InnerStruct{Inner(0)})
	if b is InnerStruct {
		if b.x is int {
			assert b.x == 0
		}
	}
}

struct MutContainer {
mut:
	abc Alphabet
}

struct Container {
	abc Alphabet
}

fn test_mutable_with_struct() {
	mut c := MutContainer{Abc{'original'}}
	if mut c.abc is Abc {
		assert c.abc.val == 'original'
		c.abc.val = 'xyz'
		assert c.abc.val == 'xyz'
	}
	if mut c.abc is Abc {
		// Note: in this second smart cast, `another` is
		// the same wrapped value, that was changed in
		// the first smart cast:
		assert c.abc.val == 'xyz'
	}
}

fn test_as_cast_with_struct() {
	x := Container{Abc{'test'}}
	if x.abc is Abc {
		assert x.abc.val == 'test'
	}
}

struct CellStr {
	str string
}

struct CellInt {
	itg i64
}

struct CellFloat {
	flt f64
}

struct CellU32 {
	u u32
}

type Cell = CellFloat | CellInt | CellStr | CellU32

fn test_mutability() {
	my_str := 'the quick brown fox jumps over the lazy dog.'
	my_itg := -1234567890
	my_flt := 3.14159265358979323846
	my_u32 := u32(4294967295)
	cell_str := CellStr{
		str: my_str
	}
	cell_itg := CellInt{
		itg: my_itg
	}
	cell_flt := CellFloat{
		flt: my_flt
	}
	cell_u32 := CellU32{
		u: my_u32
	}
	mut cell := Cell{}
	cell = cell_str
	if mut cell is CellStr {
		println('${cell.str}')
	}
	cell = cell_itg
	if mut cell is CellInt {
		println('${cell.itg}')
	}
	cell = cell_flt
	if mut cell is CellFloat {
		println('${cell.flt}')
	}
	cell = cell_u32
	if mut cell is CellU32 {
		println('${cell.u}')
	}
}

type Expr = CTempVarExpr | CallExpr

struct ExprWrapper {
mut:
	expr Expr
}

struct CallExpr {
	y int
	x string
}

struct CTempVarExpr {
	x string
}

fn gen(_ Expr) CTempVarExpr {
	return CTempVarExpr{}
}

fn test_reassign_from_function_with_parameter_selector() {
	mut f := ExprWrapper{Expr(CallExpr{})}
	if f.expr is CallExpr {
		f.expr = gen(f.expr)
	}
}

type Node = Expr | string

fn test_nested_sumtype() {
	c := Node(Expr(CallExpr{
		y: 1
	}))
	if c is Expr {
		if c is CallExpr {
			assert c.y == 1
		} else {
			assert false
		}
	} else {
		assert false
	}
}

type Food = Eggs | Milk

struct FoodWrapper {
mut:
	food Food
}

struct Milk {
mut:
	name string
}

struct Eggs {
mut:
	name string
}

fn test_if_mut_selector() {
	mut f := FoodWrapper{Food(Milk{'test'})}
	if mut f.food is Milk {
		f.food.name = 'milk'
		assert f.food.name == 'milk'
	}
}

struct NodeWrapper {
	node Node
}

fn test_nested_sumtype_selector() {
	c := NodeWrapper{Node(Expr(CallExpr{
		y: 1
	}))}
	if c.node is Expr {
		if c.node is CallExpr {
			assert c.node.y == 1
		} else {
			assert false
		}
	} else {
		assert false
	}
}

struct Foo1 {
	a int
}

struct Foo2 {
	a int
}

struct Bar1 {
	a int
}

struct Bar2 {
	a int
}

type Sum1 = Foo1 | Foo2

type Sum2 = Bar1 | Bar2

type SumAll = Sum1 | Sum2

struct All_in_one {
pub mut:
	ptrs []&SumAll
	ptr  &SumAll
}

fn test_nested_pointer_smartcast() {
	mut s := All_in_one{
		ptr:  &Sum1(Foo1{
			a: 1
		})
		ptrs: [&SumAll(Sum2(Bar1{
			a: 3
		}))]
	}

	if mut s.ptr is Sum1 {
		if mut s.ptr is Foo1 {
			assert s.ptr.a == 1
		}
	}

	a := s.ptrs[0]
	if a is Sum1 {
		if a is Foo1 {
			assert a.a == 3
		}
	}
}
