package com.zb.mom.ww.mxgateway.client;

import com.google.protobuf.ByteString;
import com.google.protobuf.Timestamp;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import mxaccess_gateway.v1.MxaccessGateway.BoolArray;
import mxaccess_gateway.v1.MxaccessGateway.DoubleArray;
import mxaccess_gateway.v1.MxaccessGateway.FloatArray;
import mxaccess_gateway.v1.MxaccessGateway.Int32Array;
import mxaccess_gateway.v1.MxaccessGateway.Int64Array;
import mxaccess_gateway.v1.MxaccessGateway.MxArray;
import mxaccess_gateway.v1.MxaccessGateway.MxDataType;
import mxaccess_gateway.v1.MxaccessGateway.MxValue;
import mxaccess_gateway.v1.MxaccessGateway.RawArray;
import mxaccess_gateway.v1.MxaccessGateway.StringArray;
import mxaccess_gateway.v1.MxaccessGateway.TimestampArray;

/**
 * Factory helpers for building {@link MxValue} and {@link MxArray} protobuf
 * messages and for converting them back into native Java types.
 *
 * <p>Each {@code *Value} factory sets the matching {@code MxDataType} and
 * COM {@code variant} type string so the worker can round-trip the value
 * through MXAccess without further coercion.
 */
public final class MxValues {
    private MxValues() {
    }

    /**
     * Builds a boolean {@link MxValue}.
     *
     * @param value the boolean payload
     * @return a populated {@code MxValue}
     */
    public static MxValue boolValue(boolean value) {
        return MxValue.newBuilder()
                .setDataType(MxDataType.MX_DATA_TYPE_BOOLEAN)
                .setVariantType("VT_BOOL")
                .setBoolValue(value)
                .build();
    }

    /**
     * Builds a 32-bit integer {@link MxValue}.
     *
     * @param value the int32 payload
     * @return a populated {@code MxValue}
     */
    public static MxValue int32Value(int value) {
        return MxValue.newBuilder()
                .setDataType(MxDataType.MX_DATA_TYPE_INTEGER)
                .setVariantType("VT_I4")
                .setInt32Value(value)
                .build();
    }

    /**
     * Builds a 64-bit integer {@link MxValue}.
     *
     * @param value the int64 payload
     * @return a populated {@code MxValue}
     */
    public static MxValue int64Value(long value) {
        return MxValue.newBuilder()
                .setDataType(MxDataType.MX_DATA_TYPE_INTEGER)
                .setVariantType("VT_I8")
                .setInt64Value(value)
                .build();
    }

    /**
     * Builds a 32-bit floating-point {@link MxValue}.
     *
     * @param value the float payload
     * @return a populated {@code MxValue}
     */
    public static MxValue floatValue(float value) {
        return MxValue.newBuilder()
                .setDataType(MxDataType.MX_DATA_TYPE_FLOAT)
                .setVariantType("VT_R4")
                .setFloatValue(value)
                .build();
    }

    /**
     * Builds a 64-bit floating-point {@link MxValue}.
     *
     * @param value the double payload
     * @return a populated {@code MxValue}
     */
    public static MxValue doubleValue(double value) {
        return MxValue.newBuilder()
                .setDataType(MxDataType.MX_DATA_TYPE_DOUBLE)
                .setVariantType("VT_R8")
                .setDoubleValue(value)
                .build();
    }

    /**
     * Builds a string {@link MxValue}.
     *
     * @param value the string payload
     * @return a populated {@code MxValue}
     */
    public static MxValue stringValue(String value) {
        return MxValue.newBuilder()
                .setDataType(MxDataType.MX_DATA_TYPE_STRING)
                .setVariantType("VT_BSTR")
                .setStringValue(value)
                .build();
    }

    /**
     * Builds a timestamp {@link MxValue} from an {@link Instant}.
     *
     * @param value the instant to encode as MXAccess time
     * @return a populated {@code MxValue}
     */
    public static MxValue timestampValue(Instant value) {
        return MxValue.newBuilder()
                .setDataType(MxDataType.MX_DATA_TYPE_TIME)
                .setVariantType("VT_DATE")
                .setTimestampValue(Timestamp.newBuilder()
                        .setSeconds(value.getEpochSecond())
                        .setNanos(value.getNano())
                        .build())
                .build();
    }

    /**
     * Converts an {@link MxValue} back into a native Java value.
     *
     * @param value the MXAccess value, may be {@code null} or marked as null
     * @return the boxed primitive, {@code String}, {@link Instant}, byte array,
     *         {@code List} of array elements, or {@code null} when the value
     *         carries no payload
     */
    public static Object nativeValue(MxValue value) {
        if (value == null || value.getIsNull()) {
            return null;
        }

        return switch (value.getKindCase()) {
            case BOOL_VALUE -> value.getBoolValue();
            case INT32_VALUE -> value.getInt32Value();
            case INT64_VALUE -> value.getInt64Value();
            case FLOAT_VALUE -> value.getFloatValue();
            case DOUBLE_VALUE -> value.getDoubleValue();
            case STRING_VALUE -> value.getStringValue();
            case TIMESTAMP_VALUE -> instant(value.getTimestampValue());
            case ARRAY_VALUE -> nativeArray(value.getArrayValue());
            case RAW_VALUE -> value.getRawValue().toByteArray();
            // Write-only sparse descriptor: never produced by a read/decoded
            // value, so it has no native representation.
            case SPARSE_ARRAY_VALUE -> null;
            case KIND_NOT_SET -> null;
        };
    }

    /**
     * Converts an {@link MxArray} into a native Java {@link List}.
     *
     * @param array the MXAccess array, may be {@code null}
     * @return a list of boxed primitives, strings, instants, or byte arrays;
     *         an empty list when the array carries no elements;
     *         {@code null} when {@code array} is {@code null}
     */
    public static Object nativeArray(MxArray array) {
        if (array == null) {
            return null;
        }

        return switch (array.getValuesCase()) {
            case BOOL_VALUES -> List.copyOf(array.getBoolValues().getValuesList());
            case INT32_VALUES -> List.copyOf(array.getInt32Values().getValuesList());
            case INT64_VALUES -> List.copyOf(array.getInt64Values().getValuesList());
            case FLOAT_VALUES -> List.copyOf(array.getFloatValues().getValuesList());
            case DOUBLE_VALUES -> List.copyOf(array.getDoubleValues().getValuesList());
            case STRING_VALUES -> List.copyOf(array.getStringValues().getValuesList());
            case TIMESTAMP_VALUES -> timestampValues(array.getTimestampValues());
            case RAW_VALUES -> rawValues(array.getRawValues());
            case VALUES_NOT_SET -> List.of();
        };
    }

    /**
     * Builds an {@link MxArray} of strings.
     *
     * @param values the string elements; the resulting array carries the size as a single dimension
     * @return a populated {@code MxArray}
     */
    public static MxArray stringArray(List<String> values) {
        return MxArray.newBuilder()
                .setElementDataType(MxDataType.MX_DATA_TYPE_STRING)
                .setVariantType("VT_ARRAY|VT_BSTR")
                .addDimensions(values.size())
                .setStringValues(StringArray.newBuilder().addAllValues(values))
                .build();
    }

    /**
     * Builds an {@link MxArray} of 32-bit integers.
     *
     * @param values the int32 elements; the resulting array carries the size as a single dimension
     * @return a populated {@code MxArray}
     */
    public static MxArray int32Array(List<Integer> values) {
        return MxArray.newBuilder()
                .setElementDataType(MxDataType.MX_DATA_TYPE_INTEGER)
                .setVariantType("VT_ARRAY|VT_I4")
                .addDimensions(values.size())
                .setInt32Values(Int32Array.newBuilder().addAllValues(values))
                .build();
    }

    /**
     * Returns a stable name for the {@link MxValue} kind, useful for logs.
     *
     * @param value the MXAccess value, may be {@code null}
     * @return the {@code KindCase} name, or {@code "KIND_NOT_SET"} when {@code value} is {@code null}
     */
    public static String kindName(MxValue value) {
        return value == null ? "KIND_NOT_SET" : value.getKindCase().name();
    }

    private static Instant instant(Timestamp timestamp) {
        return Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos());
    }

    private static List<Instant> timestampValues(TimestampArray array) {
        List<Instant> values = new ArrayList<>();
        for (Timestamp timestamp : array.getValuesList()) {
            values.add(instant(timestamp));
        }
        return values;
    }

    private static List<byte[]> rawValues(RawArray array) {
        List<byte[]> values = new ArrayList<>();
        for (ByteString rawValue : array.getValuesList()) {
            values.add(rawValue.toByteArray());
        }
        return values;
    }

    @SuppressWarnings("unused")
    private static void generatedTypeReferences(
            BoolArray boolArray,
            Int64Array int64Array,
            FloatArray floatArray,
            DoubleArray doubleArray) {
        // Keeps generated repeated-value imports visible for javadocs and IDE navigation.
    }
}
