package partners

import (
	"bytes"
	"encoding/json"
	"fmt"
)

// NullInt is a three-state JSON field: ABSENT, explicitly NULL, or a value.
//
// It exists because *int64 cannot express all three. encoding/json maps both
// "field missing" and `"field": null` to a nil *int64, so with a plain pointer
// there is NO way for a client to clear a column back to NULL — the one thing it
// would most obviously want to do after setting it. `**int64` looks like the fix
// and is not: json.Unmarshal sets the outer pointer to nil for a JSON null
// rather than allocating it, so the extra level collapses to the same result.
//
// This type is not general-purpose and should not be spread around the codebase.
// The contract it provides is:
//
//	"billing_address_id": null   -> clear the column to NULL
//	"billing_address_id": 42     -> set it to 42
//	(no such key)                -> leave the column alone
//
// Absent is tracked by Present, because Present==false means UnmarshalJSON was
// never called for the field. Null is tracked by Valid, because Valid==false
// with Present==true is precisely "the client asked for NULL".
type NullInt struct {
	Present bool
	Valid   bool
	Value   int64
}

// UnmarshalJSON implements json.Unmarshaler. A JSON null is a value here, not
// the absence of one, which is the whole reason this type exists; it must not
// be treated like the other json.Unmarshaler cases that just skip.
func (n *NullInt) UnmarshalJSON(b []byte) error {
	n.Present = true
	if bytes.Equal(bytes.TrimSpace(b), []byte("null")) {
		n.Valid = false
		n.Value = 0
		return nil
	}
	if err := json.Unmarshal(b, &n.Value); err != nil {
		return fmt.Errorf("must be a number or null: %w", err)
	}
	n.Valid = true
	return nil
}

// MarshalJSON emits the value, or null when the field was cleared. Absent
// marshals as null too; that is lossy on the way out, but this type is only
// decoded from requests, and rounding-tripping a patch is not a use it has.
func (n NullInt) MarshalJSON() ([]byte, error) {
	if !n.Valid {
		return []byte("null"), nil
	}
	return json.Marshal(n.Value)
}
