#pragma once

#include "../Parameters/DeepPluckParameterState.h"

#include <array>
#include <cstdint>
#include <optional>
#include <random>
#include <span>

namespace deeppluck
{
enum class RandomizationModule : std::uint8_t
{
    oscillator1,
    oscillator2,
    ampEnvelope,
    filter,
    filterEnvelope,
    transient,
    noise,
    unisonStereo,
    modulation,
    effectsRack,
    count
};

inline constexpr auto randomizationModuleCount =
    static_cast<std::size_t> (RandomizationModule::count);

[[nodiscard]] const char* getRandomizationModuleName (RandomizationModule module) noexcept;
[[nodiscard]] const char* getRandomizationModuleId (RandomizationModule module) noexcept;
[[nodiscard]] std::span<const int> getRandomizationParameterIndices (
    RandomizationModule module) noexcept;

class DeepPluckRandomization final
{
public:
    explicit DeepPluckRandomization (
        juce::AudioProcessorValueTreeState& parameterState,
        std::optional<std::uint64_t> deterministicSeed = std::nullopt);

    void setAmount (float newAmount) noexcept;
    [[nodiscard]] float getAmount() const noexcept;

    void setModuleLocked (RandomizationModule module, bool shouldBeLocked) noexcept;
    void toggleModuleLocked (RandomizationModule module) noexcept;
    [[nodiscard]] bool isModuleLocked (RandomizationModule module) const noexcept;

    [[nodiscard]] bool randomizeAll();
    [[nodiscard]] bool randomizeModule (RandomizationModule module);
    [[nodiscard]] bool canUndo() const noexcept;
    [[nodiscard]] bool undo();
    void invalidateUndo() noexcept;

    [[nodiscard]] float drawTargetForTests (int parameterIndex);
    [[nodiscard]] int getLastDistributionBranchForTests() const noexcept;

private:
    [[nodiscard]] bool randomize (std::optional<RandomizationModule> onlyModule);
    [[nodiscard]] float drawTarget (int parameterIndex);
    [[nodiscard]] double drawUnit();
    [[nodiscard]] float drawUniform (float minimum, float maximum);
    [[nodiscard]] float drawLogUniform (float minimum, float maximum);
    [[nodiscard]] int drawWeightedIndex (std::span<const int> weights);
    [[nodiscard]] static std::uint64_t makeProductionSeed();

    juce::AudioProcessorValueTreeState& state;
    std::mt19937_64 generator;
    std::array<bool, randomizationModuleCount> locks {};
    DeepPluckParameterValues undoValues {};
    int lastDistributionBranch = -1;
    float amount = 1.0f;
    bool undoAvailable = false;
};
}
