using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace QuanTAlib;

/// <summary>
/// AccBands: Acceleration Bands
/// </summary>
/// <remarks>
/// Acceleration Bands are a volatility-based channel indicator developed by Price Headley.
/// They create an adaptive price envelope around a moving average, with band width determined
/// by the spread between the high and low moving averages multiplied by a factor.
///
/// Calculation:
/// Middle Band = SMA(Close, Period)
/// BandWidth = [SMA(High, Period) - SMA(Low, Period)] × Factor
/// Upper Band = SMA(High, Period) + BandWidth
/// Lower Band = SMA(Low, Period) - BandWidth
///
/// Key characteristics:
/// - Bands expand during volatile periods and contract during consolidation
/// - Uses SMA of High, Low, and Close for calculations
/// - Factor parameter controls band sensitivity
///
/// Sources:
/// Headley, P. (2002). Big Trends in Trading. John Wiley & Sons.
/// </remarks>
[SkipLocalsInit]
public sealed class AccBands : ITValuePublisher, IDisposable
{
    private readonly int _period;
    private readonly double _factor;
    private readonly RingBuffer _highBuffer;
    private readonly RingBuffer _lowBuffer;
    private readonly RingBuffer _closeBuffer;
    private readonly TBarPublishedHandler _barHandler;
    private TBarSeries? _source;
    private bool _disposed;

    private const int ResyncInterval = 1000;

    [StructLayout(LayoutKind.Auto)]
    private record struct State(
        double SumHigh,
        double SumLow,
        double SumClose,
        double LastValidHigh,
        double LastValidLow,
        double LastValidClose,
        int TickCount
    );
    private State _state;
    private State _p_state;

    /// <summary>
    /// Display name for the indicator.
    /// </summary>
    public string Name { get; }

    /// <summary>
    /// Number of periods before the indicator is considered "hot" (valid).
    /// </summary>
    public int WarmupPeriod { get; }

    /// <summary>
    /// Current middle band value.
    /// </summary>
    public TValue Last { get; private set; }

    /// <summary>
    /// Current upper band value.
    /// </summary>
    public TValue Upper { get; private set; }

    /// <summary>
    /// Current lower band value.
    /// </summary>
    public TValue Lower { get; private set; }

    /// <summary>
    /// True if the indicator has enough data to produce valid results.
    /// </summary>
    public bool IsHot => _closeBuffer.IsFull;

    /// <summary>
    /// Event triggered when a new TValue is available.
    /// </summary>
    public event TValuePublishedHandler? Pub;

    /// <summary>
    /// Creates AccBands with specified period and factor.
    /// </summary>
    /// <param name="period">Lookback period for SMA calculations (must be > 0)</param>
    /// <param name="factor">Multiplier for band width (must be > 0, default: 2.0)</param>
    public AccBands(int period, double factor = 2.0)
    {
        if (period <= 0)
        {
            throw new ArgumentException("Period must be greater than 0", nameof(period));
        }

        if (factor <= 0)
        {
            throw new ArgumentException("Factor must be greater than 0", nameof(factor));
        }

        _period = period;
        _factor = factor;
        _highBuffer = new RingBuffer(period);
        _lowBuffer = new RingBuffer(period);
        _closeBuffer = new RingBuffer(period);
        Name = $"AccBands({period},{factor:F2})";
        WarmupPeriod = period;
        _barHandler = HandleBar;
    }

    /// <summary>
    /// Creates AccBands with TBarSeries source.
    /// </summary>
    public AccBands(TBarSeries source, int period, double factor = 2.0) : this(period, factor)
    {
        _source = source;
        Prime(source);
        source.Pub += _barHandler;
    }

    /// <summary>
    /// Releases resources and unsubscribes from the source event.
    /// </summary>
    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _disposed = true;

        if (_source != null)
        {
            _source.Pub -= _barHandler;
            _source = null;
        }
    }

    private void HandleBar(object? sender, in TBarEventArgs e) => Update(e.Value, e.IsNew);

    /// <summary>
    /// Helper to invoke the Pub event.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void PubEvent(TValue value, bool isNew = true)
    {
        Pub?.Invoke(this, new TValueEventArgs { Value = value, IsNew = isNew });
    }

    /// <summary>
    /// Gets a valid input value, using last-value substitution for non-finite inputs.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private double GetValidHigh(double input)
    {
        if (double.IsFinite(input))
        {
            _state.LastValidHigh = input;
            return input;
        }
        return _state.LastValidHigh;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private double GetValidLow(double input)
    {
        if (double.IsFinite(input))
        {
            _state.LastValidLow = input;
            return input;
        }
        return _state.LastValidLow;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private double GetValidClose(double input)
    {
        if (double.IsFinite(input))
        {
            _state.LastValidClose = input;
            return input;
        }
        return _state.LastValidClose;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void UpdateState(double high, double low, double close)
    {
        double removedHigh = _highBuffer.Count == _highBuffer.Capacity ? _highBuffer.Oldest : 0.0;
        double removedLow = _lowBuffer.Count == _lowBuffer.Capacity ? _lowBuffer.Oldest : 0.0;
        double removedClose = _closeBuffer.Count == _closeBuffer.Capacity ? _closeBuffer.Oldest : 0.0;

        _state.SumHigh = _state.SumHigh - removedHigh + high;
        _state.SumLow = _state.SumLow - removedLow + low;
        _state.SumClose = _state.SumClose - removedClose + close;

        _highBuffer.Add(high);
        _lowBuffer.Add(low);
        _closeBuffer.Add(close);

        _state.TickCount++;
        if (_closeBuffer.IsFull && _state.TickCount >= ResyncInterval)
        {
            _state.TickCount = 0;
            _state.SumHigh = _highBuffer.RecalculateSum();
            _state.SumLow = _lowBuffer.RecalculateSum();
            _state.SumClose = _closeBuffer.RecalculateSum();
        }
    }

    /// <summary>
    /// Updates the indicator with a TBar input.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public TValue Update(TBar input, bool isNew = true)
    {
        if (isNew)
        {
            _p_state = _state;

            double high = GetValidHigh(input.High);
            double low = GetValidLow(input.Low);
            double close = GetValidClose(input.Close);
            UpdateState(high, low, close);
        }
        else
        {
            _state = _p_state;

            double high = GetValidHigh(input.High);
            double low = GetValidLow(input.Low);
            double close = GetValidClose(input.Close);

            _highBuffer.UpdateNewest(high);
            _lowBuffer.UpdateNewest(low);
            _closeBuffer.UpdateNewest(close);

            _state = _state with
            {
                SumHigh = _highBuffer.Sum,
                SumLow = _lowBuffer.Sum,
                SumClose = _closeBuffer.Sum,
            };
        }

        int count = _closeBuffer.Count;
        if (count == 0)
        {
            Last = new TValue(input.Time, double.NaN);
            Upper = new TValue(input.Time, double.NaN);
            Lower = new TValue(input.Time, double.NaN);
        }
        else
        {
            double smaHigh = _state.SumHigh / count;
            double smaLow = _state.SumLow / count;
            double smaClose = _state.SumClose / count;
            double bandWidth = (smaHigh - smaLow) * _factor;

            Last = new TValue(input.Time, smaClose);
            Upper = new TValue(input.Time, smaHigh + bandWidth);
            Lower = new TValue(input.Time, smaLow - bandWidth);
        }

        PubEvent(Last, isNew);
        return Last;
    }

    /// <summary>
    /// Updates the indicator with a TBarSeries.
    /// </summary>
    public (TSeries Middle, TSeries Upper, TSeries Lower) Update(TBarSeries source)
    {
        if (source.Count == 0)
        {
            return (new TSeries([], []), new TSeries([], []), new TSeries([], []));
        }

        int len = source.Count;
        var tMiddle = new List<long>(len);
        var vMiddle = new List<double>(len);
        var tUpper = new List<long>(len);
        var vUpper = new List<double>(len);
        var tLower = new List<long>(len);
        var vLower = new List<double>(len);

        CollectionsMarshal.SetCount(tMiddle, len);
        CollectionsMarshal.SetCount(vMiddle, len);
        CollectionsMarshal.SetCount(tUpper, len);
        CollectionsMarshal.SetCount(vUpper, len);
        CollectionsMarshal.SetCount(tLower, len);
        CollectionsMarshal.SetCount(vLower, len);

        var tSpan = CollectionsMarshal.AsSpan(tMiddle);
        var vMiddleSpan = CollectionsMarshal.AsSpan(vMiddle);
        var vUpperSpan = CollectionsMarshal.AsSpan(vUpper);
        var vLowerSpan = CollectionsMarshal.AsSpan(vLower);

        // Use batch calculation
        Batch(source.HighValues, source.LowValues, source.CloseValues,
              vMiddleSpan, vUpperSpan, vLowerSpan, _period, _factor);

        source.Times.CopyTo(tSpan);

        // Copy timestamps to upper and lower (same time series)
        tSpan.CopyTo(CollectionsMarshal.AsSpan(tUpper));
        tSpan.CopyTo(CollectionsMarshal.AsSpan(tLower));

        // Prime the state for continued streaming
        Prime(source);

        return (new TSeries(tMiddle, vMiddle), new TSeries(tUpper, vUpper), new TSeries(tLower, vLower));
    }

    /// <summary>
    /// Initializes the indicator state using the provided TBarSeries history.
    /// </summary>
    // skipcq: CS-R1140
    public void Prime(TBarSeries source)
    {
        if (source.Count == 0)
        {
            return;
        }

        // Reset state
        _highBuffer.Clear();
        _lowBuffer.Clear();
        _closeBuffer.Clear();
        _state = default;
        _p_state = default;

        int warmupLength = Math.Min(source.Count, WarmupPeriod);
        int startIndex = source.Count - warmupLength;

        // Seed LastValidValue
        _state.LastValidHigh = double.NaN;
        _state.LastValidLow = double.NaN;
        _state.LastValidClose = double.NaN;

        for (int i = startIndex - 1; i >= 0; i--)
        {
            var bar = source[i];
            if (double.IsFinite(bar.High) && double.IsNaN(_state.LastValidHigh))
            {
                _state.LastValidHigh = bar.High;
            }

            if (double.IsFinite(bar.Low) && double.IsNaN(_state.LastValidLow))
            {
                _state.LastValidLow = bar.Low;
            }

            if (double.IsFinite(bar.Close) && double.IsNaN(_state.LastValidClose))
            {
                _state.LastValidClose = bar.Close;
            }

            if (!double.IsNaN(_state.LastValidHigh) && !double.IsNaN(_state.LastValidLow) && !double.IsNaN(_state.LastValidClose))
            {
                break;
            }
        }

        // Find valid values in warmup window if not found
        if (double.IsNaN(_state.LastValidHigh) || double.IsNaN(_state.LastValidLow) || double.IsNaN(_state.LastValidClose))
        {
            for (int i = startIndex; i < source.Count; i++)
            {
                var bar = source[i];
                if (double.IsFinite(bar.High) && double.IsNaN(_state.LastValidHigh))
                {
                    _state.LastValidHigh = bar.High;
                }

                if (double.IsFinite(bar.Low) && double.IsNaN(_state.LastValidLow))
                {
                    _state.LastValidLow = bar.Low;
                }

                if (double.IsFinite(bar.Close) && double.IsNaN(_state.LastValidClose))
                {
                    _state.LastValidClose = bar.Close;
                }

                if (!double.IsNaN(_state.LastValidHigh) && !double.IsNaN(_state.LastValidLow) && !double.IsNaN(_state.LastValidClose))
                {
                    break;
                }
            }
        }

        // Feed the buffers
        for (int i = startIndex; i < source.Count; i++)
        {
            var bar = source[i];
            double high = GetValidHigh(bar.High);
            double low = GetValidLow(bar.Low);
            double close = GetValidClose(bar.Close);
            UpdateState(high, low, close);
        }

        // Finalize state
        int count = _closeBuffer.Count;
        if (count > 0)
        {
            var lastBar = source.Last;
            double smaHigh = _state.SumHigh / count;
            double smaLow = _state.SumLow / count;
            double smaClose = _state.SumClose / count;
            double bandWidth = (smaHigh - smaLow) * _factor;

            Last = new TValue(lastBar.Time, smaClose);
            Upper = new TValue(lastBar.Time, smaHigh + bandWidth);
            Lower = new TValue(lastBar.Time, smaLow - bandWidth);
        }

        _p_state = _state;
    }

    /// <summary>
    /// Resets the indicator state.
    /// </summary>
    public void Reset()
    {
        _highBuffer.Clear();
        _lowBuffer.Clear();
        _closeBuffer.Clear();
        _state = new State(
            SumHigh: 0,
            SumLow: 0,
            SumClose: 0,
            LastValidHigh: double.NaN,
            LastValidLow: double.NaN,
            LastValidClose: double.NaN,
            TickCount: 0
        );
        _p_state = _state;
        Last = default;
        Upper = default;
        Lower = default;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    // Static Batch Methods
    /////////////////////////////////////////////////////////////////////////////////////////////////

    /// <summary>
    /// Output buffers for batch AccBands calculation.
    /// </summary>
    /// <remarks>
    /// Public Span fields are intentional: ref structs cannot use auto-properties with Span&lt;T&gt;
    /// and direct field access provides optimal performance for this high-throughput API.
    /// </remarks>
    [StructLayout(LayoutKind.Auto)]
#pragma warning disable S1104 // Fields should not have public accessibility
    public ref struct BatchOutputs
    {
        /// <summary>Output middle band (SMA of close)</summary>
        public Span<double> Middle;
        /// <summary>Output upper band</summary>
        public Span<double> Upper;
        /// <summary>Output lower band</summary>
        public Span<double> Lower;
#pragma warning restore S1104

        /// <summary>
        /// Creates a new BatchOutputs instance.
        /// </summary>
        public BatchOutputs(Span<double> middle, Span<double> upper, Span<double> lower)
        {
            Middle = middle;
            Upper = upper;
            Lower = lower;
        }
    }

    /// <summary>
    /// Input buffers for batch AccBands calculation.
    /// </summary>
    [StructLayout(LayoutKind.Auto)]
#pragma warning disable S1104 // Fields should not have public accessibility
    public ref struct BatchInputs
    {
        /// <summary>High price values</summary>
        public ReadOnlySpan<double> High;
        /// <summary>Low price values</summary>
        public ReadOnlySpan<double> Low;
        /// <summary>Close price values</summary>
        public ReadOnlySpan<double> Close;
#pragma warning restore S1104

        /// <summary>
        /// Creates a new BatchInputs instance.
        /// </summary>
        public BatchInputs(ReadOnlySpan<double> high, ReadOnlySpan<double> low, ReadOnlySpan<double> close)
        {
            High = high;
            Low = low;
            Close = close;
        }
    }

    /// <summary>
    /// Internal state for scalar calculation.
    /// </summary>
    [StructLayout(LayoutKind.Auto)]
    private ref struct ScalarState
    {
        public double SumHigh;
        public double SumLow;
        public double SumClose;
        public double LastValidHigh;
        public double LastValidLow;
        public double LastValidClose;
        public int BufferIndex;
        public int TickCount;
    }

    /// <summary>
    /// Working buffers for batch calculation.
    /// </summary>
    [StructLayout(LayoutKind.Auto)]
    private readonly ref struct WorkBuffers(Span<double> high, Span<double> low, Span<double> close)
    {
        public readonly Span<double> High = high;
        public readonly Span<double> Low = low;
        public readonly Span<double> Close = close;
    }

    /// <summary>
    /// Calculates AccBands for the entire TBarSeries using a new instance.
    /// </summary>
    public static (TSeries Middle, TSeries Upper, TSeries Lower) Batch(TBarSeries source, int period, double factor = 2.0)
    {
        var accBands = new AccBands(period, factor);
        return accBands.Update(source);
    }

    /// <summary>
    /// Calculates AccBands in-place using spans for maximum performance.
    /// Zero-allocation method.
    /// </summary>
    /// <param name="inputs">Input buffers for high, low, and close prices</param>
    /// <param name="outputs">Output buffers for middle, upper, and lower bands</param>
    /// <param name="period">Lookback period</param>
    /// <param name="factor">Band width factor</param>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Batch(
        BatchInputs inputs,
        BatchOutputs outputs,
        int period,
        double factor = 2.0)
    {
        Batch(inputs.High, inputs.Low, inputs.Close, outputs.Middle, outputs.Upper, outputs.Lower, period, factor);
    }

    /// <summary>
    /// Calculates AccBands in-place using spans for maximum performance.
    /// Zero-allocation method.
    /// </summary>
    /// <param name="high">High price values</param>
    /// <param name="low">Low price values</param>
    /// <param name="close">Close price values</param>
    /// <param name="outputs">Output buffers for middle, upper, and lower bands</param>
    /// <param name="period">Lookback period</param>
    /// <param name="factor">Band width factor</param>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Batch(
        ReadOnlySpan<double> high,
        ReadOnlySpan<double> low,
        ReadOnlySpan<double> close,
        BatchOutputs outputs,
        int period,
        double factor = 2.0)
    {
        Batch(high, low, close, outputs.Middle, outputs.Upper, outputs.Lower, period, factor);
    }

    /// <summary>
    /// Calculates AccBands in-place using spans for maximum performance.
    /// Zero-allocation method.
    /// </summary>
    /// <param name="high">High price values</param>
    /// <param name="low">Low price values</param>
    /// <param name="close">Close price values</param>
    /// <param name="middle">Output middle band (SMA of close)</param>
    /// <param name="upper">Output upper band</param>
    /// <param name="lower">Output lower band</param>
    /// <param name="period">Lookback period</param>
    /// <param name="factor">Band width factor</param>
    // Suppressing S107: This is a high-performance batch API where callers benefit from
    // direct span parameters. A BatchOutputs overload exists for callers preferring fewer parameters.
#pragma warning disable S107
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Batch(
        ReadOnlySpan<double> high,
        ReadOnlySpan<double> low,
        ReadOnlySpan<double> close,
        Span<double> middle,
        Span<double> upper,
        Span<double> lower,
        int period,
        double factor = 2.0)
#pragma warning restore S107
    {
        int len = close.Length;
        if (high.Length != len || low.Length != len)
        {
            throw new ArgumentException("High, Low, and Close must have the same length", nameof(high));
        }

        if (middle.Length < len || upper.Length < len || lower.Length < len)
        {
            throw new ArgumentException("Output buffers must be at least as long as input", nameof(middle));
        }

        if (period <= 0)
        {
            throw new ArgumentException("Period must be greater than 0", nameof(period));
        }

        if (factor <= 0)
        {
            throw new ArgumentException("Factor must be greater than 0", nameof(factor));
        }

        if (len == 0)
        {
            return;
        }

        // Scalar implementation with NaN handling
        var inputs = new BatchInputs(high, low, close);
        var outputs = new BatchOutputs(middle, upper, lower);
        CalculateScalarCore(inputs, outputs, period, factor);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static void CalculateScalarCore(
        scoped BatchInputs inputs,
        scoped BatchOutputs outputs,
        int period,
        double factor)
    {
        int len = inputs.Close.Length;

        // Always use ArrayPool to avoid span scope safety issues with stackalloc + ref structs
        double[] rentedHigh = ArrayPool<double>.Shared.Rent(period);
        double[] rentedLow = ArrayPool<double>.Shared.Rent(period);
        double[] rentedClose = ArrayPool<double>.Shared.Rent(period);

        try
        {
            var buffers = new WorkBuffers(
                rentedHigh.AsSpan(0, period),
                rentedLow.AsSpan(0, period),
                rentedClose.AsSpan(0, period));

            var state = new ScalarState
            {
                LastValidHigh = double.NaN,
                LastValidLow = double.NaN,
                LastValidClose = double.NaN,
            };

            SeedFirstValidValues(inputs, ref state);

            int warmupEnd = Math.Min(period, len);
            ProcessWarmupPhase(inputs, outputs, warmupEnd, factor, ref buffers, ref state);
            ProcessMainLoop(inputs, outputs, warmupEnd, period, factor, ref buffers, ref state);
        }
        finally
        {
            ArrayPool<double>.Shared.Return(rentedHigh);
            ArrayPool<double>.Shared.Return(rentedLow);
            ArrayPool<double>.Shared.Return(rentedClose);
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static void SeedFirstValidValues(scoped BatchInputs inputs, ref ScalarState state)
    {
        int len = inputs.Close.Length;
        for (int k = 0; k < len; k++)
        {
            if (double.IsFinite(inputs.High[k]) && double.IsNaN(state.LastValidHigh))
            {
                state.LastValidHigh = inputs.High[k];
            }

            if (double.IsFinite(inputs.Low[k]) && double.IsNaN(state.LastValidLow))
            {
                state.LastValidLow = inputs.Low[k];
            }

            if (double.IsFinite(inputs.Close[k]) && double.IsNaN(state.LastValidClose))
            {
                state.LastValidClose = inputs.Close[k];
            }

            if (!double.IsNaN(state.LastValidHigh) && !double.IsNaN(state.LastValidLow) && !double.IsNaN(state.LastValidClose))
            {
                break;
            }
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static (double h, double l, double c) GetValidHLC(scoped BatchInputs inputs, int i, ref ScalarState state)
    {
        double h = inputs.High[i];
        double l = inputs.Low[i];
        double c = inputs.Close[i];

        if (double.IsFinite(h))
        {
            state.LastValidHigh = h;
        }
        else
        {
            h = state.LastValidHigh;
        }

        if (double.IsFinite(l))
        {
            state.LastValidLow = l;
        }
        else
        {
            l = state.LastValidLow;
        }

        if (double.IsFinite(c))
        {
            state.LastValidClose = c;
        }
        else
        {
            c = state.LastValidClose;
        }

        return (h, l, c);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static void WriteBandOutputs(scoped BatchOutputs outputs, int i, double smaHigh, double smaLow, double smaClose, double factor)
    {
        double bandWidth = (smaHigh - smaLow) * factor;
        outputs.Middle[i] = smaClose;
        outputs.Upper[i] = smaHigh + bandWidth;
        outputs.Lower[i] = smaLow - bandWidth;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static void ProcessWarmupPhase(
        scoped BatchInputs inputs,
        scoped BatchOutputs outputs,
        int warmupEnd,
        double factor,
        ref WorkBuffers buffers,
        ref ScalarState state)
    {
        for (int i = 0; i < warmupEnd; i++)
        {
            var (h, l, c) = GetValidHLC(inputs, i, ref state);

            state.SumHigh += h;
            state.SumLow += l;
            state.SumClose += c;

            buffers.High[i] = h;
            buffers.Low[i] = l;
            buffers.Close[i] = c;

            int count = i + 1;
            WriteBandOutputs(outputs, i, state.SumHigh / count, state.SumLow / count, state.SumClose / count, factor);
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static void ProcessMainLoop(
        scoped BatchInputs inputs,
        scoped BatchOutputs outputs,
        int startIndex,
        int period,
        double factor,
        ref WorkBuffers buffers,
        ref ScalarState state)
    {
        int len = inputs.Close.Length;
        for (int i = startIndex; i < len; i++)
        {
            var (h, l, c) = GetValidHLC(inputs, i, ref state);

            state.SumHigh = state.SumHigh - buffers.High[state.BufferIndex] + h;
            state.SumLow = state.SumLow - buffers.Low[state.BufferIndex] + l;
            state.SumClose = state.SumClose - buffers.Close[state.BufferIndex] + c;

            buffers.High[state.BufferIndex] = h;
            buffers.Low[state.BufferIndex] = l;
            buffers.Close[state.BufferIndex] = c;

            state.BufferIndex++;
            if (state.BufferIndex >= period)
            {
                state.BufferIndex = 0;
            }

            WriteBandOutputs(outputs, i, state.SumHigh / period, state.SumLow / period, state.SumClose / period, factor);

            state.TickCount++;
            if (state.TickCount >= ResyncInterval)
            {
                ResyncSums(period, ref buffers, ref state);
            }
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static void ResyncSums(int period, ref WorkBuffers buffers, ref ScalarState state)
    {
        state.TickCount = 0;
        ReadOnlySpan<double> highSpan = buffers.High[..period];
        ReadOnlySpan<double> lowSpan = buffers.Low[..period];
        ReadOnlySpan<double> closeSpan = buffers.Close[..period];
        state.SumHigh = highSpan.SumSIMD();
        state.SumLow = lowSpan.SumSIMD();
        state.SumClose = closeSpan.SumSIMD();
    }

    /// <summary>
    /// Runs a high-performance batch calculation and returns a "Hot" AccBands instance.
    /// </summary>
    public static ((TSeries Middle, TSeries Upper, TSeries Lower) Results, AccBands Indicator) Calculate(TBarSeries source, int period, double factor = 2.0)
    {
        var accBands = new AccBands(period, factor);
        var results = accBands.Update(source);
        return (results, accBands);
    }
}
