#include "DeepPluckFactoryLibrary.h"
#include "DeepPluckFactoryData.h"
#include <juce_cryptography/juce_cryptography.h>

#include <algorithm>
#include <cmath>
#include <set>

namespace deeppluck
{
namespace
{
juce::String embeddedFile (const juce::String& name)
{
    for (int i = 0; i < DeepPluckFactoryResources::namedResourceListSize; ++i)
        if (name == DeepPluckFactoryResources::originalFilenames[i])
        {
            int size = 0;
            const auto* bytes = DeepPluckFactoryResources::getNamedResource (
                DeepPluckFactoryResources::namedResourceList[i], size);
            return juce::String::fromUTF8 (bytes, size);
        }
    return {};
}

bool validName (const juce::String& name)
{
    if (name.isEmpty() || name != name.trim() || name.length() > 80
        || name.contains ("  ") || name.containsAnyOf ("<>:\"/\\|?*") || name.endsWithChar ('.')) return false;
    for (const auto character : name) if (character < 32) return false;
    const auto base = name.upToFirstOccurrenceOf (".", false, false).toUpperCase();
    if (juce::StringArray {"CON","PRN","AUX","NUL"}.contains (base)) return false;
    if (base.length() == 4 && (base.startsWith ("COM") || base.startsWith ("LPT"))
        && base[3] >= '1' && base[3] <= '9') return false;
    return true;
}

int categoryIndex (const juce::String& id)
{
    for (std::size_t i = 0; i < factoryCategories.size(); ++i)
        if (id == factoryCategories[i].id) return static_cast<int> (i);
    return -1;
}
}

bool validateFactoryLibrary (const juce::AudioProcessorValueTreeState& state,
                             const juce::String& manifest, const FactoryFileReader& readFile,
                             bool requireFinal, FactoryLibrary& result)
{
    result = {};
    const auto fail = [&] (const juce::String& message)
    {
        result.entries.clear(); result.error = message; return false;
    };
    juce::var root;
    if (manifest.getNumBytesAsUTF8() > 1024 * 1024 || juce::JSON::parse (manifest, root).failed()
        || ! root.isObject() || root["format"].toString() != "DeepPluckFactoryLibrary"
        || ! root["schemaVersion"].isInt() || static_cast<int> (root["schemaVersion"]) != 1)
        return fail ("Invalid Factory manifest format/schema.");
    const auto* entries = root["entries"].getArray();
    const auto* reviews = root["legacyReviews"].getArray();
    if (entries == nullptr || reviews == nullptr || reviews->size() != 4)
        return fail ("Factory entries and all four Legacy reviews are required.");
    std::set<juce::String> reviewIds;
    const bool pending = root["status"].toString() == "awaiting-user-curation";
    for (const auto& review : *reviews)
    {
        const auto id = review["id"].toString();
        if (! juce::StringArray {"factory:0","factory:1","factory:2","factory:3"}.contains (id)
            || ! reviewIds.insert (id).second) return fail ("Invalid or duplicate Legacy review.");
        const auto decision = review["decision"].toString();
        if (pending ? decision != "OPEN"
                    : ! juce::StringArray {"retained","refined","replaced"}.contains (decision))
            return fail ("Legacy decisions are incomplete.");
        if (! pending && review["evidence"].toString().trim().isEmpty())
            return fail ("A Legacy decision needs explicit user evidence.");
    }
    if (pending)
    {
        if (! entries->isEmpty()) return fail ("Unapproved catalogue must not contain final sounds.");
        if (requireFinal) return fail ("OPEN: the final user-approved Factory presets are not present.");
        return true;
    }
    if (root["status"].toString() != "approved"
        || entries->size() != static_cast<int> (finalFactoryPresetCount)
        || ! root["expectedActiveCount"].isInt()
        || static_cast<int> (root["expectedActiveCount"]) != static_cast<int> (finalFactoryPresetCount))
        return fail ("Final Factory library has the wrong approved active count.");
    std::array<int,8> counts {};
    std::set<juce::String> names, ids, files;
    const auto defaults = captureParameterDefaults (state);
    for (const auto& entry : *entries)
    {
        FactoryContentEntry content;
        content.stableId = entry["id"].toString();
        content.category = entry["category"].toString();
        content.name = entry["name"].toString();
        content.version = entry["contentVersion"].toString();
        content.relativePath = entry["path"].toString();
        content.sha256 = entry["sha256"].toString();
        const auto category = categoryIndex (content.category);
        const auto slug = content.stableId.fromFirstOccurrenceOf ("factory12:", false, false);
        if (category < 0 || ! content.stableId.startsWith ("factory12:") || slug.isEmpty()
            || ! slug.containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789-") || slug.length() > 80
            || ! validName (content.name) || content.version.trim().isEmpty())
            return fail ("Invalid Factory category, identity, name or content version.");
        if (! ids.insert (content.stableId).second || ! names.insert (content.name.toLowerCase()).second)
            return fail ("Duplicate final Factory identity/name.");
        const auto expectedPrefix = juce::String (factoryCategories[static_cast<std::size_t> (category)].name) + "/";
        if (! content.relativePath.startsWith (expectedPrefix)) return fail ("Factory category/path mismatch.");
        const auto basename = content.relativePath.substring (expectedPrefix.length());
        if (! basename.endsWith (".deeppluckpreset") || ! validName (basename)
            || ! files.insert (basename.toLowerCase()).second) return fail ("Unsafe/duplicate Factory filename.");
        const auto approval = entry["approval"];
        if (approval["by"].toString() != "user" || approval["decision"].toString() != "approved"
            || approval["evidence"].toString().trim().isEmpty()
            || approval["sha256"].toString() != content.sha256)
            return fail ("Missing user approval of this exact final preset file.");
        const auto json = readFile (content.relativePath);
        if (json.isEmpty() || json.getNumBytesAsUTF8() > 1024 * 1024
            || juce::SHA256 (json.toRawUTF8(), json.getNumBytesAsUTF8()).toHexString() != content.sha256)
            return fail ("Missing or changed Factory content.");
        UserPresetData data;
        juce::String error;
        if (! decodePreset (state, defaults, json, data, error)) return fail (error);
        if (data.name != content.name) return fail ("Factory name differs from preset file.");
        const auto parsed = juce::JSON::parse (json);
        if (! parsed["parameters"].isObject()
            || parsed["parameters"].getDynamicObject()->getProperties().size() != 62)
            return fail ("Final preset must contain all 62 sound parameters.");
        for (std::size_t i = 0; i < data.values.size(); ++i)
        {
            const auto value = parsed["parameters"][allParameterIds[i]];
            const auto raw = static_cast<double> (value);
            const auto* parameter = state.getParameter (allParameterIds[i]);
            const auto& range = parameter->getNormalisableRange();
            if ((! value.isInt() && ! value.isInt64() && ! value.isDouble()) || ! std::isfinite (raw)
                || static_cast<float> (raw) < range.start || static_cast<float> (raw) > range.end)
                return fail ("Noncanonical final parameter.");
            if ((dynamic_cast<const juce::AudioParameterBool*> (parameter) != nullptr
                 || dynamic_cast<const juce::AudioParameterInt*> (parameter) != nullptr
                 || dynamic_cast<const juce::AudioParameterChoice*> (parameter) != nullptr)
                && raw != std::round (raw)) return fail ("Non-integral final discrete parameter.");
            if (std::abs (parameter->convertTo0to1 (static_cast<float> (raw))
                          - parameter->convertTo0to1 (data.values[i])) > 0.000001f)
                return fail ("Final parameter changes during canonicalization.");
        }
        content.author = data.author;
        content.values = data.values;
        result.entries.push_back (std::move (content));
        ++counts[static_cast<std::size_t> (category)];
    }
    for (std::size_t category = 0; category < counts.size(); ++category)
        if (counts[category] != finalFactoryPresetCounts[category])
            return fail ("A Factory category has the wrong approved sound count.");
    std::stable_sort (result.entries.begin(), result.entries.end(), [] (const auto& a, const auto& b)
    {
        const auto ac = categoryIndex (a.category), bc = categoryIndex (b.category);
        return ac != bc ? ac < bc : a.name.compareIgnoreCase (b.name) < 0;
    });
    result.awaitingCuration = false;
    return true;
}

FactoryLibrary loadEmbeddedFactoryLibrary (const juce::AudioProcessorValueTreeState& state)
{
    FactoryLibrary library;
    const auto read = [] (const juce::String& path)
    { return embeddedFile (path.fromLastOccurrenceOf ("/", false, false)); };
    const auto valid = validateFactoryLibrary (state, embeddedFile ("catalog.json"), read, false, library);
    juce::ignoreUnused (valid); // Build/CTest validators reject errors; hosts fail safely to Legacy content.
    return library;
}
}
