// Copyright (c) 2026 Ourpad Network
// See LICENSE file in the project root for full license text.

#include "mixer/MixerChannelStrip.hpp"
#include <algorithm>
#include <cmath>
#include <cstring>

using namespace mixer;

std::atomic<int64_t> MixerChannelStrip::nextInstanceId_{1};

MixerChannelStrip::MixerChannelStrip(int channelIndex)
    : channelIndex_(channelIndex)
    , instanceId_(nextInstanceId_++)
{
}

MixerChannelStrip::~MixerChannelStrip()
{
    Unprepare();
}

void MixerChannelStrip::setVolume(float db)
{
    volume_ = std::clamp(db, -96.0f, 12.0f);
}

void MixerChannelStrip::setPan(float pan)
{
    pan_ = std::clamp(pan, -1.0f, 1.0f);
}

void MixerChannelStrip::setMute(bool mute)
{
    mute_ = mute;
}

void MixerChannelStrip::setSolo(bool solo)
{
    solo_ = solo;
}

void MixerChannelStrip::setAuxSend(int index, const AuxSendConfig& config)
{
    if (index >= 0 && index < (int)auxSends_.size()) {
        auxSends_[index] = config;
    }
}

const AuxSendConfig& MixerChannelStrip::auxSend(int index) const
{
    static const AuxSendConfig kDefault;
    if (index >= 0 && index < (int)auxSends_.size()) {
        return auxSends_[index];
    }
    return kDefault;
}

void MixerChannelStrip::resizeAuxSends(size_t count)
{
    auxSends_.resize(count);
}

void MixerChannelStrip::setSampleRate(uint32_t sampleRate)
{
    sampleRate_ = sampleRate;
    hpfStates_.resize(2); // stereo HPF states
}

void MixerChannelStrip::setMaxBufferSize(size_t frames)
{
    maxBufferSize_ = frames;
}

void MixerChannelStrip::allocateBuffers()
{
    preFxBuffers_.clear();
    postFxBuffers_.clear();
    for (int i = 0; i < 2; ++i) {
        preFxBuffers_.emplace_back(maxBufferSize_, 0.0f);
        postFxBuffers_.emplace_back(maxBufferSize_, 0.0f);
    }
}

float MixerChannelStrip::effectiveAuxLevel(int auxIndex, bool anySoloActive) const
{
    if (auxIndex < 0 || auxIndex >= (int)auxSends_.size()) return -96.0f;

    const auto& send = auxSends_[auxIndex];
    if (!send.isActive()) return -96.0f;

    // Solo overrides: if any solo is active, only soloed channels are audible
    if (anySoloActive && !solo_) return -96.0f;
    if (mute_) return -96.0f;

    return send.level;
}

void MixerChannelStrip::applyPan(float& leftGain, float& rightGain) const
{
    float pan = pan_;
    // Constant-power pan law: -3dB at center
    float angle = (pan * 0.5f + 0.5f) * (M_PI * 0.5f); // map -1..1 to 0..PI/2
    leftGain = std::cos(angle);
    rightGain = std::sin(angle);
}

void MixerChannelStrip::applyHpf(float* buffer, uint32_t frames, HpfState& state)
{
    if (!hpEnabled_) return;

    // Simple 1st-order IIR HPF
    float fc = hpFrequency_ / sampleRate_;
    float alpha = fc / (fc + 0.5f);

    for (uint32_t i = 0; i < frames; ++i) {
        float x = buffer[i];
        float y = alpha * (state.y1 + x - state.x1);
        state.x1 = x;
        state.y1 = y;
        buffer[i] = y;
    }
}

void MixerChannelStrip::process(
    const float* const* inputBuffers,
    size_t inputChannels,
    float* const* outputBuffers,
    size_t outputChannels,
    uint32_t frames)
{
    // Clamp frames to allocated buffer size
    frames = std::min(frames, (uint32_t)maxBufferSize_);

    // Step 1: Copy input to pre-FX buffers and apply HPF
    for (size_t ch = 0; ch < std::min(inputChannels, (size_t)2); ++ch) {
        if (ch < preFxBuffers_.size() && inputBuffers[ch]) {
            std::copy(inputBuffers[ch], inputBuffers[ch] + frames,
                      preFxBuffers_[ch].begin());
            applyHpf(preFxBuffers_[ch].data(), frames,
                     ch < hpfStates_.size() ? hpfStates_[ch] : hpfStates_[0]);
        }
    }

    // Step 2: For standalone v1, there is no FX chain.
    // Simply copy pre-FX to post-FX buffers (passthrough).
    // Handle mono-to-stereo expansion: mono input feeds both channels.
    if (inputChannels == 1 && postFxBuffers_.size() >= 2 && preFxBuffers_.size() >= 1) {
        // Mono → stereo: copy same signal to both post-FX channels
        std::copy(preFxBuffers_[0].begin(),
                  preFxBuffers_[0].begin() + frames,
                  postFxBuffers_[0].begin());
        std::copy(preFxBuffers_[0].begin(),
                  preFxBuffers_[0].begin() + frames,
                  postFxBuffers_[1].begin());
    } else {
        for (size_t ch = 0; ch < std::min(inputChannels, (size_t)2); ++ch) {
            if (ch < postFxBuffers_.size() && ch < preFxBuffers_.size()) {
                std::copy(preFxBuffers_[ch].begin(),
                          preFxBuffers_[ch].begin() + frames,
                          postFxBuffers_[ch].begin());
            }
        }
    }

    // Step 3: Apply volume, pan, and mute/solo to create output
    bool isMuted = mute_.load();
    bool isSoloed = solo_.load();

    // Calculate gain from volume dB
    float volumeGain = isMuted ? 0.0f : std::pow(10.0f, volume_.load() / 20.0f);

    // Calculate pan gains
    float leftGain = 1.0f, rightGain = 1.0f;
    applyPan(leftGain, rightGain);

    // Apply to output buffers
    for (size_t outCh = 0; outCh < std::min(outputChannels, (size_t)2); ++outCh) {
        if (!outputBuffers[outCh]) continue;

        float* dst = outputBuffers[outCh];
        const float* src = (outCh < postFxBuffers_.size())
                           ? postFxBuffers_[outCh].data()
                           : (postFxBuffers_.empty() ? nullptr : postFxBuffers_[0].data());

        if (!src) {
            std::fill(dst, dst + frames, 0.0f);
            continue;
        }

        float panGain = (outCh == 0) ? leftGain : rightGain;
        float finalGain = volumeGain * panGain;

        if (finalGain < 0.001f) {
            std::fill(dst, dst + frames, 0.0f);
        } else if (std::abs(finalGain - 1.0f) < 0.001f) {
            std::copy(src, src + frames, dst);
        } else {
            for (uint32_t i = 0; i < frames; ++i) {
                dst[i] = src[i] * finalGain;
            }
        }
    }

    // Step 4: Update VU meters (peak, with 300ms decay)
    for (size_t ch = 0; ch < std::min(outputChannels, (size_t)2); ++ch) {
        if (ch >= postFxBuffers_.size()) break;

        float peak = 0.0f;
        const float* buf = postFxBuffers_[ch].data();
        for (uint32_t i = 0; i < frames; ++i) {
            float absVal = std::abs(buf[i]);
            if (absVal > peak) peak = absVal;
        }

        float peakDb = (peak > 0.00001f) ? 20.0f * std::log10(peak) : -96.0f;

        // Decay: 300ms time constant
        float& vu = (ch == 0) ? vuLeft_ : vuRight_;
        if (peakDb > vu) {
            vu = peakDb; // Instant attack
        } else {
            // Decay at ~300ms
            static const float releaseRate = 0.95f;
            vu = vu * releaseRate + peakDb * (1.0f - releaseRate);
        }
    }
}

void MixerChannelStrip::Activate()
{
    // No FX chain to activate in standalone v1
}

void MixerChannelStrip::Deactivate()
{
    // No FX chain to deactivate in standalone v1
}

void MixerChannelStrip::Unprepare()
{
    preFxBuffers_.clear();
    postFxBuffers_.clear();
}
