#pragma once

#include "DeepPluckPresetCodec.h"

#include <juce_core/juce_core.h>

#include <atomic>
#include <functional>
#include <vector>

namespace deeppluck
{
enum class PresetSource : std::uint8_t { user, pack, imported };

struct UserPresetEntry
{
    juce::String stableId;
    juce::String name;
    juce::String author;
    juce::String group;
    juce::String category;
    juce::String relativePath;
    juce::File file;
    PresetSource source = PresetSource::imported;
    bool editable = false;
    DeepPluckParameterValues values {};
};

struct UserPresetCatalog
{
    std::vector<UserPresetEntry> entries;
    int skippedErrors = 0;
    std::uint64_t generation = 0;
};

enum class PresetSaveResult
{
    saved,
    needsOverwriteConfirmation,
    invalidName,
    failed
};

class DeepPluckPresetManager final : private juce::Thread
{
public:
    DeepPluckPresetManager (const juce::AudioProcessorValueTreeState& state,
                            juce::String writingPluginVersion,
                            juce::File injectedRoot = {});
    ~DeepPluckPresetManager() override;

    [[nodiscard]] const juce::File& getRootDirectory() const noexcept;
    [[nodiscard]] juce::File getUserDirectory() const;
    [[nodiscard]] UserPresetCatalog getCatalog() const;
    [[nodiscard]] std::uint64_t getCatalogGeneration() const noexcept;

    void requestScan();
    [[nodiscard]] bool waitForScanForTests (int timeoutMilliseconds);

    [[nodiscard]] bool loadPreset (const UserPresetEntry& entry,
                                   UserPresetData& data,
                                   juce::String& error) const;
    [[nodiscard]] bool loadPresetFile (const juce::File& file,
                                       UserPresetData& data,
                                       juce::String& error) const;
    [[nodiscard]] PresetSaveResult savePreset (
        const juce::AudioProcessorValueTreeState& state,
        juce::String name,
        juce::String author,
        juce::String description,
        bool allowOverwrite,
        UserPresetEntry& savedEntry,
        juce::String& error,
        juce::File explicitUserTarget = {});
    [[nodiscard]] bool deleteUserPreset (const juce::File& file,
                                         juce::String& error);
    [[nodiscard]] bool revealRootToUser (juce::String& error);

    [[nodiscard]] static juce::String sanitizePresetBaseName (juce::String name);
    [[nodiscard]] static bool hasPresetExtension (const juce::File& file);

    // Set before starting a scan. Tests use this hook to prove that all observed
    // filesystem work remains off the marked audio thread.
    void setFileOperationObserverForTests (std::function<void()> observer);

private:
    struct ScanContext;

    void run() override;
    [[nodiscard]] UserPresetCatalog scanCatalog() const;
    void scanDirectory (const juce::File& directory,
                        const juce::File& sourceRoot,
                        PresetSource source,
                        const juce::String& group,
                        int depth,
                        ScanContext& context) const;
    [[nodiscard]] juce::String readPackName (const juce::File& packDirectory) const;
    [[nodiscard]] bool isContainedByRoot (const juce::File& file) const;
    [[nodiscard]] bool isContainedByUserDirectory (const juce::File& file) const;
    [[nodiscard]] static juce::String normalizedPath (const juce::File& file);
    [[nodiscard]] juce::File targetFileForName (const juce::String& safeBaseName) const;
    [[nodiscard]] bool ensureRootDirectories (juce::String& error) const;
    void recordFileOperation() const;

    juce::File rootDirectory;
    const juce::AudioProcessorValueTreeState& parameterState;
    juce::String pluginVersion;
    DeepPluckParameterValues defaultValues {};
    mutable juce::CriticalSection catalogLock;
    UserPresetCatalog catalog;
    std::atomic<std::uint64_t> catalogGeneration { 0 };
    std::atomic<bool> scanRequested { false };
    std::atomic<bool> scanInProgress { false };
    juce::WaitableEvent scanWake;
    juce::WaitableEvent scanComplete;
    std::function<void()> fileOperationObserver;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DeepPluckPresetManager)
};
}
