package logstorage

import (
	"fmt"
	"slices"
	"sort"
	"sync"
	"time"

	"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
	"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
	"github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil"

	"github.com/VictoriaMetrics/VictoriaLogs/lib/prefixfilter"
)

// LogRows holds a set of rows needed for Storage.MustAddRows
//
// LogRows must be obtained via GetLogRows()
type LogRows struct {
	// a holds all the bytes referred by items in LogRows
	a arena

	// fieldsBuf holds all the fields referred by items in LogRows
	fieldsBuf []Field

	// streamIDs holds streamIDs for rows added to LogRows
	streamIDs []streamID

	// timestamps holds timestamps for rows added to LogRows
	timestamps []int64

	// rows holds fields for rows added to LogRows.
	rows [][]Field

	// streamTagsCanonicals holds streamTagsCanonical entries for rows added to LogRows
	streamTagsCanonicals []string

	// streamFields contains names for stream fields
	streamFields []string

	// ignoreFields is a filter for fields, which must be ignored during data ingestion
	ignoreFields prefixfilter.Filter

	// decolorizeFields is a filter for fields, which must be cleared from ANSI color escape sequences
	decolorizeFields prefixfilter.Filter

	// extraFields contains extra fields to add to all the logs at MustAdd().
	extraFields []Field

	// extraStreamFields contains extraFields, which must be treated as stream fields.
	extraStreamFields []Field

	// defaultMsgValue contains default value for missing _msg field
	defaultMsgValue string
}

type logRows struct {
	// a holds all the bytes referred by items in logRows
	a arena

	// fieldsBuf holds all the fields referred by items in logRows
	fieldsBuf []Field

	// streamIDs holds streamIDs for rows added to logRows
	streamIDs []streamID

	// timestamps holds timestamps for rows added to logRows
	timestamps []int64

	// rows holds fields for rows added to logRows.
	rows [][]Field

	// sf is a helper for sorting fields in every added row
	sf sortedFields
}

func (lr *logRows) reset() {
	lr.a.reset()

	fb := lr.fieldsBuf
	for i := range fb {
		fb[i].Reset()
	}
	lr.fieldsBuf = fb[:0]

	sids := lr.streamIDs
	for i := range sids {
		sids[i].reset()
	}
	lr.streamIDs = sids[:0]

	lr.timestamps = lr.timestamps[:0]

	clear(lr.rows)
	lr.rows = lr.rows[:0]

	lr.sf = nil
}

// needFlush returns true if lr contains too much data, so it must be flushed to the storage.
func (lr *logRows) needFlush() bool {
	return len(lr.a.b) > (maxUncompressedBlockSize/8)*7
}

func (lr *logRows) mustAddRows(src *LogRows) {
	streamIDs := src.streamIDs
	timestamps := src.timestamps
	rows := src.rows

	if len(rows) == 0 {
		return
	}

	// a hint for the compiler for preventing from unnecessary bounds checks
	_ = streamIDs[len(rows)-1]
	_ = timestamps[len(rows)-1]

	for i := range rows {
		lr.mustAddRow(streamIDs[i], timestamps[i], rows[i])
	}
}

func (lr *logRows) mustAddRow(streamID streamID, timestamp int64, fields []Field) {
	lr.streamIDs = append(lr.streamIDs, streamID)
	lr.timestamps = append(lr.timestamps, timestamp)

	fieldsBuf := lr.fieldsBuf
	fieldsBufLen := len(fieldsBuf)
	fieldsBuf = slicesutil.SetLength(fieldsBuf, fieldsBufLen+len(fields))
	dstFields := fieldsBuf[fieldsBufLen:]
	lr.fieldsBuf = fieldsBuf
	lr.rows = append(lr.rows, dstFields)

	for i := range fields {
		f := &fields[i]

		fieldName := getCanonicalFieldName(f.Name)

		dstField := &dstFields[i]
		if len(fieldsBuf) >= len(fields) {
			fPrev := &fieldsBuf[len(fieldsBuf)-len(fields)]
			if fPrev.Name == fieldName {
				dstField.Name = fPrev.Name
			} else {
				dstField.Name = lr.a.copyString(fieldName)
			}
			if fPrev.Value == f.Value {
				dstField.Value = fPrev.Value
			} else {
				dstField.Value = lr.a.copyString(f.Value)
			}
		} else {
			dstField.Name = lr.a.copyString(fieldName)
			dstField.Value = lr.a.copyString(f.Value)
		}
	}
}

// Len returns the number of items in lr.
func (lr *logRows) Len() int {
	return len(lr.streamIDs)
}

// Less returns true if (streamID, timestamp) for row i is smaller than the (streamID, timestamp) for row j
func (lr *logRows) Less(i, j int) bool {
	a := &lr.streamIDs[i]
	b := &lr.streamIDs[j]
	if !a.equal(b) {
		return a.less(b)
	}
	return lr.timestamps[i] < lr.timestamps[j]
}

// Swap swaps rows i and j in lr.
func (lr *logRows) Swap(i, j int) {
	a := &lr.streamIDs[i]
	b := &lr.streamIDs[j]
	*a, *b = *b, *a

	tsA, tsB := &lr.timestamps[i], &lr.timestamps[j]
	*tsA, *tsB = *tsB, *tsA

	fieldsA, fieldsB := &lr.rows[i], &lr.rows[j]
	*fieldsA, *fieldsB = *fieldsB, *fieldsA
}

func (lr *logRows) sortFieldsInRows() {
	for _, row := range lr.rows {
		lr.sf = row
		sort.Sort(&lr.sf)
	}
}

type sortedFields []Field

func (sf *sortedFields) Len() int {
	return len(*sf)
}

func (sf *sortedFields) Less(i, j int) bool {
	a := *sf
	return a[i].Name < a[j].Name
}

func (sf *sortedFields) Swap(i, j int) {
	a := *sf
	a[i], a[j] = a[j], a[i]
}

func getLogRows() *logRows {
	v := lrPool.Get()
	if v == nil {
		return &logRows{}
	}
	return v.(*logRows)
}

func putLogRows(lr *logRows) {
	lr.reset()
	lrPool.Put(lr)
}

var lrPool sync.Pool

// ForEachRow calls callback for every row stored in the lr.
func (lr *LogRows) ForEachRow(callback func(streamHash uint64, r *InsertRow)) {
	r := GetInsertRow()
	for i, timestamp := range lr.timestamps {
		sid := &lr.streamIDs[i]

		streamHash := sid.id.lo ^ sid.id.hi

		r.TenantID = sid.tenantID
		r.StreamTagsCanonical = lr.streamTagsCanonicals[i]
		r.Timestamp = timestamp
		r.Fields = lr.rows[i]

		callback(streamHash, r)
	}
	// remove reference to logRows fields
	// since reset of r can modify actual LogRows
	r.Fields = nil
	PutInsertRow(r)
}

// Reset resets lr with all its settings.
//
// Call ResetKeepSettings() for resetting lr without resetting its settings.
func (lr *LogRows) Reset() {
	lr.ResetKeepSettings()

	lr.streamFields = lr.streamFields[:0]

	lr.ignoreFields.Reset()
	lr.decolorizeFields.Reset()

	lr.extraFields = nil

	clear(lr.extraStreamFields)
	lr.extraStreamFields = lr.extraStreamFields[:0]

	lr.defaultMsgValue = ""
}

// RowsCount returns current log rows count
func (lr *LogRows) RowsCount() int {
	return len(lr.rows)
}

// ResetKeepSettings resets rows stored in lr, while keeping its settings passed to GetLogRows().
func (lr *LogRows) ResetKeepSettings() {
	lr.a.reset()

	fb := lr.fieldsBuf
	for i := range fb {
		fb[i].Reset()
	}
	lr.fieldsBuf = fb[:0]

	sids := lr.streamIDs
	for i := range sids {
		sids[i].reset()
	}
	lr.streamIDs = sids[:0]

	clear(lr.streamTagsCanonicals)
	lr.streamTagsCanonicals = lr.streamTagsCanonicals[:0]

	lr.timestamps = lr.timestamps[:0]

	clear(lr.rows)
	lr.rows = lr.rows[:0]
}

// NeedFlush returns true if lr contains too much data, so it must be flushed to the storage.
func (lr *LogRows) NeedFlush() bool {
	return len(lr.a.b) > (maxUncompressedBlockSize/8)*7 || len(lr.rows) > maxUncompressedBlockSize/100
}

// MustAddInsertRow adds r to lr.
func (lr *LogRows) MustAddInsertRow(r *InsertRow) {
	st := GetStreamTags()
	streamTagsCanonical := r.StreamTagsCanonical
	if err := parseStreamTagsCanonical(st, streamTagsCanonical); err != nil {
		line := MarshalFieldsToJSON(nil, r.Fields)
		invalidStreamTagsLogger.Warnf("cannot unmarshal streamTagsCanonical: %s; skipping the log entry; log entry: %s", err, line)
		PutStreamTags(st)
		return
	}

	if st.normalize(r.Fields) {
		bLen := len(lr.a.b)
		lr.a.b = st.MarshalCanonical(lr.a.b)
		streamTagsCanonical = bytesutil.ToUnsafeString(lr.a.b[bLen:])
	}
	PutStreamTags(st)

	// Calculate the id for the StreamTags
	var sid streamID
	sid.tenantID = r.TenantID
	sid.id = hash128(bytesutil.ToUnsafeBytes(streamTagsCanonical))

	// Store the row
	lr.mustAddInternal(sid, r.Timestamp, r.Fields, streamTagsCanonical)
}

var invalidStreamTagsLogger = logger.WithThrottler("invalid_stream_tags", 5*time.Second)

func parseStreamTagsCanonical(dst *StreamTags, streamTagsCanonical string) error {
	src := bytesutil.ToUnsafeBytes(streamTagsCanonical)
	tail, err := dst.UnmarshalCanonicalInplace(src)
	if err != nil {
		return fmt.Errorf("cannot unmarshal streamTagsCanonical: %w", err)
	}
	if len(tail) > 0 {
		return fmt.Errorf("unexpected tail left after unmarshaling streamTagsCanonical; len(tail)=%d; streamTags: %s", len(tail), dst)
	}
	return nil
}

func (lr *LogRows) mustAdd(tenantID TenantID, timestamp int64, fields []Field) {
	lr.MustAdd(tenantID, timestamp, fields, -1)
}

// MustAdd adds a log entry with the given args to lr.
//
// If streamFieldsLen >= 0, then the given number of initial fields are used as log stream fields
// instead of the pre-configured stream fields from GetLogRows().
//
// It is OK to modify the args after returning from the function, since lr copies all the args to internal data.
//
// Log entries are dropped with the warning message in the following cases:
// - if there are too many log fields
// - if there are too long log field names
// - if the total length of log entries is too long
// - if the log entry contains _stream or _stream_id fields (these fields clash with the automatically generated fields by VictoriaLogs)
func (lr *LogRows) MustAdd(tenantID TenantID, timestamp int64, fields []Field, streamFieldsLen int) {
	// Compose StreamTags from fields
	st := GetStreamTags()
	if streamFieldsLen >= 0 {
		// Compose StreamTags from fields[:streamFieldsLen] and ignore lr.streamFields with lr.extraStreamFields.
		for _, f := range fields[:streamFieldsLen] {
			fieldName := getCanonicalFieldName(f.Name)

			if err := CheckStreamFieldName(fieldName); err != nil {
				line := MarshalFieldsToJSON(nil, fields)
				invalidStreamTagsLogger.Warnf("invalid stream field name %q: %s; skipping the log entry; log entry: %s", fieldName, err, line)
				return
			}

			if !lr.ignoreFields.MatchString(fieldName) {
				st.Add(fieldName, f.Value)
			}
		}
	} else if len(lr.streamFields) > 0 || len(lr.extraStreamFields) > 0 {
		// Compose StreamTags from lr.streamFields and lr.extraStreamFields.
		for _, f := range fields {
			fieldName := getCanonicalFieldName(f.Name)
			if slices.Contains(lr.streamFields, fieldName) {
				st.Add(fieldName, f.Value)
			}
		}
		for _, f := range lr.extraStreamFields {
			fieldName := getCanonicalFieldName(f.Name)
			st.Add(fieldName, f.Value)
		}
	} else {
		// Extract StreamTags from _stream field.
		// This can be used when importing the raw logs in JSON line format
		// received from /select/logsql/query endpoint.
		for i := range fields {
			f := &fields[i]
			switch f.Name {
			case "_stream":
				if err := st.unmarshalStringInplace(f.Value); err != nil {
					line := MarshalFieldsToJSON(nil, fields)
					invalidStreamTagsLogger.Warnf("cannot parse _stream=%s: %s; skipping the log entry; log entry: %s", f.Value, err, line)
					return
				}
				st.normalize(fields)
				// Remove _stream field, since it is re-generated from st below.
				f.Value = ""
			case "_stream_id":
				// Remove _stream_id field, since it is re-generated from st below.
				f.Value = ""
			}
		}
	}

	// Marshal StreamTags
	bb := bbPool.Get()
	bb.B = st.MarshalCanonical(bb.B)
	PutStreamTags(st)

	// Calculate the id for the StreamTags
	var sid streamID
	sid.tenantID = tenantID
	sid.id = hash128(bb.B)

	// Store the row
	streamTagsCanonical := bytesutil.ToUnsafeString(bb.B)
	lr.mustAddInternal(sid, timestamp, fields, streamTagsCanonical)
	bbPool.Put(bb)
}

func (lr *LogRows) mustAddInternal(sid streamID, timestamp int64, fields []Field, streamTagsCanonical string) {
	// Verify that the log entry doesn't exceed limits.
	if len(fields) > maxColumnsPerBlock {
		line := MarshalFieldsToJSON(nil, fields)
		tooManyColumnsLogger.Warnf("ignoring log entry with too big number of fields %d, since it exceeds the limit %d; "+
			"see https://docs.victoriametrics.com/victorialogs/faq/#how-many-fields-a-single-log-entry-may-contain ; log entry: %s", len(fields), maxColumnsPerBlock, line)
		return
	}
	for i := range fields {
		fieldName := fields[i].Name
		if len(fieldName) > maxFieldNameSize {
			line := MarshalFieldsToJSON(nil, fields)
			tooLongFieldNameLogger.Warnf("ignoring log entry with too long field name %q, since its length (%d) exceeds the limit %d bytes; "+
				"see https://docs.victoriametrics.com/victorialogs/faq/#what-is-the-maximum-supported-field-name-length ; log entry: %s",
				fieldName, len(fieldName), maxFieldNameSize, line)
			return
		}
	}
	if rowLen := EstimatedJSONRowLen(fields); rowLen > maxUncompressedBlockSize {
		line := MarshalFieldsToJSON(nil, fields)
		tooLongEntryLogger.Warnf("ignoring too long log entry with the estimated length of %d bytes, since it exceeds the limit %d bytes; "+
			"see https://docs.victoriametrics.com/victorialogs/faq/#what-length-a-log-record-is-expected-to-have ; log entry: %s", rowLen, maxUncompressedBlockSize, line)
		return
	}

	stcs := lr.streamTagsCanonicals
	if len(stcs) > 0 && string(stcs[len(stcs)-1]) == streamTagsCanonical {
		stcs = append(stcs, stcs[len(stcs)-1])
	} else {
		streamTagsCanonicalCopy := lr.a.copyString(streamTagsCanonical)
		stcs = append(stcs, streamTagsCanonicalCopy)
	}
	lr.streamTagsCanonicals = stcs

	lr.streamIDs = append(lr.streamIDs, sid)
	lr.timestamps = append(lr.timestamps, timestamp)

	fieldsLen := len(lr.fieldsBuf)
	hasMsgField := lr.addFieldsInternal(fields, &lr.ignoreFields, &lr.decolorizeFields, true)
	if lr.addFieldsInternal(lr.extraFields, nil, nil, false) {
		hasMsgField = true
	}

	// Add optional default _msg field
	if !hasMsgField && lr.defaultMsgValue != "" {
		lr.fieldsBuf = append(lr.fieldsBuf, Field{
			Value: lr.defaultMsgValue,
		})
	}

	// Add log row fields to lr.rows
	row := lr.fieldsBuf[fieldsLen:]
	lr.rows = append(lr.rows, row)
}

var (
	tooManyColumnsLogger   = logger.WithThrottler("too_many_columns", 5*time.Second)
	tooLongFieldNameLogger = logger.WithThrottler("too_logn_field_name", 5*time.Second)
	tooLongEntryLogger     = logger.WithThrottler("too_long_entry", 5*time.Second)
)

func (lr *LogRows) addFieldsInternal(fields []Field, ignoreFields, decolorizeFields *prefixfilter.Filter, mustCopyFields bool) bool {
	if len(fields) == 0 {
		return false
	}

	var prevRow []Field
	if len(lr.rows) > 0 {
		prevRow = lr.rows[len(lr.rows)-1]
	}

	fb := lr.fieldsBuf
	hasMsgField := false
	for i := range fields {
		f := &fields[i]

		fieldName := getCanonicalFieldName(f.Name)

		if ignoreFields.MatchString(fieldName) {
			continue
		}
		if f.Value == "" {
			// Skip fields without values
			continue
		}
		if fieldName == "_time" {
			// Values for the _time field are stored in lr.timestamps
			// See https://github.com/VictoriaMetrics/VictoriaLogs/issues/1168
			line := MarshalFieldsToJSON(nil, fields)
			unexpectedTimeFieldLogger.Warnf("skipping _time field with the value %q because the timestamp is parsed from another field "+
				"according to https://docs.victoriametrics.com/victorialogs/data-ingestion/#http-parameters ; log entry: %s", f.Value, line)
			continue
		}
		if fieldName == "_stream" || fieldName == "_stream_id" {
			line := MarshalFieldsToJSON(nil, fields)
			unexpectedStreamFieldLogger.Warnf("skipping %q field with the value %q since it clashes with the automatically generated field by VictoriaLogs; "+
				"see https://docs.victoriametrics.com/victorialogs/keyconcepts/#stream-fields; log entry: %s", fieldName, f.Value, line)
			continue
		}

		var prevField *Field
		if prevRow != nil && i < len(prevRow) {
			prevField = &prevRow[i]
		}

		fb = append(fb, Field{})
		dstField := &fb[len(fb)-1]

		if fieldName == "" {
			hasMsgField = true
		}

		if prevField != nil && prevField.Name == fieldName {
			dstField.Name = prevField.Name
		} else {
			if mustCopyFields {
				dstField.Name = lr.a.copyString(fieldName)
			} else {
				dstField.Name = fieldName
			}
			prevRow = nil
		}
		if prevField != nil && prevField.Value == f.Value {
			dstField.Value = prevField.Value
		} else {
			if mustCopyFields {
				dstField.Value = lr.a.copyString(f.Value)
			} else {
				dstField.Value = f.Value
			}

			if decolorizeFields.MatchString(fieldName) && hasColorSequences(dstField.Value) {
				bLen := len(lr.a.b)
				lr.a.b = dropColorSequences(lr.a.b, dstField.Value)
				dstField.Value = bytesutil.ToUnsafeString(lr.a.b[bLen:])
			}
		}
	}
	lr.fieldsBuf = fb

	return hasMsgField
}

var (
	unexpectedTimeFieldLogger   = logger.WithThrottler("unexpected_time_field", 5*time.Second)
	unexpectedStreamFieldLogger = logger.WithThrottler("unexpected_stream_field", 5*time.Second)
)

func getCanonicalFieldName(fieldName string) string {
	if fieldName == "_msg" {
		return ""
	}
	return fieldName
}

func getCanonicalColumnName(fieldName string) string {
	if fieldName == "" {
		return "_msg"
	}
	return fieldName
}

// GetRowString returns string representation of the row with the given idx.
func (lr *LogRows) GetRowString(idx int) string {
	tf := TimeFormatter(lr.timestamps[idx])
	streamTags := getStreamTagsString(lr.streamTagsCanonicals[idx])
	var fields []Field
	fields = append(fields[:0], lr.rows[idx]...)
	fields = append(fields, Field{
		Name:  "_time",
		Value: tf.String(),
	})
	fields = append(fields, Field{
		Name:  "_stream",
		Value: streamTags,
	})
	sort.Slice(fields, func(i, j int) bool {
		return fields[i].Name < fields[j].Name
	})
	line := MarshalFieldsToJSON(nil, fields)
	return string(line)
}

// GetLogRows returns LogRows from the pool for the given streamFields.
//
// streamFields is a set of fields, which must be associated with the stream.
//
// ignoreFields is a set of fields, which must be ignored during data ingestion.
// ignoreFields entries may end with '*'. In this case they match any fields with the prefix until '*'.
//
// decolorizeFields is a set of fields, which must be cleared from ANSI color escape sequences.
// decolorizeFields entries may end with '*'. In this case they match any fields with the prefix until '*'.
//
// extraFields is a set of fields, which must be added to all the logs passed to MustAdd().
//
// defaultMsgValue is the default value to store in non-existing or empty _msg.
//
// Return back it to the pool with PutLogRows() when it is no longer needed.
func GetLogRows(streamFields, ignoreFields, decolorizeFields []string, extraFields []Field, defaultMsgValue string) *LogRows {
	v := logRowsPool.Get()
	if v == nil {
		v = &LogRows{}
	}
	lr := v.(*LogRows)

	// initialize ignoreFields
	for _, f := range ignoreFields {
		f = getCanonicalFieldName(f)
		lr.ignoreFields.AddAllowFilter(f)
	}
	for _, f := range extraFields {
		// Extra fields must override the existing fields for the sake of consistency and security,
		// so the client won't be able to override them.
		fieldName := getCanonicalFieldName(f.Name)
		lr.ignoreFields.AddAllowFilter(fieldName)
	}

	// initialize decolorizeFields
	for _, f := range decolorizeFields {
		f = getCanonicalFieldName(f)
		lr.decolorizeFields.AddAllowFilter(f)
	}

	// Initialize streamFields
	for _, f := range streamFields {
		f = getCanonicalFieldName(f)
		if !lr.ignoreFields.MatchString(f) {
			lr.streamFields = append(lr.streamFields, f)
		}
	}

	// Initialize extraStreamFields
	for _, f := range extraFields {
		fieldName := getCanonicalFieldName(f.Name)
		if slices.Contains(streamFields, fieldName) {
			lr.extraStreamFields = append(lr.extraStreamFields, f)
			lr.streamFields = slices.DeleteFunc(lr.streamFields, func(s string) bool { return s == fieldName })
		}
	}

	lr.extraFields = extraFields
	lr.defaultMsgValue = defaultMsgValue

	return lr
}

// PutLogRows returns lr to the pool.
func PutLogRows(lr *LogRows) {
	lr.Reset()
	logRowsPool.Put(lr)
}

var logRowsPool sync.Pool

// EstimatedJSONRowLen returns an approximate length of the log entry with the given fields if represented as JSON.
//
// The calculation logic must stay in sync with block.uncompressedSizeBytes() in block.go.
// If you change logic here, update block.uncompressedSizeBytes() accordingly and vice versa.
func EstimatedJSONRowLen(fields []Field) int {
	n := len("{}\n")
	n += len(`"_time":""`) + len(time.RFC3339Nano)
	for _, f := range fields {
		// VictoriaLogs data model (https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model)
		// treats empty values as non-existing values
		if f.Value == "" {
			continue
		}

		name := getCanonicalColumnName(f.Name)
		n += estimatedJSONFieldLen(name, f.Value)
	}
	return n
}

// estimatedJSONFieldLen returns an approximate length of the field with the given name and value if represented as JSON.
//
// The field name must be in raw form (e.g., "" to "_msg") before passing.
func estimatedJSONFieldLen(name, value string) int {
	return len(`,"":""`) + len(name) + len(value)
}

// GetInsertRow returns InsertRow from a pool.
//
// Pass the returned row to PutInsertRow when it is no longer needed, so it could be reused.
func GetInsertRow() *InsertRow {
	v := insertRowsPool.Get()
	if v == nil {
		return &InsertRow{}
	}
	return v.(*InsertRow)
}

// PutInsertRow returns r to the pool, so it could be reused via GetInsertRow.
func PutInsertRow(r *InsertRow) {
	r.Reset()
	insertRowsPool.Put(r)
}

var insertRowsPool sync.Pool

// InsertRow represents a row to insert into VictoriaLogs via native protocol.
type InsertRow struct {
	TenantID            TenantID
	StreamTagsCanonical string
	Timestamp           int64
	Fields              []Field
}

// Reset resets r to zero value.
func (r *InsertRow) Reset() {
	r.TenantID.Reset()
	r.StreamTagsCanonical = ""
	r.Timestamp = 0

	clear(r.Fields)
	r.Fields = r.Fields[:0]
}

// Marshal appends marshaled r to dst and returns the result.
func (r *InsertRow) Marshal(dst []byte) []byte {
	dst = r.TenantID.marshal(dst)
	dst = encoding.MarshalBytes(dst, bytesutil.ToUnsafeBytes(r.StreamTagsCanonical))
	dst = encoding.MarshalUint64(dst, uint64(r.Timestamp))
	dst = encoding.MarshalVarUint64(dst, uint64(len(r.Fields)))
	for _, field := range r.Fields {
		dst = field.marshal(dst, true)
	}
	return dst
}

// AppendJSON appends marshaled r to dst in JSON format and returns the result.
func (r *InsertRow) AppendJSON(dst []byte) []byte {
	fields := r.Fields
	fields = SkipLeadingFieldsWithoutValues(fields)

	dst = append(dst, `{"_time":"`...)
	dst = marshalTimestampRFC3339NanoString(dst, r.Timestamp)
	dst = append(dst, '"')

	for i := range fields {
		f := &fields[i]
		if f.Value == "" {
			// Skip fields without values
			continue
		}
		dst = append(dst, ',')
		dst = f.marshalToJSON(dst)
	}
	dst = append(dst, '}')
	return dst
}

// UnmarshalInplace unmarshals r from src and returns the remaining tail.
//
// The r is valid until src contents isn't changed.
func (r *InsertRow) UnmarshalInplace(src []byte) ([]byte, error) {
	srcOrig := src

	tail, err := r.TenantID.unmarshal(src)
	if err != nil {
		return srcOrig, fmt.Errorf("cannot unmarshal tenantID: %w", err)
	}
	src = tail

	streamTagsCanonical, n := encoding.UnmarshalBytes(src)
	if n <= 0 {
		return srcOrig, fmt.Errorf("cannot unmarshal streamTagCanonical")
	}
	r.StreamTagsCanonical = bytesutil.ToUnsafeString(streamTagsCanonical)
	src = src[n:]

	if len(src) < 8 {
		return srcOrig, fmt.Errorf("cannot unmarshal timestamp")
	}
	timestamp := encoding.UnmarshalUint64(src)
	r.Timestamp = int64(timestamp)
	src = src[8:]

	fieldsLen, n := encoding.UnmarshalVarUint64(src)
	if n <= 0 {
		return srcOrig, fmt.Errorf("cannot unmarshal the number of fields")
	}
	if fieldsLen > maxColumnsPerBlock {
		return srcOrig, fmt.Errorf("too many fields in the log entry: %d; mustn't exceed %d", fieldsLen, maxColumnsPerBlock)
	}
	src = src[n:]

	r.Fields = slicesutil.SetLength(r.Fields, int(fieldsLen))
	for i := range r.Fields {
		tail, err = r.Fields[i].unmarshalInplace(src, true)
		if err != nil {
			return srcOrig, fmt.Errorf("cannot unmarshal field #%d: %w", i, err)
		}
		src = tail
	}

	return src, nil
}
