// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

// Package vararray holds the escaped, delimiter-separated variable-length array
// codec (MarshalVarArray / UnmarshalVarArray and the delimiter/escape bytes). It
// is a dependency-free leaf (imports only stdlib), so consumers that need only
// this codec — e.g. the plugin SDK at pkg/pipeline/sdk — can use it without
// transitively pulling in pkg/encoding's logging dependencies. pkg/encoding
// forwards to these functions, so existing encoding.*VarArray callers are
// unaffected.
package vararray

import (
	"bytes"
	"errors"
)

const (
	// EntityDelimiter is the delimiter for entities in a variable-length array.
	EntityDelimiter = '|'
	// Escape is the escape character for entities in a variable-length array.
	Escape = '\\'
)

// MarshalVarArray marshals a byte slice into a variable-length array format.
// It escapes delimiter and escape characters within the source slice.
func MarshalVarArray(dest, src []byte) []byte {
	if bytes.IndexByte(src, EntityDelimiter) < 0 && bytes.IndexByte(src, Escape) < 0 {
		dest = append(dest, src...)
		dest = append(dest, EntityDelimiter)
		return dest
	}
	for _, b := range src {
		if b == EntityDelimiter || b == Escape {
			dest = append(dest, Escape)
		}
		dest = append(dest, b)
	}
	dest = append(dest, EntityDelimiter)
	return dest
}

// UnmarshalVarArray unmarshals a variable-length array from src starting at idx.
//
// WARNING: This function mutates src. Decoding is performed in-place by
// overwriting bytes in src[idx:next) to remove escape characters. The decoded
// value is the view src[idx:end] into the same backing array; copy it (for
// example, with bytes.Clone or append([]byte(nil), ...)) if you need to
// preserve the original encoded buffer or keep the decoded value independent of
// subsequent in-place decoding on the same buffer.
//
// It returns:
//   - end: the index of the first byte after the decoded value (exclusive)
//   - next: the index of the next element (the byte after the delimiter)
//
// The caller can iterate without creating subslices by tracking indices:
//
//	for idx < len(src) {
//	    end, next, err := UnmarshalVarArray(src, idx)
//	    // use src[idx:end]
//	    idx = next
//	}
func UnmarshalVarArray(src []byte, idx int) (int, int, error) {
	if idx >= len(src) {
		return 0, 0, errors.New("empty entity value")
	}
	if src[idx] == EntityDelimiter {
		// Empty value; value is src[idx:idx], next starts after the delimiter.
		return idx, idx + 1, nil
	}
	// Decode in-place: read index i, write index j.
	writeIdx := idx
	for readIdx := idx; readIdx < len(src); readIdx++ {
		b := src[readIdx]
		switch {
		case b == Escape:
			// Escape must be followed by at least one more byte.
			if readIdx+1 >= len(src) {
				return 0, 0, errors.New("invalid escape character")
			}
			readIdx++
			src[writeIdx] = src[readIdx]
			writeIdx++
		case b == EntityDelimiter:
			// Return end index of decoded value and index after delimiter.
			return writeIdx, readIdx + 1, nil
		default:
			src[writeIdx] = b
			writeIdx++
		}
	}
	return 0, 0, errors.New("invalid variable array")
}
