#include "CandidateFiles.h"
#include "ParameterContext.h"
#include "Presets/DeepPluckFactoryLibrary.h"
#include "GeneratorTests.h"

#include <charconv>
#include <iostream>
#include <map>

namespace
{
std::uint64_t unsignedInteger (const juce::String& value, bool hexadecimalAllowed = false)
{
    auto text = value.toStdString();
    int base = 10;
    if (hexadecimalAllowed && text.starts_with ("0x")) { text.erase (0, 2); base = 16; }
    std::uint64_t result = 0;
    const auto parsed = std::from_chars (text.data(), text.data() + text.size(), result, base);
    deeppluck::presets::require (! text.empty() && parsed.ec == std::errc {} && parsed.ptr == text.data() + text.size(), "Invalid unsigned integer/seed");
    return result;
}
}

int main (int argc, char** argv)
{
    using namespace deeppluck::presets;
    if (argc == 2 && juce::String (argv[1]) == "--help")
    {
        std::cout << "DeepPluckPresetGenerator " << DEEPPLUCK_PRESET_TOOL_VERSION << "\n"
            "generate --category all|deep|warm|bright|emotional|atmospheric|arp|poly|experimental\n"
            " --count 1..100 --start-index 1..1000000 --seed UINT64_OR_0xHEX --profile rev1 --output NEW_ABSOLUTE_RUN_ROOT\n"
            "validate --input ABSOLUTE_RUN_ROOT\n"
            "prepare-audition --input ABSOLUTE_RUN_ROOT --output NEW_ABSOLUTE_AUDITION_ROOT\n"
            "factory-check --input ABSOLUTE_FACTORY_ROOT --require-final yes|no\n"
            "stage-promotion --input ABSOLUTE_REVIEWED_FACTORY_ROOT --output NEW_ABSOLUTE_STAGING_ROOT\n"
            "self-test --suite profiles|files|factory|integration --output NEW_ABSOLUTE_TEST_ROOT\n"
            "Outputs are restricted to project-local V0.12.0 artifacts. No Factory promotion.\n";
        return 0;
    }
    try
    {
        require (argc >= 2, "Use --help for commands");
        const juce::String command (argv[1]);
        require (command == "generate" || command == "validate" || command == "prepare-audition"
                 || command == "factory-check" || command == "self-test" || command == "stage-promotion", "Unknown command");
        std::map<juce::String, juce::String> arguments;
        for (int i = 2; i < argc; ++i)
        {
            const juce::String key (argv[i]);
            require (++i < argc && key.startsWith ("--"), "Missing option value");
            require (arguments.emplace (key, juce::String (argv[i])).second, "Duplicate option");
        }
        const auto get = [&] (const juce::String& key)
        {
            const auto found = arguments.find (key);
            require (found != arguments.end(), "Missing " + key);
            return found->second;
        };
        const auto path = [&] (const juce::String& key)
        {
            const auto value = get (key);
            require (juce::File::isAbsolutePath (value), "Use absolute paths");
            return juce::File (value);
        };
        juce::ScopedJuceInitialiser_GUI initialiseJuce;
        ParameterContext context;
        if (command == "generate")
        {
            require (arguments.size() == 6, "Unexpected/missing generate option");
            require (get ("--profile") == profileVersion, "Unknown profile version");
            const auto count = unsignedInteger (get ("--count"));
            const auto start = unsignedInteger (get ("--start-index"));
            require (count >= 1 && count <= 100 && start >= 1 && start <= 1000000 - count + 1, "Invalid count/index");
            const auto seed = unsignedInteger (get ("--seed"), true);
            std::vector<int> selected;
            const auto category = get ("--category");
            for (std::size_t i = 0; i < deeppluck::factoryCategories.size(); ++i)
                if (category == "all" || category == deeppluck::factoryCategories[i].id) selected.push_back (static_cast<int> (i));
            require (! selected.empty(), "Unknown category");
            generateRun (context.state, selected, static_cast<int> (count), static_cast<int> (start), seed, path ("--output"));
            const auto manifest = validateRun (context.state, path ("--output"));
            std::cout << "Generated and validated " << manifest["entries"].getArray()->size() << " candidates; none are approved Factory presets.\n";
        }
        else if (command == "validate")
        {
            require (arguments.size() == 1, "Unexpected validate option");
            const auto manifest = validateRun (context.state, path ("--input"));
            std::cout << "PASS: " << manifest["entries"].getArray()->size() << " candidate files. Audio/user approval are separate.\n";
        }
        else if (command == "prepare-audition")
        {
            require (arguments.size() == 2, "Unexpected audition option");
            prepareAudition (context.state, path ("--input"), path ("--output"));
            std::cout << "Prepared isolated read-only candidate pack and editable User folder.\n";
        }
        else if (command == "stage-promotion")
        {
            require(arguments.size()==2,"Unexpected promotion option");
            stagePromotion(context.state,path("--input"),path("--output"));
            std::cout << "Validated the reviewed Factory library and staged exact copies; production Factory unchanged.\n";
        }
        else if (command == "factory-check")
        {
            require (arguments.size() == 2, "Unexpected Factory option");
            const auto root = path ("--input");
            require (get ("--require-final") == "yes" || get ("--require-final") == "no", "Invalid final gate option");
            deeppluck::FactoryLibrary library;
            require (deeppluck::validateFactoryLibrary (context.state, root.getChildFile ("catalog.json").loadFileAsString(),
                     [&] (const juce::String& file) { return containedFile (root, file).loadFileAsString(); },
                     get ("--require-final") == "yes", library), library.error);
            std::cout << (library.awaitingCuration
                              ? "Gate A manifest valid; final Factory library is still OPEN.\n"
                              : "PASS: " + std::to_string (library.entries.size())
                                    + " approved active Factory entries.\n");
        }
        else
        {
            require (arguments.size() == 2, "Unexpected self-test option");
            runGeneratorTests (context.state, get ("--suite"), path ("--output"));
        }
        return 0;
    }
    catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; }
}
