package patient

import (
	"reflect"
	"strings"
)

func SetPatient(dst, src interface{}, preserve map[string]bool) {
	dv := reflect.ValueOf(dst).Elem()
	sv := reflect.ValueOf(src).Elem()

	for i := 0; i < dv.NumField(); i++ {
		f := dv.Type().Field(i)
		fieldName := f.Name

		// skip preserved fields
		if preserve[fieldName] {
			continue
		}

		df := dv.Field(i)
		sf := sv.Field(i)
		if !df.CanSet() {
			// skip unexported skip
			continue
		}

		switch df.Kind() {

		case reflect.Struct:
			nestedPreserve := map[string]bool{}

			for key := range preserve {
				if strings.HasPrefix(key, fieldName+".") {
					nestedPreserve[strings.TrimPrefix(key, fieldName+".")] = true
				}
			}

			SetPatient(df.Addr().Interface(), sf.Addr().Interface(), nestedPreserve)
		case reflect.Pointer:
			// Check if pointer points to a struct
			if df.Type().Elem().Kind() == reflect.Struct {
				if sf.IsNil() {
					break
				}

				// If dest pointer is nil, allocate it
				if df.IsNil() {
					df.Set(reflect.New(df.Type().Elem()))
				}

				// Recurse into struct via Elem()
				nestedPreserve := map[string]bool{}
				for key := range preserve {
					if strings.HasPrefix(key, fieldName+".") {
						nestedPreserve[strings.TrimPrefix(key, fieldName+".")] = true
					}
				}

				SetPatient(df.Elem().Addr().Interface(), sf.Elem().Addr().Interface(), nestedPreserve)
			}
		default:
			// Overwrite only when src has non-zero value
			if !sf.IsZero() {
				df.Set(sf)
			}
		}
	}
}
