package main

import (
	"fmt"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
)

type User struct {
	ID        int
	Name      string
	Email     string
	CreatedAt time.Time
	UpdatedAt time.Time
	password  string // unexported field
}

type Address struct {
	Street string
	City   string
}

type Order struct {
	ID     int
	Items  []string
	Total  float64
	User   *User
	Addr   Address
}

func main() {
	// Basic comparison
	u1 := User{ID: 1, Name: "Alice", Email: "alice@example.com"}
	u2 := User{ID: 1, Name: "Alice", Email: "alice@example.com"}

	if cmp.Equal(u1, u2) {
		fmt.Println("Users are equal")
	}

	// Get detailed diff
	u3 := User{ID: 1, Name: "Alice", Email: "bob@example.com"}
	diff := cmp.Diff(u1, u3)
	if diff != "" {
		fmt.Println("Difference found:")
		fmt.Println(diff)
	}

	// Ignore specific fields
	now := time.Now()
	u4 := User{ID: 1, Name: "Alice", CreatedAt: now}
	u5 := User{ID: 1, Name: "Alice", CreatedAt: now.Add(time.Hour)}

	opts := cmpopts.IgnoreFields(User{}, "CreatedAt", "UpdatedAt")
	if cmp.Equal(u4, u5, opts) {
		fmt.Println("Equal when ignoring timestamps")
	}

	// Compare unexported fields
	u6 := User{ID: 1, Name: "Alice", password: "secret"}
	u7 := User{ID: 1, Name: "Alice", password: "secret"}

	// This would panic without AllowUnexported
	opts = cmp.AllowUnexported(User{})
	if cmp.Equal(u6, u7, opts) {
		fmt.Println("Equal including unexported fields")
	}

	// Ignore unexported fields instead
	opts = cmpopts.IgnoreUnexported(User{})
	if cmp.Equal(u6, u7, opts) {
		fmt.Println("Equal ignoring unexported fields")
	}

	// Compare slices ignoring order
	s1 := []string{"apple", "banana", "cherry"}
	s2 := []string{"cherry", "apple", "banana"}

	opts = cmpopts.SortSlices(func(a, b string) bool { return a < b })
	if cmp.Equal(s1, s2, opts) {
		fmt.Println("Slices equal (ignoring order)")
	}

	// Approximate float comparison
	f1 := 3.14159
	f2 := 3.14160

	opts = cmpopts.EquateApprox(0.001, 0) // tolerance of 0.1%
	if cmp.Equal(f1, f2, opts) {
		fmt.Println("Floats approximately equal")
	}

	// Compare empty slices and nil as equal
	var nilSlice []string
	emptySlice := []string{}

	opts = cmpopts.EquateEmpty()
	if cmp.Equal(nilSlice, emptySlice, opts) {
		fmt.Println("Empty and nil slices equal")
	}

	// Custom comparer
	type Money struct {
		Amount   int64  // in cents
		Currency string
	}

	m1 := Money{Amount: 1000, Currency: "USD"}
	m2 := Money{Amount: 1000, Currency: "usd"}

	moneyComparer := cmp.Comparer(func(a, b Money) bool {
		// Case-insensitive currency comparison
		return a.Amount == b.Amount &&
			a.Currency == b.Currency || // exact match
				(a.Currency != "" && b.Currency != "" &&
					a.Currency[0] == b.Currency[0]) // first char match
	})

	if cmp.Equal(m1, m2, moneyComparer) {
		fmt.Println("Money values equal with custom comparer")
	}

	// Combine multiple options
	o1 := Order{ID: 1, Items: []string{"a", "b"}, Total: 99.99}
	o2 := Order{ID: 1, Items: []string{"b", "a"}, Total: 100.00}

	opts = cmp.Options{
		cmpopts.SortSlices(func(a, b string) bool { return a < b }),
		cmpopts.EquateApprox(0.01, 0),
	}
	if cmp.Equal(o1, o2, opts) {
		fmt.Println("Orders equal with combined options")
	}
}
