/*
 * Copyright (c) 2026 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "core/common/dynamic_module_helper.h"

#import <Foundation/Foundation.h>

#include <dlfcn.h>
#include <memory>
#include <mutex>

#include "base/log/log_wrapper.h"
#include "base/utils/utils.h"
#include "compatible/components/component_loader.h"
#include "interfaces/inner_api/ace/utils.h"
#include "core/common/dynamic_module.h"
#include "core/components_ng/pattern/menu/bridge/menu/menu_dynamic_module.h"
#include "core/components_ng/pattern/menu/bridge/menu_item/menu_item_dynamic_module.h"
#include "core/components_ng/pattern/menu/bridge/menu_item_group/menu_item_group_dynamic_module.h"

// Forward declarations for static module creation functions
extern "C" void* OHOS_ACE_DynamicModule_Create_Menu();
extern "C" void* OHOS_ACE_DynamicModule_Create_MenuItem();
extern "C" void* OHOS_ACE_DynamicModule_Create_MenuItemGroup();

namespace OHOS::Ace {
namespace {
const std::string DYNAMIC_MODULE_LIB_PREFIX = "libarkui_";
static NSString* DYNAMIC_MODULE_LIB_POSTFIX = @".dylib";
static NSString* FRAMEWORK_TYPE = @"framework";

// Static module instances for components bundled into libarkui_ios
static std::unique_ptr<DynamicModule> g_menuModule = nullptr;
static std::unique_ptr<DynamicModule> g_menuItemModule = nullptr;
static std::unique_ptr<DynamicModule> g_menuItemGroupModule = nullptr;

// Initialize static modules
void InitializeStaticModules()
{
    static std::once_flag initFlag;
    std::call_once(initFlag, []() {
        g_menuModule.reset(reinterpret_cast<DynamicModule*>(OHOS_ACE_DynamicModule_Create_Menu()));
        g_menuItemModule.reset(reinterpret_cast<DynamicModule*>(OHOS_ACE_DynamicModule_Create_MenuItem()));
        g_menuItemGroupModule.reset(reinterpret_cast<DynamicModule*>(OHOS_ACE_DynamicModule_Create_MenuItemGroup()));
        LOGI("InitializeStaticModules finished");
    });
}

const std::unordered_map<std::string, std::string> soMap = {
    {"Marquee", "marquee"},
    {"Stepper", "stepper" },
    {"StepperItem", "stepper" },
    {"Slider", "slider" },
    {"Checkbox", "checkbox"},
    {"CheckboxGroup", "checkbox"},
    {"Gauge", "gauge"},
    {"Rating", "rating"},
    {"FlowItem", "waterflow" },
    {"WaterFlow", "waterflow" },
    {"Counter", "counter"},
    {"Sidebar", "sidebar"},
    {"ColumnSplit", "linearsplit"},
    {"RowSplit", "linearsplit"},
    {"Radio", "radio"},
    {"QRCode", "qrcode"},
    {"TimePicker", "timepicker"},
    {"TimePickerDialog", "timepicker"},
    {"Indexer", "indexer"},
    {"Hyperlink", "hyperlink"},
    {"PatternLock", "patternlock"},
    {"CalendarPicker", "calendarpicker"},
    {"CalendarPickerDialog", "calendarpicker"},
    {"SymbolGlyph", "symbol"},
    {"DataPanel", "datapanel"},
    {"Richeditor", "richeditor"},
    {"Search", "search"},
    // Menu components are now statically linked, handled in GetDynamicModule
    {"TextClock", "textclock"},
    {"DynamicLayout", "dynamiclayout"},
};
} // namespace
DynamicModuleHelper& DynamicModuleHelper::GetInstance()
{
    static DynamicModuleHelper instance;
    return instance;
}

std::unique_ptr<ComponentLoader> DynamicModuleHelper::GetLoaderByName(const char* name)
{
    return nullptr;
}

namespace {
// Helper function to get static module by name
DynamicModule* GetStaticModule(const std::string& name)
{
    if (name == "Menu") {
        return g_menuModule.get();
    }
    if (name == "MenuItem") {
        return g_menuItemModule.get();
    }
    if (name == "MenuItemGroup") {
        return g_menuItemGroupModule.get();
    }
    return nullptr;
}

// Helper function to get module from cache (double-checked locking)
DynamicModule* GetModuleFromCache(const std::string& name, std::mutex& mutex,
                                  std::unordered_map<std::string, std::unique_ptr<DynamicModule>>& moduleMap)
{
    {
        std::lock_guard<std::mutex> lock(mutex);
        auto iter = moduleMap.find(name);
        if (iter != moduleMap.end()) {
            return iter->second.get();
        }
    }
    return nullptr;
}

// Helper function to build dylib path
NSString* BuildDylibPath(const std::string& moduleName)
{
    std::string moduleNameStr = DYNAMIC_MODULE_LIB_PREFIX + moduleName;
    NSString* nsModuleName = [NSString stringWithUTF8String:moduleNameStr.c_str()];
    NSString* frameworkPath = [[NSBundle mainBundle] pathForResource:nsModuleName ofType:FRAMEWORK_TYPE];
    return [frameworkPath stringByAppendingPathComponent:[nsModuleName stringByAppendingString:DYNAMIC_MODULE_LIB_POSTFIX]];
}

// Helper function to load dynamic library and create module
// Returns: pair<module*, handle*> or {nullptr, nullptr} on failure
std::pair<DynamicModule*, void*> LoadDynamicLibrary(const std::string& name, const std::string& moduleName)
{
    NSString* dylibPath = BuildDylibPath(moduleName);
    const char* libName = [dylibPath UTF8String];

    LOGI("First load %{public}s nativeModule start", name.c_str());
    auto* handle = dlopen(libName, RTLD_NOLOAD);
    if (handle == nullptr) {
        LOGE("Failed to load dynamic module library: %{public}s, error: %{public}s", name.c_str(), dlerror());
        return {nullptr, nullptr};
    }

    auto* createSym = reinterpret_cast<DynamicModuleCreateFunc>(dlsym(handle, (DYNAMIC_MODULE_CREATE + name).c_str()));
    if (createSym == nullptr) {
        LOGE("Failed to find symbol in library %{public}s, error: %{public}s", name.c_str(), dlerror());
        dlclose(handle);
        return {nullptr, nullptr};
    }

    DynamicModule* module = createSym();
    if (module == nullptr) {
        LOGE("Failed to create DynamicModule instance from library %{public}s", name.c_str());
        dlclose(handle);
        return {nullptr, nullptr};
    }

    LOGI("First load %{public}s nativeModule finish", name.c_str());
    return {module, handle};
}

// Helper function to insert module into cache with race condition handling
DynamicModule* InsertModuleToCache(const std::string& name, DynamicModule* module, void* handle,
                                    std::mutex& mutex,
                                    std::unordered_map<std::string, std::unique_ptr<DynamicModule>>& moduleMap)
{
    std::lock_guard<std::mutex> lock(mutex);

    // Check if another thread already loaded it
    auto iter = moduleMap.find(name);
    if (iter != moduleMap.end()) {
        // Another thread already loaded it, use that one
        delete module;
        if (handle) {
            dlclose(handle);
        }
        return iter->second.get();
    }

    moduleMap.emplace(name, std::unique_ptr<DynamicModule>(module));
    return module;
}
} // namespace

DynamicModule* DynamicModuleHelper::GetDynamicModule(const std::string& name)
{
    // Initialize static modules (Menu, MenuItem, MenuItemGroup)
    InitializeStaticModules();

    // Check for statically linked modules first
    DynamicModule* staticModule = GetStaticModule(name);
    if (staticModule != nullptr) {
        return staticModule;
    }

    // Check cache first (double-checked locking)
    DynamicModule* cachedModule = GetModuleFromCache(name, moduleMapMutex_, moduleMap_);
    if (cachedModule != nullptr) {
        return cachedModule;
    }

    // Find module mapping
    auto it = soMap.find(name);
    if (it == soMap.end()) {
        LOGE("No shared library mapping found for nativeModule: %{public}s", name.c_str());
        return nullptr;
    }

    // Load dynamic library (without holding lock - dlopen/d/dlsym may be slow)
    auto [module, handle] = LoadDynamicLibrary(name, it->second);
    if (module == nullptr) {
        return nullptr;
    }

    // Insert into cache (handles race condition if another thread loaded it)
    return InsertModuleToCache(name, module, handle, moduleMapMutex_, moduleMap_);
}

bool DynamicModuleHelper::IsDynamicModuleLoaded(const std::string& name)
{
    std::lock_guard<std::mutex> lock(moduleMapMutex_);
    auto iter = moduleMap_.find(name);
    if (iter != moduleMap_.end()) {
        return true;
    }
    return false;
}

} // namespace OHOS::Ace