package dump

import (
	
	
	
	
	
	
	
	
	
	

	
	
)

// printValue must keep track of already-printed pointer values to avoid
// infinite recursion. refer the pkg: github.com/kr/pretty
type visit struct {
	v   uintptr
	typ reflect.Type
}

// Dumper struct definition
type Dumper struct {
	*Options
	// locker for visited
	mu sync.RWMutex
	// visited struct records
	visited map[visit]int
	// is value in the slice, map, struct. will not apply indent.
	msValue bool
	// current depth
	curDepth int
	// current indent string bytes
	indentBytes []byte
	// prevDepth, nextDepth int
	// indentStr, indentPrev, lineEnd string
}

// NewDumper create
func ( io.Writer,  int) *Dumper {
	return &Dumper{
		Options: NewDefaultOptions(, ),
		// init map
		visited: make(map[visit]int),
	}
}

// NewWithOptions create
func ( ...OptionFunc) *Dumper {
	return NewDumper(os.Stdout, defaultSkip).WithOptions(...)
}

// WithSkip for dumper
func ( *Dumper) ( int) *Dumper {
	.CallerSkip = 
	return 
}

// WithoutColor for dumper
func ( *Dumper) () *Dumper {
	.NoColor = true
	return 
}

// WithOptions for dumper
func ( *Dumper) ( ...OptionFunc) *Dumper {
	for ,  := range  {
		(.Options)
	}
	return 
}

// ResetOptions for dumper
func ( *Dumper) () {
	.curDepth = 0
	.visited = make(map[visit]int)
	.Options = NewDefaultOptions(os.Stdout, .CallerSkip)
}

// Dump vars
func ( *Dumper) ( ...any) { .dump(...) }

// Print vars. alias of Dump()
func ( *Dumper) ( ...any) { .dump(...) }

// Println vars. alias of Dump()
func ( *Dumper) ( ...any) { .dump(...) }

// Fprint print vars to io.Writer
func ( *Dumper) ( io.Writer,  ...any) {
	 := .Output // backup

	.Output = 
	.dump(...)
	.Output =  // restore
}

// dump go vars
func ( *Dumper) ( ...any) {
	// reset some settings.
	.curDepth = 0
	.visited = make(map[visit]int)

	// clear all theme settings.
	if .NoColor {
		.ColorTheme = make(Theme)
	}

	// show print position
	if .ShowFlag != Fnopos {
		// get the print position
		, , ,  := runtime.Caller(.CallerSkip)
		if  {
			.printCaller(, , )
		}
	}

	// print var data
	for ,  := range  {
		// d.advance(1)
		.printOne()
		// d.advance(-1)
	}
}

func ( *Dumper) ( uintptr,  string,  int) {
	// eg: github.com/gookit/goutil/dump.ExamplePrint
	 := runtime.FuncForPC().Name()

	 := strconv.Itoa()
	 := []string{"PRINT AT "}

	// eg:
	// "PRINT AT github.com/gookit/goutil/dump.ExamplePrint(goutil/dump/dump_test.go:23)"
	// "PRINT AT github.com/gookit/goutil/dump.ExamplePrint(dump_test.go:23)"
	// "PRINT AT github.com/gookit/goutil/dump.ExamplePrint(:23)"
	for ,  := range callerFlags {
		// has a flag
		if .ShowFlag& == 0 {
			continue
		}
		switch  {
		case Ffunc: // full func name
			 = append(, , "(")
		case Ffile: // full file path
			 = append(, )
		case Ffname: // only file name
			 := filepath.Base() // file name
			 = append(, )
		default: // Fline
			 = append(, ":", )
		}
	}

	// fallback. eg: "PRINT AT goutil/dump/dump_test.go:23"
	if len() == 1 {
		 = append(, , ":", )
	} else if .ShowFlag&Ffunc != 0 { // has func, add ")"
		 = append(, ")")
	}

	 := strings.Join(, "")
	.print(.ColorTheme.caller(), "\n")
}

func ( *Dumper) ( int) {
	.curDepth += 
	// d.nextDepth = d.curDepth + step
	if .curDepth < 1 {
		.indentBytes = []byte{}
		return
	}

	.indentBytes = strutil.RepeatBytes(.IndentChar, .IndentLen*.curDepth)
}

func ( *Dumper) ( any) {
	if  == nil {
		.indentPrint("<nil>,\n")
		return
	}

	if ,  := .([]byte);  && .BytesAsString {
		 := .ColorTheme.string(string())
		if .ShowLen {
			 := .ColorTheme.valTip("#len=" + strconv.Itoa(len()) + ",cap=" + strconv.Itoa(cap()))
			.printf("[]byte(\"%s\"), %s\n", , )
		} else {
			.printf("[]byte(\"%s\"),\n", )
		}
		return
	}

	// print reflect value
	 := reflect.ValueOf()
	.printRValue(.Type(), )
}

// print reflect value
func ( *Dumper) ( reflect.Type,  reflect.Value) {
	// if is a ptr, get real type and value
	 := .Kind() == reflect.Ptr
	if  {
		if .IsNil() {
			.printf("%s<nil>,\n", .String())
			return
		}

		,  = .Elem(), .Elem()
		.indentPrint("&") // add prefix
	}

	if !.IsValid() {
		.indentPrint(.String(), "<nil>, #invalid\n")
	}

	// if v.CanAddr() && !d.checkCyclicRef(t, v) {
	// 	return // don't print v again
	// }

	if .curDepth > .MaxDepth {
		// if !v.CanInterface() {
		// 	d.printf("%s,\n", v.String())
		// } else {
		// 	// v.Interface() will stack overflow on cyclic refer
		// 	d.printf("%#v,\n", v.Interface())
		// }
		.printf("%s(!OVER MAX DEPTH!),\n", .String())
		return
	}

	switch .Kind() {
	case reflect.Bool:
		.printf("%s(%v),\n", .String(), .Bool())
	case reflect.Float32, reflect.Float64:
		.printf("%s(%v),\n", .String(), .Float())
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		 := strconv.FormatInt(.Int(), 10)
		 = .ColorTheme.integer()
		.printf("%s(%s),%s\n", .String(), , .rvStringer(, ))
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		 := strconv.FormatUint(.Uint(), 10)
		 = .ColorTheme.integer()
		.printf("%s(%s),%s\n", .String(), , .rvStringer(, ))
	case reflect.String:
		 := .ColorTheme.string(.String())
		if .ShowLen {
			 := .ColorTheme.valTip("#len=" + strconv.Itoa(.Len()))
			.printf("%s(\"%s\"), %s\n", .String(), , )
		} else {
			.printf("%s(\"%s\"),\n", .String(), )
		}
	case reflect.Complex64, reflect.Complex128:
		.printf("%#v\n", .Complex())
	case reflect.Slice, reflect.Array:
		if .CanAddr() && !.checkCyclicRef(, ) {
			break // don't print v again
		}

		 := .Len()
		if .ShowLen {
			 := .ColorTheme.valTip("#len=" + strconv.Itoa() + ",cap=" + strconv.Itoa(.Cap()))
			.write(!, .String(), " [ ", , "\n")
		} else {
			.write(!, .String(), " [\n")
		}
		.msValue = false

		for  := 0;  < ; ++ {
			if  > .MaxElementsNum {
				.indentPrint("...(!OVER MAX SETTING!)...\n")
				break
			}

			 := .Index()
			.advance(1)

			// d.msValue = true
			.(.Type(), )
			// d.msValue = false

			// d.printf("%v,\n", v.Index(i).Interface())
			.advance(-1)
		}

		.indentPrint("],\n")
	case reflect.Struct:
		if .CanAddr() && !.checkCyclicRef(, ) {
			break // don't print v again
		}

		// up: special handel time.Time struct
		if  == timeType {
			.printf("time.Time(%s),\n", .ColorTheme.string(.fmtTimeValue()))
			break
		}
		// up: if is type alias of time.Time, use a datetime format
		if ! && .ConvertibleTo(timeType) {
			 := .Convert(timeType)
			.printf("%s(%s),\n", .String(), .ColorTheme.string(.fmtTimeValue()))
			break
		}

		.write(!, .ColorTheme.msType(.String()), " {\n")
		.msValue = false

		 := .NumField()
		for  := 0;  < ; ++ {
			 := .Field().Name
			if .SkipPrivate && isUnexported() {
				continue
			}

			 := .Field()
			if .SkipNilField && isNilOrInvalid() {
				continue
			}

			.advance(1)

			// print field name
			.indentPrint(.ColorTheme.field(), ": ")

			.msValue = true
			.(.Type(), )
			.msValue = false

			.advance(-1)
		}

		.indentPrint("},\n")
	case reflect.Map:
		if .ShowLen {
			 := .ColorTheme.valTip("#len=" + strconv.Itoa(.Len()))
			.write(!, .ColorTheme.msType(.String()), " { ", , "\n")
		} else {
			.write(!, .ColorTheme.msType(.String()), " {\n")
		}
		.msValue = false

		for ,  := range .MapKeys() {
			 := .MapIndex()
			if .SkipNilField && isNilOrInvalid() {
				continue
			}

			.advance(1)

			// print key name
			if !.CanInterface() {
				// d.printf("<cyan>%s</>: ", key.String())
				.printf("%s: ", .String())
			} else {
				.printf("%#v: ", .Interface())
			}

			if .CanAddr() && !.checkCyclicRef(.Type(), ) {
				.advance(-1)
				continue // don't print mv again
			}

			// print field value
			.msValue = true
			.(.Type(), )
			.msValue = false

			.advance(-1)
		}

		.indentPrint("},\n")
	case reflect.Interface:
		if .CanAddr() && !.checkCyclicRef(, ) {
			break // don't print v again
		}

		switch  := .Elem(); {
		case .Kind() == reflect.Invalid:
			.indentPrint("nil,\n")
		case .IsValid():
			// d.advance(1)
			.(.Type(), )
		default:
			.indentPrint(.String(), "(nil),\n")
		}
	// case reflect.Ptr:
	case reflect.Chan:
		.printf("(%s)(%#v),\n", .String(), .Pointer())
	case reflect.Func:
		.printf("(%s) {...},\n", .String())
	case reflect.UnsafePointer:
		.printf("(%#v),\n", .Pointer())
	case reflect.Invalid:
		.indentPrint(.String(), "(nil),\n")
	default:
		if .CanAddr() && !.checkCyclicRef(, ) {
			break // don't print v again
		}

		if .CanInterface() {
			.printf("%s(%#v),\n", .String(), .Interface())
		} else {
			.printf("%s(%v),\n", .String(), .String())
		}
	}
}

func ( *Dumper) ( reflect.Type,  reflect.Value) ( bool) {
	 := .UnsafeAddr()
	 := visit{, }

	.mu.RLock()
	if ,  := .visited[];  &&  < .MaxDepth {
		.indentPrint(.String(), "{(!CYCLIC REFERENCE!)}\n")
		.mu.RUnlock()
		return false // don't print v again
	}
	.mu.RUnlock()

	// record visited
	.mu.Lock()
	.visited[] = .curDepth
	.mu.Unlock()
	return true
}

func ( *Dumper) ( reflect.Type,  reflect.Value) string {
	// fmt.Println("Implements fmt.Stringer:", t.Implements(stringerType))
	if .CanInterface() && .Implements(stringerType) {
		return .ColorTheme.valTip(` #str: "` + .Interface().(fmt.Stringer).String() + `"`)
	}
	return ""
}

func ( *Dumper) ( reflect.Value) string {
	var  string
	if .CanInterface() {
		 = .Interface().(time.Time).Format(time.RFC3339)
	} else {
		 = .String()
	}
	return 
}

func ( *Dumper) ( ...any) {
	if .NoColor {
		_, _ = fmt.Fprint(.Output, ...)
	} else {
		ccolor.Fprint(.Output, ...)
	}
}

func ( *Dumper) ( string,  ...any) {
	if !.msValue {
		_, _ = .Output.Write(.indentBytes)
	}

	if .NoColor {
		_, _ = fmt.Fprintf(.Output, , ...)
	} else {
		ccolor.Fprintf(.Output, , ...)
	}
}

func ( *Dumper) ( bool,  ...any) {
	if  && !.msValue {
		_, _ = .Output.Write(.indentBytes)
	}

	if .NoColor {
		_, _ = fmt.Fprint(.Output, ...)
	} else {
		ccolor.Fprint(.Output, ...)
	}
}

func ( *Dumper) ( ...any) {
	.write(true, ...)
}