// 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 vectorized

import (
	modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1"
	"github.com/apache/skywalking-banyandb/pkg/pool"
)

// One pool per supported column element type. Pools hold *TypedColumn[T]
// instances that have been Reset; on Get the caller may need to grow the
// backing slice if the prior capacity is smaller than the new request.
var (
	int64ColumnPool      = pool.Register[*TypedColumn[int64]]("vectorized.column.int64")
	float64ColumnPool    = pool.Register[*TypedColumn[float64]]("vectorized.column.float64")
	stringColumnPool     = pool.Register[*TypedColumn[string]]("vectorized.column.string")
	bytesColumnPool      = pool.Register[*TypedColumn[[]byte]]("vectorized.column.bytes")
	int64ArrayColumnPool = pool.Register[*TypedColumn[[]int64]]("vectorized.column.int64array")
	strArrayColumnPool   = pool.Register[*TypedColumn[[]string]]("vectorized.column.strarray")
	tagValueColumnPool   = pool.Register[*TypedColumn[*modelv1.TagValue]]("vectorized.column.tagvalue")
	fieldValueColumnPool = pool.Register[*TypedColumn[*modelv1.FieldValue]]("vectorized.column.fieldvalue")
)

// AcquireColumn returns a recycled Column of the requested type. The
// returned column is Reset (length 0, validity cleared) and has a backing
// capacity of at least the requested size. Callers must pair every
// AcquireColumn with a matching ReleaseColumn once they are done with the
// column.
//
// Capacity behavior: pooled columns retain whichever capacity they had at
// the last Release. If a pooled column is too small for the new request the
// backing slice is reallocated; otherwise the existing backing slice is
// reused. Over time the pool converges to the largest capacity seen.
func AcquireColumn(t ColumnType, capacity int) Column {
	switch t {
	case ColumnTypeInt64:
		c := int64ColumnPool.Get()
		if c == nil {
			return NewInt64Column(capacity)
		}
		if cap(c.data) < capacity {
			c.data = make([]int64, 0, capacity)
		}
		return c
	case ColumnTypeFloat64:
		c := float64ColumnPool.Get()
		if c == nil {
			return NewFloat64Column(capacity)
		}
		if cap(c.data) < capacity {
			c.data = make([]float64, 0, capacity)
		}
		return c
	case ColumnTypeString:
		c := stringColumnPool.Get()
		if c == nil {
			return NewStringColumn(capacity)
		}
		if cap(c.data) < capacity {
			c.data = make([]string, 0, capacity)
		}
		return c
	case ColumnTypeBytes:
		c := bytesColumnPool.Get()
		if c == nil {
			return NewBytesColumn(capacity)
		}
		if cap(c.data) < capacity {
			c.data = make([][]byte, 0, capacity)
		}
		return c
	case ColumnTypeInt64Array:
		c := int64ArrayColumnPool.Get()
		if c == nil {
			return NewInt64ArrayColumn(capacity)
		}
		if cap(c.data) < capacity {
			c.data = make([][]int64, 0, capacity)
		}
		return c
	case ColumnTypeStrArray:
		c := strArrayColumnPool.Get()
		if c == nil {
			return NewStrArrayColumn(capacity)
		}
		if cap(c.data) < capacity {
			c.data = make([][]string, 0, capacity)
		}
		return c
	case ColumnTypeTagValue:
		c := tagValueColumnPool.Get()
		if c == nil {
			return NewTagValueColumn(capacity)
		}
		if cap(c.data) < capacity {
			c.data = make([]*modelv1.TagValue, 0, capacity)
		}
		return c
	case ColumnTypeFieldValue:
		c := fieldValueColumnPool.Get()
		if c == nil {
			return NewFieldValueColumn(capacity)
		}
		if cap(c.data) < capacity {
			c.data = make([]*modelv1.FieldValue, 0, capacity)
		}
		return c
	}
	panic("vectorized: unknown ColumnType " + t.String())
}

// ReleaseColumn returns a column to its per-type pool. The column is Reset
// (length cleared, validity cleared) before being put back. A nil column is
// a no-op so callers can release defensively. Unknown column types are also
// no-ops — the column is simply dropped on the floor for the GC.
func ReleaseColumn(c Column) {
	if c == nil {
		return
	}
	switch col := c.(type) {
	case *TypedColumn[int64]:
		col.Reset()
		int64ColumnPool.Put(col)
	case *TypedColumn[float64]:
		col.Reset()
		float64ColumnPool.Put(col)
	case *TypedColumn[string]:
		col.Reset()
		stringColumnPool.Put(col)
	case *TypedColumn[[]byte]:
		col.Reset()
		bytesColumnPool.Put(col)
	case *TypedColumn[[]int64]:
		col.Reset()
		int64ArrayColumnPool.Put(col)
	case *TypedColumn[[]string]:
		col.Reset()
		strArrayColumnPool.Put(col)
	case *TypedColumn[*modelv1.TagValue]:
		col.Reset()
		tagValueColumnPool.Put(col)
	case *TypedColumn[*modelv1.FieldValue]:
		col.Reset()
		fieldValueColumnPool.Put(col)
	}
}
