package mxgateway

import (
	"fmt"
	"time"

	pb "gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated"
	"google.golang.org/protobuf/types/known/timestamppb"
)

// BoolValue builds an MXAccess Boolean value.
func BoolValue(value bool) *MxValue {
	return &pb.MxValue{
		DataType:    pb.MxDataType_MX_DATA_TYPE_BOOLEAN,
		VariantType: "VT_BOOL",
		Kind:        &pb.MxValue_BoolValue{BoolValue: value},
	}
}

// Int32Value builds an MXAccess Int32 value.
func Int32Value(value int32) *MxValue {
	return &pb.MxValue{
		DataType:    pb.MxDataType_MX_DATA_TYPE_INTEGER,
		VariantType: "VT_I4",
		Kind:        &pb.MxValue_Int32Value{Int32Value: value},
	}
}

// Int64Value builds an MXAccess Int64 value.
func Int64Value(value int64) *MxValue {
	return &pb.MxValue{
		DataType:    pb.MxDataType_MX_DATA_TYPE_INTEGER,
		VariantType: "VT_I8",
		Kind:        &pb.MxValue_Int64Value{Int64Value: value},
	}
}

// FloatValue builds an MXAccess Float value.
func FloatValue(value float32) *MxValue {
	return &pb.MxValue{
		DataType:    pb.MxDataType_MX_DATA_TYPE_FLOAT,
		VariantType: "VT_R4",
		Kind:        &pb.MxValue_FloatValue{FloatValue: value},
	}
}

// DoubleValue builds an MXAccess Double value.
func DoubleValue(value float64) *MxValue {
	return &pb.MxValue{
		DataType:    pb.MxDataType_MX_DATA_TYPE_DOUBLE,
		VariantType: "VT_R8",
		Kind:        &pb.MxValue_DoubleValue{DoubleValue: value},
	}
}

// StringValue builds an MXAccess String value.
func StringValue(value string) *MxValue {
	return &pb.MxValue{
		DataType:    pb.MxDataType_MX_DATA_TYPE_STRING,
		VariantType: "VT_BSTR",
		Kind:        &pb.MxValue_StringValue{StringValue: value},
	}
}

// TimestampValue builds an MXAccess timestamp value from a Go time.
func TimestampValue(value time.Time) *MxValue {
	return &pb.MxValue{
		DataType:    pb.MxDataType_MX_DATA_TYPE_TIME,
		VariantType: "VT_DATE",
		Kind:        &pb.MxValue_TimestampValue{TimestampValue: timestamppb.New(value)},
	}
}

// NativeValue converts a protobuf MxValue to the closest Go representation
// without discarding raw fallback data.
func NativeValue(value *MxValue) (any, error) {
	if value == nil || value.GetIsNull() {
		return nil, nil
	}

	switch kind := value.GetKind().(type) {
	case *pb.MxValue_BoolValue:
		return kind.BoolValue, nil
	case *pb.MxValue_Int32Value:
		return kind.Int32Value, nil
	case *pb.MxValue_Int64Value:
		return kind.Int64Value, nil
	case *pb.MxValue_FloatValue:
		return kind.FloatValue, nil
	case *pb.MxValue_DoubleValue:
		return kind.DoubleValue, nil
	case *pb.MxValue_StringValue:
		return kind.StringValue, nil
	case *pb.MxValue_TimestampValue:
		if kind.TimestampValue == nil {
			return nil, nil
		}
		return kind.TimestampValue.AsTime(), nil
	case *pb.MxValue_ArrayValue:
		return NativeArray(kind.ArrayValue)
	case *pb.MxValue_RawValue:
		return append([]byte(nil), kind.RawValue...), nil
	default:
		return nil, fmt.Errorf("mxgateway: unsupported value kind %T", kind)
	}
}

// NativeArray converts a protobuf MxArray to the closest Go slice
// representation.
func NativeArray(array *MxArray) (any, error) {
	if array == nil {
		return nil, nil
	}

	switch values := array.GetValues().(type) {
	case *pb.MxArray_BoolValues:
		return append([]bool(nil), values.BoolValues.GetValues()...), nil
	case *pb.MxArray_Int32Values:
		return append([]int32(nil), values.Int32Values.GetValues()...), nil
	case *pb.MxArray_Int64Values:
		return append([]int64(nil), values.Int64Values.GetValues()...), nil
	case *pb.MxArray_FloatValues:
		return append([]float32(nil), values.FloatValues.GetValues()...), nil
	case *pb.MxArray_DoubleValues:
		return append([]float64(nil), values.DoubleValues.GetValues()...), nil
	case *pb.MxArray_StringValues:
		return append([]string(nil), values.StringValues.GetValues()...), nil
	case *pb.MxArray_TimestampValues:
		result := make([]time.Time, 0, len(values.TimestampValues.GetValues()))
		for _, value := range values.TimestampValues.GetValues() {
			if value == nil {
				result = append(result, time.Time{})
				continue
			}
			result = append(result, value.AsTime())
		}
		return result, nil
	case *pb.MxArray_RawValues:
		rawValues := values.RawValues.GetValues()
		result := make([][]byte, 0, len(rawValues))
		for _, value := range rawValues {
			result = append(result, append([]byte(nil), value...))
		}
		return result, nil
	default:
		return nil, fmt.Errorf("mxgateway: unsupported array value kind %T", values)
	}
}
