using System.Buffers;
using System.Numerics;
using System.Text.Json;

namespace HyperMath.Core
{
///


/// Represents a multi-dimensional tensor with generic type T.
///

///
/// The type of elements in the tensor. Must be unmanaged and support numeric operations.
///
public class Tensor : IDisposable where T : unmanaged, INumber, IExponentialFunctions, ITrigonometricFunctions
{
///

/// Manages the shared data array with reference counting.
///

private class SharedData
{
public T[] Data;
public int Length => Data.Length;
public int refCount;

        public SharedData(int size)
        {
            Data = ArrayPool<T>.Shared.Rent(size);
            Array.Clear(Data, 0, size);
            refCount = 1;
        }

        private SharedData(T[] data)
        {
            Data = data;
            refCount = 1;
        }

        public void AddRef()
        {
            Interlocked.Increment(ref refCount);
        }

        public void Release()
        {
            if (Interlocked.Decrement(ref refCount) == 0)
            {
                ArrayPool<T>.Shared.Return(Data, clearArray: true);
            }
        }

        public SharedData CloneData()
        {
            var newData = ArrayPool<T>.Shared.Rent(Data.Length);
            Array.Copy(Data, newData, Data.Length);
            return new SharedData(newData);
        }
    }

    private SharedData sharedData;
    private readonly int[] shape;
    private readonly int[] strides;
    private int offset;
    private bool disposed = false;

    /// <summary>
    /// Gets the shape of the tensor.
    /// </summary>
    public int[] Shape => (int[])shape.Clone();

    /// <summary>
    /// Gets the total number of elements in the tensor.
    /// </summary>
    public int TotalSize { get; }

    /// <summary>
    /// Threshold for enabling parallelization.
    /// </summary>
    public static int ParallelizationThreshold { get; set; } = 1000;

    #region Constructors

    /// <summary>
    /// Initializes a new tensor with the specified dimensions.
    /// </summary>
    /// <param name="dimensions">Sizes for each dimension.</param>
    public Tensor(params int[] dimensions)
    {
        if (dimensions == null || dimensions.Length == 0)
            throw new ArgumentException("Tensor must have at least one dimension.", nameof(dimensions));

        foreach (var dim in dimensions)
        {
            if (dim <= 0)
                throw new ArgumentException("Each dimension size must be positive.", nameof(dimensions));
        }

        shape = (int[])dimensions.Clone();
        strides = CalculateStrides(shape);
        TotalSize = shape.Aggregate(1, (a, b) => a * b);
        sharedData = new SharedData(TotalSize);
        offset = 0;
    }

    private Tensor(SharedData sharedData, int[] shape, int[] strides, int offset)
    {
        this.sharedData = sharedData;
        this.shape = shape;
        this.strides = strides;
        this.offset = offset;
        TotalSize = shape.Aggregate(1, (a, b) => a * b);
        sharedData.AddRef();
    }

    #endregion

    #region Indexer and Element Access

    /// <summary>
    /// Gets or sets the element at the specified indices.
    /// </summary>
    /// <param name="indices">Indices array.</param>
    public T this[params int[] indices]
    {
        get
        {
            EnsureNotDisposed();
            int flatIndex = CalculateFlatIndex(indices);
            return sharedData.Data[flatIndex];
        }
        set
        {
            EnsureNotDisposed();
            int flatIndex = CalculateFlatIndex(indices);
            EnsureUniqueData();
            sharedData.Data[flatIndex] = value;
        }
    }

    /// <summary>
    /// Ensures that the data is unique before modifying it.
    /// </summary>
    private void EnsureUniqueData()
    {
        if (sharedData.refCount > 1)
        {
            var newSharedData = sharedData.CloneData();
            sharedData.Release();
            sharedData = newSharedData;
            offset = 0;
        }
    }

    /// <summary>
    /// Calculates the flat index from multi-dimensional indices.
    /// </summary>
    /// <param name="indices">Indices array.</param>
    /// <returns>Flat index.</returns>
    private int CalculateFlatIndex(int[] indices)
    {
        if (indices == null)
            throw new ArgumentNullException(nameof(indices));

        if (indices.Length != shape.Length)
            throw new ArgumentException("Number of indices must match number of tensor dimensions.", nameof(indices));

        int flatIndex = offset;
        for (int i = 0; i < shape.Length; i++)
        {
            if (indices[i] < 0 || indices[i] >= shape[i])
                throw new ArgumentOutOfRangeException(nameof(indices), $"Index {indices[i]} out of bounds for dimension {i}.");

            flatIndex += indices[i] * strides[i];
        }

        if (flatIndex < 0 || flatIndex >= sharedData.Data.Length)
            throw new IndexOutOfRangeException("Flat index out of range.");

        return flatIndex;
    }

    #endregion

    #region Cloning and Slicing

    /// <summary>
    /// Clones the tensor, creating a new tensor with the same data.
    /// </summary>
    /// <returns>A new tensor cloned from the current tensor.</returns>
    public Tensor<T> Clone()
    {
        EnsureNotDisposed();
        var newSharedData = sharedData.CloneData();
        return new Tensor<T>(newSharedData, (int[])shape.Clone(), (int[])strides.Clone(), offset);
    }

    /// <summary>
    /// Creates a tensor view (slice) from the current tensor.
    /// </summary>
    /// <param name="ranges">Ranges for each dimension.</param>
    /// <returns>A new tensor view.</returns>
    public Tensor<T> Slice(params Range[] ranges)
    {
        EnsureNotDisposed();

        if (ranges == null)
            throw new ArgumentNullException(nameof(ranges));

        if (ranges.Length != shape.Length)
            throw new ArgumentException("Number of ranges must match number of tensor dimensions.", nameof(ranges));

        int[] newShape = new int[shape.Length];
        int newOffset = offset;

        for (int i = 0; i < shape.Length; i++)
        {
            var range = ranges[i];
            int start = range.Start.IsFromEnd ? shape[i] - range.Start.Value : range.Start.Value;
            int end = range.End.IsFromEnd ? shape[i] - range.End.Value : range.End.Value;

            if (start < 0 || end > shape[i] || start > end)
                throw new ArgumentOutOfRangeException(nameof(ranges), $"Invalid range for dimension {i}.");

            newShape[i] = end - start;
            newOffset += start * strides[i];
        }

        return new Tensor<T>(sharedData, newShape, strides, newOffset);
    }

    /// <summary>
    /// Provides advanced slicing with steps.
    /// </summary>
    /// <param name="ranges">Ranges with steps for each dimension.</param>
    /// <returns>A new tensor view with steps.</returns>
    public Tensor<T> SliceWithSteps(params (int start, int end, int step)[] ranges)
    {
        EnsureNotDisposed();

        if (ranges == null)
            throw new ArgumentNullException(nameof(ranges));

        if (ranges.Length != shape.Length)
            throw new ArgumentException("Number of ranges must match number of tensor dimensions.", nameof(ranges));

        int[] newShape = new int[shape.Length];
        int newOffset = offset;
        int[] newStrides = new int[shape.Length];

        for (int i = 0; i < shape.Length; i++)
        {
            var (start, end, step) = ranges[i];
            if (step <= 0)
                throw new ArgumentException("Step must be positive.", nameof(ranges));

            if (start < 0 || start >= shape[i])
                throw new ArgumentOutOfRangeException(nameof(ranges), $"Start index out of bounds for dimension {i}.");

            if (end < start || end > shape[i])
                throw new ArgumentOutOfRangeException(nameof(ranges), $"End index out of bounds for dimension {i}.");

            newShape[i] = (int)Math.Ceiling((double)(end - start) / step);
            newOffset += start * strides[i];
            newStrides[i] = strides[i] * step;
        }

        return new Tensor<T>(sharedData, newShape, newStrides, newOffset);
    }

    #endregion

    #region Arithmetic Operations

    // Custom delegate types to support ref parameters
    private delegate void ElementWiseOperationDelegate(ref T a, T b);
    private delegate void ScalarOperationDelegate(ref T a, T b);

    // Element-wise operations, in-place operations, and scalar operations

    /// <summary>
    /// Performs element-wise addition with another tensor.
    /// </summary>
    public Tensor<T> Add(Tensor<T> other)
    {
        return ElementWiseOperation(other, (a, b) => a + b);
    }

    /// <summary>
    /// Performs in-place addition with another tensor.
    /// </summary>
    public void AddInPlace(Tensor<T> other)
    {
        InPlaceElementWiseOperation(other, AddOperation);
    }

    // Named method for addition operation
    private void AddOperation(ref T a, T b)
    {
        a = a + b;
    }

    /// <summary>
    /// Asynchronously performs element-wise addition with another tensor.
    /// </summary>
    public async Task<Tensor<T>> AddAsync(Tensor<T> other)
    {
        return await Task.Run(() => Add(other));
    }

    // Similar methods for Subtract, Multiply, Divide, etc.

    public Tensor<T> Subtract(Tensor<T> other)
    {
        return ElementWiseOperation(other, (a, b) => a - b);
    }

    public void SubtractInPlace(Tensor<T> other)
    {
        InPlaceElementWiseOperation(other, SubtractOperation);
    }

    private void SubtractOperation(ref T a, T b)
    {
        a = a - b;
    }

    public async Task<Tensor<T>> SubtractAsync(Tensor<T> other)
    {
        return await Task.Run(() => Subtract(other));
    }

    public Tensor<T> Multiply(Tensor<T> other)
    {
        return ElementWiseOperation(other, (a, b) => a * b);
    }

    public void MultiplyInPlace(Tensor<T> other)
    {
        InPlaceElementWiseOperation(other, MultiplyOperation);
    }

    private void MultiplyOperation(ref T a, T b)
    {
        a = a * b;
    }

    public async Task<Tensor<T>> MultiplyAsync(Tensor<T> other)
    {
        return await Task.Run(() => Multiply(other));
    }

    public Tensor<T> Divide(Tensor<T> other)
    {
        return ElementWiseOperation(other, (a, b) => a / b);
    }

    public void DivideInPlace(Tensor<T> other)
    {
        InPlaceElementWiseOperation(other, DivideOperation);
    }

    private void DivideOperation(ref T a, T b)
    {
        a = a / b;
    }

    public async Task<Tensor<T>> DivideAsync(Tensor<T> other)
    {
        return await Task.Run(() => Divide(other));
    }

    // Scalar operations

    public Tensor<T> AddScalar(T scalar)
    {
        return ScalarOperation(scalar, (a, b) => a + b);
    }

    public void AddScalarInPlace(T scalar)
    {
        InPlaceScalarOperation(scalar, AddScalarOperation);
    }

    private void AddScalarOperation(ref T a, T b)
    {
        a = a + b;
    }

    public Tensor<T> SubtractScalar(T scalar)
    {
        return ScalarOperation(scalar, (a, b) => a - b);
    }

    public void SubtractScalarInPlace(T scalar)
    {
        InPlaceScalarOperation(scalar, SubtractScalarOperation);
    }

    private void SubtractScalarOperation(ref T a, T b)
    {
        a = a - b;
    }

    public Tensor<T> MultiplyScalar(T scalar)
    {
        return ScalarOperation(scalar, (a, b) => a * b);
    }

    public void MultiplyScalarInPlace(T scalar)
    {
        InPlaceScalarOperation(scalar, MultiplyScalarOperation);
    }

    private void MultiplyScalarOperation(ref T a, T b)
    {
        a = a * b;
    }

    public Tensor<T> DivideScalar(T scalar)
    {
        return ScalarOperation(scalar, (a, b) => a / b);
    }

    public void DivideScalarInPlace(T scalar)
    {
        InPlaceScalarOperation(scalar, DivideScalarOperation);
    }

    private void DivideScalarOperation(ref T a, T b)
    {
        a = a / b;
    }


    #endregion

    #region Advanced Operations

    /// <summary>
    /// Performs a matrix multiplication with another tensor.
    /// </summary>
    public Tensor<T> MatMul(Tensor<T> other)
    {
        EnsureNotDisposed();
        other?.EnsureNotDisposed();

        if (other == null)
            throw new ArgumentNullException(nameof(other));

        if (this.shape.Length != 2 || other.shape.Length != 2)
            throw new ArgumentException("MatMul is only supported for 2D tensors.");

        if (this.shape[1] != other.shape[0])
            throw new ArgumentException($"Inner tensor dimensions must match for MatMul. Found {this.shape[1]} and {other.shape[0]}.");

        int m = this.shape[0];
        int n = this.shape[1];
        int p = other.shape[1];

        Tensor<T> result = new Tensor<T>(m, p);

        if (TotalSize >= ParallelizationThreshold)
        {
            Parallel.For(0, m, i =>
            {
                for (int j = 0; j < p; j++)
                {
                    T sum = T.Zero;
                    for (int k = 0; k < n; k++)
                    {
                        sum += this[i, k] * other[k, j];
                    }
                    result[i, j] = sum;
                }
            });
        }
        else
        {
            for (int i = 0; i < m; i++)
            {
                for (int j = 0; j < p; j++)
                {
                    T sum = T.Zero;
                    for (int k = 0; k < n; k++)
                    {
                        sum += this[i, k] * other[k, j];
                    }
                    result[i, j] = sum;
                }
            }
        }

        return result;
    }

    // Implement BatchMatrixMultiply, DotProduct, Transpose, etc., following similar patterns.

    /// <summary>
    /// Transposes the tensor along two specified axes.
    /// </summary>
    public Tensor<T> Transpose(int axis1, int axis2)
    {
        EnsureNotDisposed();

        if (axis1 < 0 || axis1 >= shape.Length)
            throw new ArgumentOutOfRangeException(nameof(axis1), "Axis1 is out of bounds.");

        if (axis2 < 0 || axis2 >= shape.Length)
            throw new ArgumentOutOfRangeException(nameof(axis2), "Axis2 is out of bounds.");

        int[] newShape = (int[])shape.Clone();
        int[] newStrides = (int[])strides.Clone();

        // Swap shape and strides
        (newShape[axis1], newShape[axis2]) = (newShape[axis2], newShape[axis1]);
        (newStrides[axis1], newStrides[axis2]) = (newStrides[axis2], newStrides[axis1]);

        return new Tensor<T>(sharedData, newShape, newStrides, offset);
    }

    #endregion

    #region Helper Methods

    /// <summary>
    /// Performs element-wise operation with another tensor.
    /// </summary>
    private Tensor<T> ElementWiseOperation(Tensor<T> other, Func<T, T, T> operation)
    {
        EnsureNotDisposed();

        if (other == null)
            throw new ArgumentNullException(nameof(other));

        other.EnsureNotDisposed();

        int[] resultShape = DetermineBroadcastShape(this.shape, other.shape);
        Tensor<T> result = new Tensor<T>(resultShape);

        int[] aStridesBroadcasted = ComputeBroadcastStrides(this.shape, resultShape, this.strides);
        int[] bStridesBroadcasted = ComputeBroadcastStrides(other.shape, resultShape, other.strides);

        if (result.TotalSize >= ParallelizationThreshold)
        {
            Parallel.For(0, result.TotalSize, i =>
            {
                int[] indices = UnflattenIndex(i, resultShape);
                int aIndex = ComputeFlatIndex(indices, aStridesBroadcasted) + this.offset;
                int bIndex = ComputeFlatIndex(indices, bStridesBroadcasted) + other.offset;
                result.sharedData.Data[i] = operation(this.sharedData.Data[aIndex], other.sharedData.Data[bIndex]);
            });
        }
        else
        {
            for (int i = 0; i < result.TotalSize; i++)
            {
                int[] indices = UnflattenIndex(i, resultShape);
                int aIndex = ComputeFlatIndex(indices, aStridesBroadcasted) + this.offset;
                int bIndex = ComputeFlatIndex(indices, bStridesBroadcasted) + other.offset;
                result.sharedData.Data[i] = operation(this.sharedData.Data[aIndex], other.sharedData.Data[bIndex]);
            }
        }

        return result;
    }

    /// <summary>
    /// Performs in-place element-wise operation with another tensor.
    /// </summary>
    private void InPlaceElementWiseOperation(Tensor<T> other, ElementWiseOperationDelegate operation)
    {
        EnsureNotDisposed();

        if (other == null)
            throw new ArgumentNullException(nameof(other));

        other.EnsureNotDisposed();

        int[] resultShape = DetermineBroadcastShape(this.shape, other.shape);

        if (!this.shape.SequenceEqual(resultShape))
            throw new InvalidOperationException("In-place operation requires the shapes to be broadcastable to the shape of this tensor.");

        EnsureUniqueData();

        int[] aStridesBroadcasted = this.strides;
        int[] bStridesBroadcasted = ComputeBroadcastStrides(other.shape, this.shape, other.strides);

        if (TotalSize >= ParallelizationThreshold)
        {
            Parallel.For(0, TotalSize, i =>
            {
                int[] indices = UnflattenIndex(i, shape);
                int aIndex = ComputeFlatIndex(indices, aStridesBroadcasted) + this.offset;
                int bIndex = ComputeFlatIndex(indices, bStridesBroadcasted) + other.offset;
                operation(ref sharedData.Data[aIndex], other.sharedData.Data[bIndex]);
            });
        }
        else
        {
            for (int i = 0; i < TotalSize; i++)
            {
                int[] indices = UnflattenIndex(i, shape);
                int aIndex = ComputeFlatIndex(indices, aStridesBroadcasted) + this.offset;
                int bIndex = ComputeFlatIndex(indices, bStridesBroadcasted) + other.offset;
                operation(ref sharedData.Data[aIndex], other.sharedData.Data[bIndex]);
            }
        }
    }

    /// <summary>
    /// Performs a scalar operation on each element of the tensor.
    /// </summary>
    private Tensor<T> ScalarOperation(T scalar, Func<T, T, T> operation)
    {
        EnsureNotDisposed();

        Tensor<T> result = new Tensor<T>((int[])shape.Clone());

        if (TotalSize >= ParallelizationThreshold)
        {
            Parallel.For(0, TotalSize, i =>
            {
                result.sharedData.Data[i] = operation(sharedData.Data[offset + i], scalar);
            });
        }
        else
        {
            for (int i = 0; i < TotalSize; i++)
            {
                result.sharedData.Data[i] = operation(sharedData.Data[offset + i], scalar);
            }
        }

        return result;
    }

    /// <summary>
    /// Performs an in-place scalar operation on each element of the tensor.
    /// </summary>
    private void InPlaceScalarOperation(T scalar, ScalarOperationDelegate operation)
    {
        EnsureNotDisposed();
        EnsureUniqueData();

        if (TotalSize >= ParallelizationThreshold)
        {
            Parallel.For(0, TotalSize, i =>
            {
                operation(ref sharedData.Data[offset + i], scalar);
            });
        }
        else
        {
            for (int i = 0; i < TotalSize; i++)
            {
                operation(ref sharedData.Data[offset + i], scalar);
            }
        }
    }

    #endregion

    #region Strides and Index Calculations

    /// <summary>
    /// Calculates the strides for row-major order.
    /// </summary>
    private static int[] CalculateStrides(int[] shape)
    {
        int[] strides = new int[shape.Length];
        strides[shape.Length - 1] = 1;
        for (int i = shape.Length - 2; i >= 0; i--)
        {
            strides[i] = strides[i + 1] * shape[i + 1];
        }
        return strides;
    }

    /// <summary>
    /// Determines the broadcasted shape from two shapes.
    /// </summary>
    private static int[] DetermineBroadcastShape(int[] aShape, int[] bShape)
    {
        int maxLength = Math.Max(aShape.Length, bShape.Length);
        int[] broadcastShape = new int[maxLength];

        for (int i = 0; i < maxLength; i++)
        {
            int aDim = i < maxLength - aShape.Length ? 1 : aShape[i - (maxLength - aShape.Length)];
            int bDim = i < maxLength - bShape.Length ? 1 : bShape[i - (maxLength - bShape.Length)];

            if (aDim != bDim && aDim != 1 && bDim != 1)
                throw new InvalidOperationException($"Shapes {string.Join(",", aShape)} and {string.Join(",", bShape)} are not broadcastable.");

            broadcastShape[i] = Math.Max(aDim, bDim);
        }

        return broadcastShape;
    }

    /// <summary>
    /// Computes broadcasted strides for a tensor based on the result shape.
    /// </summary>
    private static int[] ComputeBroadcastStrides(int[] originalShape, int[] resultShape, int[] originalStrides)
    {
        int originalLength = originalShape.Length;
        int resultLength = resultShape.Length;
        int[] broadcastStrides = new int[resultLength];

        int sizeDifference = resultLength - originalLength;
        for (int i = resultLength - 1; i >= 0; i--)
        {
            if (i - sizeDifference >= 0 && originalShape[i - sizeDifference] == resultShape[i])
            {
                broadcastStrides[i] = originalStrides[i - sizeDifference];
            }
            else if (i - sizeDifference >= 0 && originalShape[i - sizeDifference] == 1)
            {
                broadcastStrides[i] = 0;
            }
            else if (i - sizeDifference < 0)
            {
                broadcastStrides[i] = 0;
            }
            else
            {
                throw new InvalidOperationException("Shapes are not broadcastable.");
            }
        }

        return broadcastStrides;
    }

    /// <summary>
    /// Converts a flat index to multi-dimensional indices based on the shape.
    /// </summary>
    private static int[] UnflattenIndex(int flatIndex, int[] shape)
    {
        int[] indices = new int[shape.Length];
        for (int i = shape.Length - 1; i >= 0; i--)
        {
            indices[i] = flatIndex % shape[i];
            flatIndex /= shape[i];
        }
        return indices;
    }

    /// <summary>
    /// Computes the flat index from multi-dimensional indices and strides.
    /// </summary>
    private static int ComputeFlatIndex(int[] indices, int[] strides)
    {
        int flatIndex = 0;
        for (int i = 0; i < indices.Length; i++)
        {
            flatIndex += indices[i] * strides[i];
        }
        return flatIndex;
    }

    #endregion

    #region Dispose Pattern

    /// <summary>
    /// Ensures that the tensor has not been disposed.
    /// </summary>
    private void EnsureNotDisposed()
    {
        if (disposed)
            throw new ObjectDisposedException(nameof(Tensor<T>));
    }

    /// <summary>
    /// Dispose pattern implementation.
    /// </summary>
    public void Dispose()
    {
        if (!disposed)
        {
            sharedData.Release();
            disposed = true;
        }
    }

    #endregion

    #region Visualization and Serialization

    /// <summary>
    /// Displays the tensor data for debugging purposes.
    /// </summary>
    public void Display()
    {
        EnsureNotDisposed();

        if (shape.Length == 1)
        {
            Console.Write("[");
            for (int i = 0; i < shape[0]; i++)
            {
                Console.Write(this[i]);
                if (i < shape[0] - 1)
                    Console.Write(", ");
            }
            Console.WriteLine("]");
        }
        else if (shape.Length == 2)
        {
            for (int i = 0; i < shape[0]; i++)
            {
                Console.Write("[");
                for (int j = 0; j < shape[1]; j++)
                {
                    Console.Write(this[i, j]);
                    if (j < shape[1] - 1)
                        Console.Write(", ");
                }
                Console.WriteLine("]");
            }
        }
        else
        {
            Console.WriteLine("Tensor data display is only implemented for 1D and 2D tensors.");
        }
    }

    /// <summary>
    /// Converts the tensor data to a 1D array.
    /// </summary>
    /// <returns>1D array containing tensor data.</returns>
    public T[] ToArray()
    {
        EnsureNotDisposed();

        T[] array = new T[TotalSize];
        Array.Copy(sharedData.Data, offset, array, 0, TotalSize);
        return array;
    }

    /// <summary>
    /// Serializes the tensor to a JSON string.
    /// </summary>
    /// <returns>JSON representation of the tensor.</returns>
    public string ToJson()
    {
        EnsureNotDisposed();
        return JsonSerializer.Serialize(new SerializedTensor<T>
        {
            Shape = this.shape,
            Data = this.ToArray()
        });
    }

    /// <summary>
    /// Deserializes a JSON string to create a tensor.
    /// </summary>
    /// <param name="json">JSON string representing a tensor.</param>
    /// <returns>Deserialized tensor.</returns>
    public static Tensor<T> FromJson(string json)
    {
        if (string.IsNullOrEmpty(json))
            throw new ArgumentException("JSON string cannot be null or empty.", nameof(json));

        var deserialized = JsonSerializer.Deserialize<SerializedTensor<T>>(json);

        if (deserialized == null)
            throw new InvalidOperationException("Failed to deserialize tensor. Deserialized object is null.");

        if (deserialized.Shape == null || deserialized.Data == null)
            throw new InvalidOperationException("Deserialized tensor has null Shape or Data.");

        if (deserialized.Data.Length != deserialized.Shape.Aggregate(1, (a, b) => a * b))
            throw new InvalidOperationException("Data length does not match shape.");

        Tensor<T> tensor = new Tensor<T>(deserialized.Shape);
        Array.Copy(deserialized.Data, 0, tensor.sharedData.Data, tensor.offset, tensor.TotalSize);
        return tensor;
    }

    /// <summary>
    /// Internal class for serialization.
    /// </summary>
    private class SerializedTensor<TData> where TData : unmanaged
    {
        public int[]? Shape { get; set; }
        public TData[]? Data { get; set; }
    }

    #endregion
}
}

based on best practices and academic knowledge in software engineering and numerical computing
please only do the following and give me FULL code with these updates take your time to think and plan before generating the code to make sure you generated the full code for the full class. so i can avoid any copy paste due to human errors.

1-API Design and Consistency:
Issue: Some method names and patterns may not align with common conventions.
Recommendation:
Follow .NET naming conventions; for example, use Add instead of AddInPlace and provide overloads or separate methods for in-place operations, possibly using Try patterns.
Consider implementing interfaces like IEquatable<Tensor>, IEnumerable, and ICloneable for better interoperability.

2-Memory Management and Safety:
Issue: The use of ArrayPool and manual reference counting introduces complexity and potential for memory leaks or premature disposal.
Recommendation:
Ensure that all code paths that allocate resources are matched with proper disposal, possibly using SafeHandle patterns.

3-Operator Overloading:
Issue: Currently, arithmetic operations are method-based, which can be less intuitive.
Recommendation:
Overload operators like +, -, *, and / to allow natural arithmetic expressions, e.g., tensorA + tensorB.
Ensure that operator overloads handle broadcasting and other tensor behaviors correctly.

4-Code Style and Organization:
Issue: Some inconsistencies in code formatting and structure.
Recommendation:
Organize code regions logically and consider splitting large methods into smaller, more manageable ones.

5-Thread Safety and Concurrency:
Issue: While the class employs Parallel.For for parallelization, there is a potential risk of race conditions when multiple threads access or modify shared data, especially in in-place operations.
Recommendation:
Ensure that all accesses to shared mutable state are properly synchronized.
Use thread-safe collections or synchronization mechanisms like locks when modifying shared data.
Consider making the class immutable or provide immutable views to prevent unintended side effects.

6-Cache computed values like TotalSize if tensor immutability is enforced to avoid redundant calculations.

7-Serialization Enhancements:
Issue: The serialization approach may not be efficient for large tensors or custom types.
Recommendation:
Optimize ToJson and FromJson methods for performance, possibly using streaming.
Support alternative serialization formats like BSON or Protocol Buffers.
Ensure that serialization handles endianness and numerical precision appropriately.

8-Provides asynchronous versions of operations
9-Reduction Operations: Methods like Sum, Mean, Max, and Min across specified axes.

Again please make sure to generate the FULL code for all given requirements