#include "rive/file.hpp"
#include "rive/bindable_artboard.hpp"
#include "rive/runtime_header.hpp"
#include "rive/animation/animation.hpp"
#include "rive/artboard_component_list.hpp"
#include "rive/core/field_types/core_color_type.hpp"
#include "rive/core/field_types/core_double_type.hpp"
#include "rive/core/field_types/core_string_type.hpp"
#include "rive/core/field_types/core_uint_type.hpp"
#include "rive/generated/core_registry.hpp"
#include "rive/importers/artboard_importer.hpp"
#include "rive/importers/backboard_importer.hpp"
#include "rive/importers/bindable_property_importer.hpp"
#include "rive/importers/data_converter_group_importer.hpp"
#include "rive/importers/data_converter_formula_importer.hpp"
#include "rive/importers/enum_importer.hpp"
#include "rive/importers/file_asset_importer.hpp"
#include "rive/importers/script_asset_importer.hpp"
#include "rive/importers/import_stack.hpp"
#ifdef WITH_RIVE_SCRIPTING
#include "rive/lua/rive_lua_libs.hpp"
#endif
#include "rive/importers/keyed_object_importer.hpp"
#include "rive/importers/keyed_property_importer.hpp"
#include "rive/importers/linear_animation_importer.hpp"
#include "rive/importers/scripted_object_importer.hpp"
#include "rive/importers/state_machine_importer.hpp"
#include "rive/importers/state_machine_listener_importer.hpp"
#include "rive/importers/state_machine_layer_importer.hpp"
#include "rive/importers/layer_state_importer.hpp"
#include "rive/importers/state_transition_importer.hpp"
#include "rive/importers/state_machine_layer_component_importer.hpp"
#include "rive/importers/transition_viewmodel_condition_importer.hpp"
#include "rive/importers/viewmodel_importer.hpp"
#include "rive/importers/viewmodel_instance_importer.hpp"
#include "rive/importers/viewmodel_instance_list_importer.hpp"
#include "rive/importers/data_bind_path_importer.hpp"
#include "rive/animation/blend_state_transition.hpp"
#include "rive/animation/any_state.hpp"
#include "rive/animation/entry_state.hpp"
#include "rive/animation/exit_state.hpp"
#include "rive/animation/animation_state.hpp"
#include "rive/animation/blend_state_1d_input.hpp"
#include "rive/animation/blend_state_1d_viewmodel.hpp"
#include "rive/animation/blend_state_direct.hpp"
#include "rive/animation/transition_property_viewmodel_comparator.hpp"
#include "rive/constraints/scrolling/scroll_physics.hpp"
#include "rive/data_bind/data_bind.hpp"
#include "rive/data_bind/data_bind_path.hpp"
#include "rive/data_bind/bindable_property.hpp"
#include "rive/data_bind/bindable_property_artboard.hpp"
#include "rive/data_bind/bindable_property_asset.hpp"
#include "rive/data_bind/bindable_property_number.hpp"
#include "rive/data_bind/bindable_property_string.hpp"
#include "rive/data_bind/bindable_property_color.hpp"
#include "rive/data_bind/bindable_property_enum.hpp"
#include "rive/data_bind/bindable_property_integer.hpp"
#include "rive/data_bind/bindable_property_boolean.hpp"
#include "rive/data_bind/bindable_property_trigger.hpp"
#include "rive/data_bind/bindable_property_asset.hpp"
#include "rive/data_bind/converters/data_converter_group.hpp"
#include "rive/data_bind/converters/data_converter_number_to_list.hpp"
#include "rive/assets/file_asset.hpp"
#include "rive/assets/audio_asset.hpp"
#include "rive/assets/script_asset.hpp"
#include "rive/assets/file_asset_contents.hpp"
#include "rive/scripted/scripted_drawable.hpp"
#include "rive/scripted/scripted_layout.hpp"
#include "rive/scripted/scripted_object.hpp"
#include "rive/scripted/scripted_path_effect.hpp"
#include "rive/viewmodel/viewmodel.hpp"
#include "rive/viewmodel/data_enum.hpp"
#include "rive/viewmodel/viewmodel_instance.hpp"
#include "rive/viewmodel/viewmodel_instance_list.hpp"
#include "rive/viewmodel/viewmodel_instance_viewmodel.hpp"
#include "rive/viewmodel/viewmodel_instance_number.hpp"
#include "rive/viewmodel/viewmodel_instance_string.hpp"
#include "rive/viewmodel/viewmodel_property_viewmodel.hpp"
#include "rive/viewmodel/viewmodel_property_string.hpp"
#include "rive/viewmodel/viewmodel_property_number.hpp"
#include "rive/viewmodel/viewmodel_property_enum.hpp"
#include "rive/viewmodel/viewmodel_property_enum_custom.hpp"
#include "rive/viewmodel/viewmodel_property_enum_system.hpp"
#include "rive/viewmodel/viewmodel_property_list.hpp"
#include "rive/viewmodel/viewmodel_property_trigger.hpp"
#include "rive/viewmodel/viewmodel_property_symbol_list_index.hpp"
#include "rive/viewmodel/runtime/viewmodel_runtime.hpp"

// Default namespace for Rive Cpp code
using namespace rive;

#if defined(DEBUG) && defined(WITH_RIVE_TOOLS)
size_t File::debugTotalFileCount = 0;
#endif

#if !defined(RIVE_FMT_U64)
#if defined(__ANDROID__)
#if INTPTR_MAX == INT64_MAX
#define RIVE_FMT_U64 "%lu"
#define RIVE_FMT_I64 "%ld"
#else
#define RIVE_FMT_U64 "%llu"
#define RIVE_FMT_I64 "%lld"
#endif
#elif defined(_WIN32)
#define RIVE_FMT_U64 "%lld"
#define RIVE_FMT_I64 "%llu"
#else
#include <inttypes.h>
#define RIVE_FMT_U64 "%" PRIu64
#define RIVE_FMT_I64 "%" PRId64
#endif
#endif

// Import a single Rive runtime object.
// Used by the file importer.
static Core* readRuntimeObject(BinaryReader& reader,
                               const RuntimeHeader& header)
{
    auto coreObjectKey = reader.readVarUintAs<int>();
    auto object = CoreRegistry::makeCoreInstance(coreObjectKey);
    while (true)
    {
        auto propertyKey = reader.readVarUintAs<uint16_t>();
        if (propertyKey == 0)
        {
            // Terminator. https://media.giphy.com/media/7TtvTUMm9mp20/giphy.gif
            break;
        }

        if (reader.hasError())
        {
            delete object;
            return nullptr;
        }
        if (object == nullptr || !object->deserialize(propertyKey, reader))
        {
            // We have an unknown object or property, first see if core knows
            // the property type.
            int id = CoreRegistry::propertyFieldId(propertyKey);
            if (id == -1)
            {
                // No, check if it's in toc.
                id = header.propertyFieldId(propertyKey);
            }

            if (id == -1)
            {
                // Still couldn't find it, give up.
                fprintf(stderr,
                        "Unknown property key %d, missing from property ToC.\n",
                        propertyKey);
                delete object;
                return nullptr;
            }

            switch (id)
            {
                case CoreUintType::id:
                    CoreUintType::deserialize(reader);
                    break;
                case CoreStringType::id:
                    CoreStringType::deserialize(reader);
                    break;
                case CoreDoubleType::id:
                    CoreDoubleType::deserialize(reader);
                    break;
                case CoreColorType::id:
                    CoreColorType::deserialize(reader);
                    break;
            }
        }
    }
    if (object == nullptr)
    {
        // fprintf(stderr,
        //         "File contains an unknown object with coreType " RIVE_FMT_U64
        //         ", which " "this runtime doesn't understand.\n",
        //         coreObjectKey);
        return nullptr;
    }
    return object;
}

File::File(Factory* factory, rcp<FileAssetLoader> assetLoader) :
    m_factory(factory), m_assetLoader(std::move(assetLoader))
{
#if defined(DEBUG) && defined(WITH_RIVE_TOOLS)
    debugTotalFileCount++;
#endif
    assert(factory);
}

File::~File()
{
#if defined(DEBUG) && defined(WITH_RIVE_TOOLS)
    debugTotalFileCount--;
#endif
    for (auto artboard : m_artboards)
    {
        delete artboard;
    }
    for (auto& viewModel : m_ViewModels)
    {
        viewModel->unref();
    }
    for (auto& viewModelInstance : m_ViewModelInstances)
    {
        viewModelInstance->unref();
    }
    for (auto& enumData : m_Enums)
    {
        enumData->unref();
    }
    for (auto& dataConverter : m_DataConverters)
    {
        delete dataConverter;
    }
    for (auto& keyframeInterpolator : m_keyframeInterpolators)
    {
        delete keyframeInterpolator;
    }
    for (auto& physics : m_scrollPhysics)
    {
        delete physics;
    }
    delete m_backboard;
#ifdef WITH_RIVE_SCRIPTING
    cleanupScriptingVM();
#endif
}

rcp<File> File::import(Span<const uint8_t> bytes,
                       Factory* factory,
                       ImportResult* result,
                       rcp<FileAssetLoader> assetLoader)
{
    BinaryReader reader(bytes);
    RuntimeHeader header;
    if (!RuntimeHeader::read(reader, header))
    {
        fprintf(stderr, "Bad header\n");
        if (result)
        {
            *result = ImportResult::malformed;
        }
        return nullptr;
    }
    if (header.majorVersion() != majorVersion)
    {
        fprintf(stderr,
                "Unsupported version %u.%u expected %u.%u.\n",
                header.majorVersion(),
                header.minorVersion(),
                majorVersion,
                minorVersion);
        if (result)
        {
            *result = ImportResult::unsupportedVersion;
        }
        return nullptr;
    }
    auto file = make_rcp<File>(factory, std::move(assetLoader));

    auto readResult = file->read(reader, header);
    if (result)
    {
        *result = readResult;
    }
    if (readResult != ImportResult::success)
    {
        file.reset(nullptr);
    }
    return file;
}

ImportResult File::read(BinaryReader& reader, const RuntimeHeader& header)
{
    ImportStack importStack;
#ifdef WITH_RIVE_SCRIPTING
    std::vector<InBandByteCode> inBandBytecode;
#endif
    // TODO: @hernan consider moving this to a special importer. It's not that
    // simple because Core doesn't have a typeKey, so it should be treated as
    // a special case. In any case, it's not that bad having it here for now.
    Core* lastBindableObject = nullptr;
    while (!reader.reachedEnd())
    {
        auto object = readRuntimeObject(reader, header);
        if (object == nullptr)
        {
            importStack.readNullObject();
            continue;
        }
        if (!object->is<DataBind>())
        {
            lastBindableObject = object;
        }
        else if (lastBindableObject != nullptr)
        {
            object->as<DataBind>()->target(lastBindableObject);
        }
        if (object->import(importStack) == StatusCode::Ok)
        {
            switch (object->coreType())
            {
                case Backboard::typeKey:
                    m_backboard = object->as<Backboard>();
                    break;
                case Artboard::typeKey:
                {
                    Artboard* ab = object->as<Artboard>();
                    ab->m_Factory = m_factory;
                    m_artboards.push_back(ab);
                }
                break;
                case ImageAsset::typeKey:
                case FontAsset::typeKey:
                case AudioAsset::typeKey:
                case ScriptAsset::typeKey:
                {
                    auto fa = object->as<FileAsset>();
                    m_fileAssets.push_back(rcp<FileAsset>(fa));
                }
                break;
                case ViewModel::typeKey:
                {
                    auto vmc = object->as<ViewModel>();
                    m_ViewModels.push_back(vmc);
                    break;
                }
                case ViewModelInstance::typeKey:
                {
                    auto vmi = object->as<ViewModelInstance>();
                    m_ViewModelInstances.push_back(vmi);
                    break;
                }
                case DataEnum::typeKey:
                case DataEnumCustom::typeKey:
                {
                    auto de = object->as<DataEnum>();
                    m_Enums.push_back(de);
                    break;
                }
                case ViewModelPropertyEnumCustom::typeKey:
                {
                    auto vme = object->as<ViewModelPropertyEnumCustom>();
                    if (vme->enumId() < m_Enums.size())
                    {
                        vme->dataEnum(m_Enums[vme->enumId()]);
                    }
                }
                break;
            }
        }
        else
        {
            if (lastBindableObject == object)
            {
                lastBindableObject = nullptr;
            }
            fprintf(stderr,
                    "Failed to import object of type %d\n",
                    object->coreType());
            delete object;
            continue;
        }
        std::unique_ptr<ImportStackObject> stackObject = nullptr;
        auto stackType = object->coreType();

        switch (stackType)
        {
            case Backboard::typeKey:
                stackObject = rivestd::make_unique<BackboardImporter>(
                    object->as<Backboard>());
                static_cast<BackboardImporter*>(stackObject.get())->file(this);
                break;
            case Artboard::typeKey:
                stackObject = rivestd::make_unique<ArtboardImporter>(
                    object->as<Artboard>());
                break;
            case DataEnumCustom::typeKey:
                stackObject = rivestd::make_unique<EnumImporter>(
                    object->as<DataEnumCustom>());
                break;
            case LinearAnimation::typeKey:
                stackObject = rivestd::make_unique<LinearAnimationImporter>(
                    object->as<LinearAnimation>());
                break;
            case KeyedObject::typeKey:
                stackObject = rivestd::make_unique<KeyedObjectImporter>(
                    object->as<KeyedObject>());
                break;
            case KeyedProperty::typeKey:
            {
                auto importer = importStack.latest<LinearAnimationImporter>(
                    LinearAnimation::typeKey);
                if (importer == nullptr)
                {
                    return ImportResult::malformed;
                }
                stackObject = rivestd::make_unique<KeyedPropertyImporter>(
                    importer->animation(),
                    object->as<KeyedProperty>());
                break;
            }
            case StateMachine::typeKey:
                stackObject = rivestd::make_unique<StateMachineImporter>(
                    object->as<StateMachine>());
                break;
            case StateMachineLayer::typeKey:
            {
                auto artboardImporter =
                    importStack.latest<ArtboardImporter>(ArtboardBase::typeKey);
                if (artboardImporter == nullptr)
                {
                    return ImportResult::malformed;
                }

                stackObject = rivestd::make_unique<StateMachineLayerImporter>(
                    object->as<StateMachineLayer>(),
                    artboardImporter->artboard());

                break;
            }
            case EntryState::typeKey:
            case ExitState::typeKey:
            case AnyState::typeKey:
            case AnimationState::typeKey:
            case BlendState1DViewModel::typeKey:
            case BlendState1DInput::typeKey:
            case BlendStateDirect::typeKey:
                stackObject = rivestd::make_unique<LayerStateImporter>(
                    object->as<LayerState>());
                stackType = LayerState::typeKey;
                break;
            case StateTransition::typeKey:
            case BlendStateTransition::typeKey:
                stackObject = rivestd::make_unique<StateTransitionImporter>(
                    object->as<StateTransition>());
                stackType = StateTransition::typeKey;
                break;
            case StateMachineListener::typeKey:
                stackObject =
                    rivestd::make_unique<StateMachineListenerImporter>(
                        object->as<StateMachineListener>());
                break;
            case ImageAsset::typeKey:
            case FontAsset::typeKey:
            case AudioAsset::typeKey:
                stackObject = rivestd::make_unique<FileAssetImporter>(
                    object->as<FileAsset>(),
                    m_assetLoader,
                    m_factory);
                stackType = FileAsset::typeKey;
                break;
#ifdef WITH_RIVE_SCRIPTING
            case ScriptAsset::typeKey:
            {
                auto scriptAsset = object->as<ScriptAsset>();
                stackObject =
                    rivestd::make_unique<ScriptAssetImporter>(scriptAsset,
                                                              m_assetLoader,
                                                              m_factory,
                                                              &inBandBytecode);
                stackType = FileAsset::typeKey;
                scriptAsset->file(this);
                break;
            }
#endif
            case ManifestAsset::typeKey:
                stackObject = rivestd::make_unique<FileAssetImporter>(
                    object->as<FileAsset>(),
                    m_assetLoader,
                    m_factory);
                stackType = FileAsset::typeKey;
                m_manifest = rcp<FileAsset>(object->as<ManifestAsset>());
                break;
            case ViewModel::typeKey:
                stackObject = rivestd::make_unique<ViewModelImporter>(
                    object->as<ViewModel>());
                stackType = ViewModel::typeKey;
                object->as<ViewModel>()->file(this);
                break;
            case ViewModelInstance::typeKey:
                stackObject = rivestd::make_unique<ViewModelInstanceImporter>(
                    object->as<ViewModelInstance>());
                stackType = ViewModelInstance::typeKey;
                break;
            case ViewModelInstanceList::typeKey:
                stackObject =
                    rivestd::make_unique<ViewModelInstanceListImporter>(
                        object->as<ViewModelInstanceList>());
                stackType = ViewModelInstanceList::typeKey;
                break;
            case TransitionViewModelCondition::typeKey:
            case TransitionArtboardCondition::typeKey:
                stackObject =
                    rivestd::make_unique<TransitionViewModelConditionImporter>(
                        object->as<TransitionViewModelCondition>());
                stackType = TransitionViewModelCondition::typeKey;
                break;
            case BindablePropertyNumber::typeKey:
            case BindablePropertyString::typeKey:
            case BindablePropertyColor::typeKey:
            case BindablePropertyEnum::typeKey:
            case BindablePropertyBoolean::typeKey:
            case BindablePropertyAsset::typeKey:
            case BindablePropertyArtboard::typeKey:
            case BindablePropertyTrigger::typeKey:
            case BindablePropertyInteger::typeKey:
            case BindablePropertyList::typeKey:
                stackObject = rivestd::make_unique<BindablePropertyImporter>(
                    object->as<BindableProperty>());
                stackType = BindablePropertyBase::typeKey;
                break;
            case DataConverterGroupBase::typeKey:
                stackObject = rivestd::make_unique<DataConverterGroupImporter>(
                    object->as<DataConverterGroup>());
                stackType = DataConverterGroupBase::typeKey;
                break;
            case DataConverterFormulaBase::typeKey:
                stackObject =
                    rivestd::make_unique<DataConverterFormulaImporter>(
                        object->as<DataConverterFormula>());
                stackType = DataConverterFormulaBase::typeKey;
                break;
            case DataConverterNumberToList::typeKey:
                object->as<DataConverterNumberToList>()->file(this);
                break;
            case ArtboardComponentList::typeKey:
                object->as<ArtboardComponentList>()->file(this);
                break;
            case NestedArtboard::typeKey:
            case NestedArtboardLayout::typeKey:
            case NestedArtboardLeaf::typeKey:
                object->as<NestedArtboard>()->file(this);
                break;
            case ScriptedDataConverter::typeKey:
            case ScriptedDrawable::typeKey:
            case ScriptedLayout::typeKey:
            case ScriptedPathEffect::typeKey:
            {
                auto scriptedObject = ScriptedObject::from(object);
                if (scriptedObject != nullptr)
                {
                    stackObject = rivestd::make_unique<ScriptedObjectImporter>(
                        scriptedObject);
                    stackType = ScriptedDrawable::typeKey;
                }
                break;
            }
            case DataBindPathBase::typeKey:
                stackObject = rivestd::make_unique<DataBindPathImporter>(
                    object->as<DataBindPath>());
                stackType = DataBindPathBase::typeKey;
                break;
        }
        if (importStack.makeLatest(stackType, std::move(stackObject)) !=
            StatusCode::Ok)
        {
            // Some previous stack item didn't resolve.
            return ImportResult::malformed;
        }
        if (object->is<StateMachineLayerComponent>() &&
            importStack.makeLatest(
                StateMachineLayerComponent::typeKey,
                rivestd::make_unique<StateMachineLayerComponentImporter>(
                    object->as<StateMachineLayerComponent>())) !=
                StatusCode::Ok)
        {
            return ImportResult::malformed;
        }
        if (object->is<DataConverter>())
        {
            m_DataConverters.push_back(object->as<DataConverter>());
        }
        else if (object->is<KeyFrameInterpolator>())
        {
            // The file only owns the interpolators that don't belong to a
            // specific artboard
            auto artboardImporter =
                importStack.latest<ArtboardImporter>(ArtboardBase::typeKey);
            if (artboardImporter == nullptr)
            {
                m_keyframeInterpolators.push_back(
                    object->as<KeyFrameInterpolator>());
            }
        }
        else if (object->is<ScrollPhysics>())
        {
            m_scrollPhysics.push_back(object->as<ScrollPhysics>());
        }
    }

    auto resolved = importStack.resolve();
#ifdef WITH_RIVE_SCRIPTING
    registerScripts();
#endif
    return !reader.hasError() && resolved == StatusCode::Ok
               ? ImportResult::success
               : ImportResult::malformed;
}

#ifdef WITH_RIVE_SCRIPTING
void File::registerScripts()
{
    if (m_scriptingVM == nullptr)
    {
        makeScriptingVM();
    }

    // Add all scripts to the VM for registration
    for (auto asset : m_fileAssets)
    {
        if (asset->is<ScriptAsset>())
        {
            ScriptAsset* scriptAsset = asset->as<ScriptAsset>();
            // At runtime, generatorFunctionRef should be 0, meaning
            // it hasn't been registered yet with a VM.
            if (scriptAsset->verified())
            {
                m_scriptingVM->addModule(scriptAsset);
            }
        }
    }

    // Perform registration - ScriptingContext will handle dependencies and
    // retries
    m_scriptingVM->performRegistration();
}

void File::makeScriptingVM()
{
    cleanupScriptingVM();
    m_scriptingContext =
        rivestd::make_unique<CPPRuntimeScriptingContext>(m_factory);
    m_scriptingVM = rivestd::make_unique<ScriptingVM>(m_scriptingContext.get());
    m_luaState = new LuaState(m_scriptingVM->state());
    m_luaState->initializeData(m_ViewModels);
}

void File::cleanupScriptingVM()
{
    if (m_scriptingVM != nullptr)
    {
        delete m_luaState;
        m_luaState = nullptr;
        m_scriptingVM.reset();
        m_scriptingContext.reset();
    }
}
#endif

Artboard* File::artboard(std::string name) const
{
    for (const auto& artboard : m_artboards)
    {
        if (artboard->name() == name)
        {
            return artboard;
        }
    }
    return nullptr;
}

Artboard* File::artboard() const
{
    if (m_artboards.empty())
    {
        return nullptr;
    }
    return m_artboards[0];
}

Artboard* File::artboard(size_t index) const
{
    if (index >= m_artboards.size())
    {
        return nullptr;
    }
    return m_artboards[index];
}

std::string File::artboardNameAt(size_t index) const
{
    auto ab = this->artboard(index);
    return ab ? ab->name() : "";
}

std::unique_ptr<ArtboardInstance> File::artboardDefault() const
{
    auto ab = this->artboard();
    return ab ? ab->instance() : nullptr;
}

std::unique_ptr<ArtboardInstance> File::artboardAt(size_t index) const
{
    auto ab = this->artboard(index);
    return ab ? ab->instance() : nullptr;
}

std::unique_ptr<ArtboardInstance> File::artboardNamed(std::string name) const
{
    auto ab = this->artboard(name);
    return ab ? ab->instance() : nullptr;
}

rcp<BindableArtboard> File::bindableArtboardNamed(std::string name) const
{
    auto ab = this->artboardNamed(name);
    return ab ? make_rcp<BindableArtboard>(ref_rcp(this), std::move(ab))
              : nullptr;
}

rcp<BindableArtboard> File::bindableArtboardDefault() const
{
    auto ab = this->artboardDefault();
    return ab ? make_rcp<BindableArtboard>(ref_rcp(this), std::move(ab))
              : nullptr;
}

rcp<BindableArtboard> File::internalBindableArtboardFromArtboard(
    Artboard* artboard) const
{
    auto ab = artboard ? artboard->instance() : nullptr;
    return ab ? make_rcp<BindableArtboard>(nullptr, std::move(ab)) : nullptr;
}

void File::completeViewModelInstance(
    rcp<ViewModelInstance> viewModelInstance) const
{
    std::unordered_map<ViewModelInstance*, rcp<ViewModelInstance>> instancesMap;
    completeViewModelInstance(viewModelInstance, instancesMap);
}

void File::completeViewModelInstance(
    rcp<ViewModelInstance> viewModelInstance,
    std::unordered_map<ViewModelInstance*, rcp<ViewModelInstance>>&
        instancesMap) const
{
    auto viewModel = m_ViewModels[viewModelInstance->viewModelId()];
    auto propertyValues = viewModelInstance->propertyValues();
    for (auto& value : propertyValues)
    {
        if (value->is<ViewModelInstanceViewModel>())
        {
            auto property = viewModel->property(value->viewModelPropertyId());
            if (property->is<ViewModelPropertyViewModel>())
            {
                auto valueViewModel = value->as<ViewModelInstanceViewModel>();
                auto propertViewModel =
                    property->as<ViewModelPropertyViewModel>();
                auto viewModelReference =
                    m_ViewModels[propertViewModel->viewModelReferenceId()];
                auto viewModelReferenceInstance = viewModelReference->instance(
                    valueViewModel->propertyValue());
                valueViewModel->parentViewModelInstance(
                    viewModelInstance.get());
                if (viewModelReferenceInstance != nullptr)
                {
                    auto itr = instancesMap.find(viewModelReferenceInstance);

                    if (itr == instancesMap.end())
                    {
                        auto viewModelReferenceInstanceCopy =
                            copyViewModelInstance(viewModelReferenceInstance,
                                                  instancesMap);
                        instancesMap[viewModelReferenceInstance] =
                            viewModelReferenceInstanceCopy;
                        valueViewModel->referenceViewModelInstance(
                            viewModelReferenceInstanceCopy);
                    }
                    else
                    {
                        valueViewModel->referenceViewModelInstance(itr->second);
                    }
                }
            }
        }
        else if (value->is<ViewModelInstanceList>())
        {
            auto viewModelList = value->as<ViewModelInstanceList>();
            viewModelList->parentViewModelInstance(viewModelInstance.get());
            for (auto& listItem : viewModelList->listItems())
            {
                auto viewModel = m_ViewModels[listItem->viewModelId()];
                auto viewModelListItemInstance =
                    viewModel->instance(listItem->viewModelInstanceId());
                if (viewModelListItemInstance != nullptr)
                {

                    auto itr = instancesMap.find(viewModelListItemInstance);

                    if (itr == instancesMap.end())
                    {
                        auto viewModelInstanceListItemCopy =
                            copyViewModelInstance(viewModelListItemInstance,
                                                  instancesMap);
                        instancesMap[viewModelListItemInstance] =
                            viewModelInstanceListItemCopy;
                        listItem->viewModelInstance(
                            viewModelInstanceListItemCopy);
                    }
                    else
                    {
                        listItem->viewModelInstance(itr->second);
                    }
                }
            }
        }
        value->viewModelProperty(
            viewModel->property(value->viewModelPropertyId()));
    }
}

rcp<ViewModelInstance> File::copyViewModelInstance(
    ViewModelInstance* viewModelInstance,
    std::unordered_map<ViewModelInstance*, rcp<ViewModelInstance>>&
        instancesMap) const
{
    auto copy = rcp<ViewModelInstance>(
        viewModelInstance->clone()->as<ViewModelInstance>());
    completeViewModelInstance(copy, instancesMap);
#ifdef WITH_RIVE_TOOLS
    if (copy && m_triggerViewModelCreatedCallback &&
        m_viewmodelInstanceCreatedCallback)
    {
        // Serialize and send to Dart when instance is created
        m_viewmodelInstanceCreatedCallback(copy.get());
    }
#endif
    return copy;
}

rcp<ViewModelInstance> File::createViewModelInstance(std::string name) const
{
    for (auto& viewModel : m_ViewModels)
    {
        if (viewModel->is<ViewModel>())
        {
            if (viewModel->name() == name)
            {
                return createViewModelInstance(viewModel);
            }
        }
    }
    return nullptr;
}

rcp<ViewModelInstance> File::createViewModelInstance(
    std::string name,
    std::string instanceName) const
{
    for (auto& viewModel : m_ViewModels)
    {
        if (viewModel->name() == name)
        {
            auto instance = viewModel->instance(instanceName);
            if (instance != nullptr)
            {
                std::unordered_map<ViewModelInstance*, rcp<ViewModelInstance>>
                    instancesMap;
                return copyViewModelInstance(instance, instancesMap);
            }
        }
    }
    return nullptr;
}

rcp<ViewModelInstance> File::createViewModelInstance(size_t index,
                                                     size_t instanceIndex) const
{
    if (index < m_ViewModels.size())
    {
        auto viewModel = m_ViewModels[index];
        auto instance = viewModel->instance(instanceIndex);
        if (instance != nullptr)
        {
            std::unordered_map<ViewModelInstance*, rcp<ViewModelInstance>>
                instancesMap;
            return copyViewModelInstance(instance, instancesMap);
        }
    }
    return nullptr;
}

uint32_t File::findViewModelId(ViewModel* search) const
{
    uint32_t viewModelId = 0;
    for (auto& viewModel : m_ViewModels)
    {
        if (viewModel == search)
        {
            break;
        }
        viewModelId++;
    }
    return viewModelId;
}

rcp<ViewModelInstance> File::createViewModelInstance(ViewModel* viewModel) const
{
    if (viewModel != nullptr)
    {
        uint32_t viewModelId = findViewModelId(viewModel);

        auto viewModelInstance = new ViewModelInstance();
        viewModelInstance->viewModelId(viewModelId);
        viewModelInstance->viewModel(viewModel);
        auto properties = viewModel->properties();
        uint32_t propertyId = 0;
        for (auto& property : properties)
        {
            ViewModelInstanceValue* viewModelInstanceValue = nullptr;
            switch (property->coreType())
            {
                case ViewModelPropertyStringBase::typeKey:
                    viewModelInstanceValue = new ViewModelInstanceString();
                    break;
                case ViewModelPropertyNumberBase::typeKey:
                    viewModelInstanceValue = new ViewModelInstanceNumber();
                    break;
                case ViewModelPropertyBooleanBase::typeKey:
                    viewModelInstanceValue = new ViewModelInstanceBoolean();
                    break;
                case ViewModelPropertyColorBase::typeKey:
                    viewModelInstanceValue = new ViewModelInstanceColor();
                    break;
                case ViewModelPropertyListBase::typeKey:
                    viewModelInstanceValue = new ViewModelInstanceList();
                    viewModelInstanceValue->as<ViewModelInstanceList>()
                        ->parentViewModelInstance(viewModelInstance);
                    break;
                case ViewModelPropertyEnumSystemBase::typeKey:
                case ViewModelPropertyEnumCustomBase::typeKey:
                case ViewModelPropertyEnumBase::typeKey:
                    viewModelInstanceValue = new ViewModelInstanceEnum();
                    break;
                case ViewModelPropertyTriggerBase::typeKey:
                    viewModelInstanceValue = new ViewModelInstanceTrigger();
                    break;
                case ViewModelPropertyViewModelBase::typeKey:
                {
                    viewModelInstanceValue = new ViewModelInstanceViewModel();
                    auto propertViewModel =
                        property->as<ViewModelPropertyViewModel>();
                    auto viewModelReference =
                        m_ViewModels[propertViewModel->viewModelReferenceId()];
                    auto viewModelInstanceViewModel =
                        viewModelInstanceValue
                            ->as<ViewModelInstanceViewModel>();
                    auto referenceViewModelInstance =
                        createViewModelInstance(viewModelReference);
                    if (referenceViewModelInstance)
                    {
                        viewModelInstanceViewModel->parentViewModelInstance(
                            viewModelInstance);
                        viewModelInstanceViewModel->referenceViewModelInstance(
                            referenceViewModelInstance);
                    }
                }
                break;
                case ViewModelPropertyAssetImageBase::typeKey:
                    viewModelInstanceValue = new ViewModelInstanceAssetImage();
                    break;
                case ViewModelPropertySymbolListIndexBase::typeKey:
                    viewModelInstanceValue =
                        new ViewModelInstanceSymbolListIndex();
                    break;
                case ViewModelPropertyArtboardBase::typeKey:
                    viewModelInstanceValue = new ViewModelInstanceArtboard();
                    break;
                default:
                    fprintf(stderr, "Missing view model property type\n");
                    break;
            }
            if (viewModelInstanceValue != nullptr)
            {
                viewModelInstanceValue->viewModelProperty(property);
                viewModelInstanceValue->viewModelPropertyId(propertyId);
            }
            viewModelInstance->addValue(viewModelInstanceValue);
            propertyId++;
        }
#ifdef WITH_RIVE_TOOLS
        if (viewModelInstance && m_triggerViewModelCreatedCallback &&
            m_viewmodelInstanceCreatedCallback)
        {
            // Serialize and send to Dart when instance is created
            m_viewmodelInstanceCreatedCallback(viewModelInstance);
        }
#endif
        return rcp<ViewModelInstance>(viewModelInstance);
    }
    return nullptr;
}

rcp<ViewModelInstance> File::createViewModelInstance(Artboard* artboard) const
{
    if ((size_t)artboard->viewModelId() < m_ViewModels.size())
    {
        auto viewModel = m_ViewModels[artboard->viewModelId()];
        if (viewModel != nullptr)
        {
            return createViewModelInstance(viewModel);
        }
    }
    return nullptr;
}

rcp<ViewModelInstance> File::createDefaultViewModelInstance(
    Artboard* artboard) const
{
    if ((size_t)artboard->viewModelId() < m_ViewModels.size())
    {
        auto viewModel = m_ViewModels[artboard->viewModelId()];
        if (viewModel != nullptr)
        {
            return createDefaultViewModelInstance(viewModel);
        }
    }
    return nullptr;
}

rcp<ViewModelInstance> File::createDefaultViewModelInstance(
    ViewModel* viewModel) const
{
    auto viewModelInstance = viewModel->instance(0);
    if (viewModelInstance != nullptr)
    {
        auto copy = rcp<ViewModelInstance>(
            viewModelInstance->clone()->as<ViewModelInstance>());
        completeViewModelInstance(copy);
#ifdef WITH_RIVE_TOOLS
        if (copy && m_triggerViewModelCreatedCallback &&
            m_viewmodelInstanceCreatedCallback)
        {
            // Serialize and send to Dart when instance is created
            m_viewmodelInstanceCreatedCallback(copy.get());
        }
#endif
        return copy;
    }
    return createViewModelInstance(viewModel);
}

ViewModelInstanceListItem* File::viewModelInstanceListItem(
    rcp<ViewModelInstance> viewModelInstance)
{
    // Search for an implicit artboard linked to the viewModel.
    // It will return the first one it finds, but there could be more.
    // We should decide if we want to be more restrictive and only return
    // an artboard if one and only one is found.
    for (auto& artboard : m_artboards)
    {
        if (artboard->viewModelId() == viewModelInstance->viewModelId())
        {
            return viewModelInstanceListItem(viewModelInstance, artboard);
        }
    }
    return nullptr;
}

ViewModelInstanceListItem* File::viewModelInstanceListItem(
    rcp<ViewModelInstance> viewModelInstance,
    Artboard* artboard)
{
    auto viewModelInstanceListItem = new ViewModelInstanceListItem();
    viewModelInstanceListItem->viewModelInstance(viewModelInstance);
    viewModelInstanceListItem->artboard(artboard);
    return viewModelInstanceListItem;
}

ViewModel* File::viewModel(std::string name)
{
    for (auto& viewModel : m_ViewModels)
    {
        if (viewModel->name() == name)
        {
            return viewModel;
        }
    }
    return nullptr;
}

ViewModel* File::viewModel(size_t index)
{
    if (index < m_ViewModels.size())
    {
        return m_ViewModels[index];
    }
    return nullptr;
}

ViewModelRuntime* File::viewModelByIndex(size_t index) const
{
    if (index < m_ViewModels.size())
    {
        return createViewModelRuntime(m_ViewModels[index]).get();
    }
    fprintf(stderr,
            "Could not find View Model. Index %zu is out of range.\n",
            index);
    return nullptr;
}

ViewModelRuntime* File::viewModelByName(std::string name) const
{
    for (auto& viewModel : m_ViewModels)
    {
        if (viewModel->name() == name)
        {
            return createViewModelRuntime(viewModel).get();
        }
    }
    fprintf(stderr, "Could not find View Model named %s.\n", name.c_str());
    return nullptr;
}

ViewModelRuntime* File::defaultArtboardViewModel(Artboard* artboard) const
{
    if (artboard == nullptr)
    {
        fprintf(stderr, "Invalid Artboard\n");
        return nullptr;
    }
    if ((size_t)artboard->viewModelId() < m_ViewModels.size())
    {
        auto viewModel = m_ViewModels[artboard->viewModelId()];
        return createViewModelRuntime(viewModel).get();
    }
    fprintf(stderr,
            "Could not find a View Model linked to Artboard %s.\n",
            artboard->name().c_str());
    return nullptr;
}

rcp<ViewModelRuntime> File::createViewModelRuntime(ViewModel* viewModel) const
{
    auto viewModelRuntime = make_rcp<ViewModelRuntime>(viewModel, this);
    m_viewModelRuntimes.push_back(viewModelRuntime);
    return viewModelRuntime;
}

Span<const rcp<FileAsset>> File::assets() const { return m_fileAssets; }

const std::vector<DataEnum*>& File::enums() const { return m_Enums; }

#ifdef WITH_RIVE_TOOLS
const std::vector<uint8_t> File::stripAssets(Span<const uint8_t> bytes,
                                             std::set<uint16_t> typeKeys,
                                             ImportResult* result)
{
    std::vector<uint8_t> strippedData;
    strippedData.reserve(bytes.size());
    BinaryReader reader(bytes);
    RuntimeHeader header;
    if (!RuntimeHeader::read(reader, header))
    {
        if (result)
        {
            *result = ImportResult::malformed;
        }
    }
    else if (header.majorVersion() != majorVersion)
    {
        if (result)
        {
            *result = ImportResult::unsupportedVersion;
        }
    }
    else
    {
        strippedData.insert(strippedData.end(),
                            bytes.data(),
                            reader.position());
        const uint8_t* from = reader.position();
        const uint8_t* to = reader.position();
        uint16_t lastAssetType = 0;
        while (!reader.reachedEnd())
        {
            auto object = readRuntimeObject(reader, header);
            if (object == nullptr)
            {
                continue;
            }
            if (object->is<FileAssetBase>())
            {
                lastAssetType = object->coreType();
            }
            if (object->is<FileAssetContents>() &&
                typeKeys.find(lastAssetType) != typeKeys.end())
            {
                if (from != to)
                {
                    strippedData.insert(strippedData.end(), from, to);
                }
                from = reader.position();
            }
            delete object;
            to = reader.position();
        }
        if (from != to)
        {
            strippedData.insert(strippedData.end(), from, to);
        }
        *result = ImportResult::success;
    }
    return strippedData;
}

#endif

rcp<FileAsset> File::asset(size_t index)
{
    if (index >= 0 && index < m_fileAssets.size())
    {
        return m_fileAssets[index];
    }
    return nullptr;
}
