package pgx

import (
	"database/sql/driver"
	"encoding/binary"
	"fmt"
	"slices"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgtype"
	"github.com/pgvector/pgvector-go"
	"github.com/x448/float16"
)

type HalfVectorCodec struct{}

func (HalfVectorCodec) FormatSupported(format int16) bool {
	return format == pgx.BinaryFormatCode || format == pgx.TextFormatCode
}

func (HalfVectorCodec) PreferredFormat() int16 {
	return pgx.BinaryFormatCode
}

func (HalfVectorCodec) PlanEncode(m *pgtype.Map, oid uint32, format int16, value any) pgtype.EncodePlan {
	_, ok := value.(pgvector.HalfVector)
	if !ok {
		return nil
	}

	switch format {
	case pgx.BinaryFormatCode:
		return encodePlanHalfVectorCodecBinary{}
	case pgx.TextFormatCode:
		return encodePlanHalfVectorCodecText{}
	}

	return nil
}

type encodePlanHalfVectorCodecBinary struct{}

func (encodePlanHalfVectorCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) {
	v := value.(pgvector.HalfVector)
	vec := v.Slice()
	dim := len(vec)
	buf = slices.Grow(buf, 4+2*dim)
	buf = binary.BigEndian.AppendUint16(buf, uint16(dim))
	buf = binary.BigEndian.AppendUint16(buf, 0)
	for _, v := range vec {
		buf = binary.BigEndian.AppendUint16(buf, float16.Fromfloat32(v).Bits())
	}
	return buf, nil
}

type encodePlanHalfVectorCodecText struct{}

func (encodePlanHalfVectorCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) {
	v := value.(pgvector.HalfVector)
	return v.EncodeText(buf)
}

func (HalfVectorCodec) PlanScan(m *pgtype.Map, oid uint32, format int16, target any) pgtype.ScanPlan {
	_, ok := target.(*pgvector.HalfVector)
	if !ok {
		return nil
	}

	switch format {
	case pgx.BinaryFormatCode:
		return scanPlanHalfVectorCodecBinary{}
	case pgx.TextFormatCode:
		return scanPlanHalfVectorCodecText{}
	}

	return nil
}

type scanPlanHalfVectorCodecBinary struct{}

func (scanPlanHalfVectorCodecBinary) Scan(src []byte, dst any) error {
	v := (dst).(*pgvector.HalfVector)
	buf := src
	dim := int(binary.BigEndian.Uint16(buf[0:2]))
	unused := binary.BigEndian.Uint16(buf[2:4])
	if unused != 0 {
		return fmt.Errorf("expected unused to be 0")
	}

	vec := make([]float32, 0, dim)
	offset := 4
	for i := 0; i < dim; i++ {
		vec = append(vec, float16.Frombits(binary.BigEndian.Uint16(buf[offset:offset+2])).Float32())
		offset += 2
	}
	v.SetSlice(vec)
	return nil
}

type scanPlanHalfVectorCodecText struct{}

func (scanPlanHalfVectorCodecText) Scan(src []byte, dst any) error {
	v := (dst).(*pgvector.HalfVector)
	return v.Scan(src)
}

func (c HalfVectorCodec) DecodeDatabaseSQLValue(m *pgtype.Map, oid uint32, format int16, src []byte) (driver.Value, error) {
	return c.DecodeValue(m, oid, format, src)
}

func (c HalfVectorCodec) DecodeValue(m *pgtype.Map, oid uint32, format int16, src []byte) (any, error) {
	if src == nil {
		return nil, nil
	}

	var vec pgvector.HalfVector
	scanPlan := c.PlanScan(m, oid, format, &vec)
	if scanPlan == nil {
		return nil, fmt.Errorf("Unable to decode halfvec type")
	}

	err := scanPlan.Scan(src, &vec)
	if err != nil {
		return nil, err
	}

	return vec, nil
}
