// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Andrea De Murtas

#pragma once

#include <juce_dsp/juce_dsp.h>
#include "GjallaDSP.h"

namespace gjalla
{

// Power section. Solid state, so: no sag, no rail droop, no bias shift, no
// transformer. A transistor power amp is close to a straight wire until it runs
// out of rails, and then it stops dead. The tanh here is the softest honest
// approximation of that, and Master pushes into it by up to +12 dB.
//
// Unlike the preamp there is no level compensation: Master is meant to be a
// volume control that happens to grit up at the top, and compensating it would
// take that away.
//
// Last stage inside the oversampling block.
class PowerAmp
{
public:
    void prepare(int numChannels) noexcept
    {
        channels = juce::jlimit(1, 2, numChannels);
        reset();
    }

    void reset() noexcept { primed = false; }

    // master is the 0..10 knob.
    void process(juce::dsp::AudioBlock<float>& block, float master, double sampleRate) noexcept
    {
        const auto numSamples = (int) block.getNumSamples();
        const auto nCh = juce::jmin((int) block.getNumChannels(), channels);

        const auto target = driveGainFor(master);

        driveSm.setTimeConstant(sampleRate, 0.02f);
        if (! primed)
        {
            driveSm.snap(target);
            primed = true;
        }

        auto* left = block.getChannelPointer(0);
        auto* right = nCh > 1 ? block.getChannelPointer(1) : nullptr;
        float* const data[2] { left, right };

        for (int i = 0; i < numSamples; ++i)
        {
            const auto g = driveSm.next(target);
            for (int ch = 0; ch < nCh; ++ch)
                data[ch][i] = std::tanh(data[ch][i] * g);
        }
    }

    static float driveGainFor(float master) noexcept
    {
        return juce::Decibels::decibelsToGain(juce::jlimit(0.0f, 10.0f, master) * 1.2f);
    }

private:
    OnePoleSmoother driveSm;
    int channels = 2;
    bool primed = false;
};

} // namespace gjalla
