/*
 * 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 "napi/native_api.h"
#include "AudioVividWrapper.h"
#include "AudioVividPlaybackManager.h"
#include "AudioVividFlvMuxerManager.h"
#include "AudioConfig.h"
#include "AudioLog.h"
#include <memory>
#include <cstring>

static std::unique_ptr<AudioVividWrapper> g_audioVividWrapper = nullptr;
static std::unique_ptr<AudioVividPlaybackManager> g_playbackManager = nullptr;
static std::unique_ptr<AudioVividFlvMuxerManager> g_flvMuxerManager = nullptr;

static napi_value InitializeVivid(napi_env env, napi_callback_info info)
{
    g_audioVividWrapper = std::make_unique<AudioVividWrapper>();
    bool success = g_audioVividWrapper->Initialize();
    napi_value result;
    napi_get_boolean(env, success, &result);
    return result;
}

static napi_value UpdateObjectPosition(napi_env env, napi_callback_info info)
{
    size_t argc = 4;
    napi_value args[4] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (!g_audioVividWrapper) {
        napi_throw_error(env, nullptr, "AudioVivid not initialized");
        return nullptr;
    }
    int32_t objectIndex;
    double x;
    double y;
    double z;
    napi_get_value_int32(env, args[0], &objectIndex);
    napi_get_value_double(env, args[1], &x);
    napi_get_value_double(env, args[2], &y); // 第2个入参
    napi_get_value_double(env, args[3], &z); // 第3个入参
    bool success = g_audioVividWrapper->UpdateObjectPosition(
        objectIndex, static_cast<float>(x), static_cast<float>(y), static_cast<float>(z));
    napi_value result;
    napi_get_boolean(env, success, &result);
    return result;
}

static napi_value UpdateObjectGain(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (!g_audioVividWrapper) {
        napi_throw_error(env, nullptr, "AudioVivid not initialized");
        return nullptr;
    }
    int32_t objectIndex = 0;
    double gain = 0.0;
    napi_get_value_int32(env, args[0], &objectIndex);
    napi_get_value_double(env, args[1], &gain);
    AVCODEC_SAMPLE_LOGI("UpdateObjectGain in C++: objectIndex=%d, gain(double)=%f, gain(float)=%f",
        objectIndex,
        gain,
        static_cast<float>(gain));
    bool success = g_audioVividWrapper->UpdateObjectGain(objectIndex, static_cast<float>(gain));
    napi_value result;
    napi_get_boolean(env, success, &result);
    return result;
}

static napi_value GetMetadata(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (!g_audioVividWrapper) {
        napi_throw_error(env, nullptr, "AudioVivid not initialized");
        return nullptr;
    }
    bool withStaticMeta = false;
    if (argc >= 1) {
        napi_get_value_bool(env, args[0], &withStaticMeta);
    }
    std::vector<uint8_t> metadata = g_audioVividWrapper->GetMetadata(withStaticMeta);
    void *data = nullptr;
    napi_value arrayBuffer;
    napi_create_arraybuffer(env, metadata.size(), &data, &arrayBuffer);
    memcpy(data, metadata.data(), metadata.size());
    return arrayBuffer;
}

static napi_value StartPlayback(napi_env env, napi_callback_info info)
{
    g_playbackManager = std::make_unique<AudioVividPlaybackManager>();
    int32_t ret = g_playbackManager->Initialize();
    if (ret != 0) {
        napi_throw_error(env, nullptr, "Failed to initialize playback manager");
        return nullptr;
    }
    ret = g_playbackManager->Start();
    if (ret != 0) {
        napi_throw_error(env, nullptr, "Failed to start playback");
        return nullptr;
    }
    napi_value result;
    napi_get_boolean(env, true, &result);
    return result;
}

static napi_value StopPlayback(napi_env env, napi_callback_info info)
{
    if (!g_playbackManager) {
        napi_throw_error(env, nullptr, "Playback manager not initialized");
        return nullptr;
    }
    int32_t ret = g_playbackManager->Stop();
    if (ret != 0) {
        napi_throw_error(env, nullptr, "Failed to stop playback");
        return nullptr;
    }
    napi_value result;
    napi_get_boolean(env, true, &result);
    return result;
}

static napi_value UpdatePlaybackMetadata(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (argc < 1) {
        napi_throw_error(env, nullptr, "Missing metadata argument");
        return nullptr;
    }
    bool isArrayBuffer;
    napi_is_arraybuffer(env, args[0], &isArrayBuffer);
    if (!isArrayBuffer) {
        napi_throw_error(env, nullptr, "Metadata must be an ArrayBuffer");
        return nullptr;
    }
    void *data = nullptr;
    size_t length = 0;
    napi_get_arraybuffer_info(env, args[0], &data, &length);
    if (!g_playbackManager) {
        napi_throw_error(env, nullptr, "Playback manager not initialized");
        return nullptr;
    }
    g_playbackManager->UpdateMetadata(static_cast<uint8_t *>(data), static_cast<int32_t>(length));
    napi_value result;
    napi_get_boolean(env, true, &result);
    return result;
}

static napi_value UpdateFlvMuxMetadata(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (argc < 1) {
        napi_throw_error(env, nullptr, "Missing metadata argument");
        return nullptr;
    }
    bool isArrayBuffer;
    napi_is_arraybuffer(env, args[0], &isArrayBuffer);
    if (!isArrayBuffer) {
        napi_throw_error(env, nullptr, "Metadata must be an ArrayBuffer");
        return nullptr;
    }
    void *data = nullptr;
    size_t length = 0;
    napi_get_arraybuffer_info(env, args[0], &data, &length);
    if (!g_flvMuxerManager) {
        napi_throw_error(env, nullptr, "FLV muxer not initialized");
        return nullptr;
    }
    g_flvMuxerManager->UpdateMetadata(static_cast<uint8_t *>(data), static_cast<int32_t>(length));
    napi_value result;
    napi_get_boolean(env, true, &result);
    return result;
}

static napi_value StartFlvMux(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    int32_t fd = -1;
    if (argc >= 1) {
        napi_get_value_int32(env, args[0], &fd);
    }
    if (fd < 0) {
        napi_throw_error(env, nullptr, "Invalid fd");
        return nullptr;
    }

    AVCODEC_SAMPLE_LOGI("StartFlvMux: fd = %{public}d", fd);

    g_flvMuxerManager = std::make_unique<AudioVividFlvMuxerManager>();
    int32_t ret = g_flvMuxerManager->Initialize(fd);
    if (ret != 0) {
        const char *steps[] = {"", "PCM文件打开失败", "编码器创建失败", "编码器配置失败",
            "OH_AVMuxer创建失败", "音频格式创建失败", "添加音频轨失败", "封装器启动失败"};
        std::string errMsg = "初始化失败[步骤" + std::to_string(ret) + "]: ";
        errMsg += (ret >= 1 && ret <= 7) ? steps[ret] : "未知错误"; // ret 范围 [1,7]
        napi_throw_error(env, nullptr, errMsg.c_str());
        return nullptr;
    }
    ret = g_flvMuxerManager->Start();
    if (ret != 0) {
        napi_throw_error(env, nullptr, "Failed to start FLV muxing");
        return nullptr;
    }
    napi_value result;
    napi_get_boolean(env, true, &result);
    return result;
}

static napi_value StopFlvMux(napi_env env, napi_callback_info info)
{
    if (!g_flvMuxerManager) {
        napi_throw_error(env, nullptr, "FLV muxer not initialized");
        return nullptr;
    }
    int32_t ret = g_flvMuxerManager->Stop();
    if (ret != 0) {
        napi_throw_error(env, nullptr, "Failed to stop FLV muxing");
        return nullptr;
    }
    napi_value result;
    napi_get_boolean(env, true, &result);
    return result;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        {"initializeVivid", nullptr, InitializeVivid, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"updateObjectPosition", nullptr, UpdateObjectPosition, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"updateObjectGain", nullptr, UpdateObjectGain, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"getMetadata", nullptr, GetMetadata, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"startPlayback", nullptr, StartPlayback, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"stopPlayback", nullptr, StopPlayback, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"updatePlaybackMetadata", nullptr, UpdatePlaybackMetadata, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"startFlvMux", nullptr, StartFlvMux, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"stopFlvMux", nullptr, StopFlvMux, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"updateFlvMuxMetadata", nullptr, UpdateFlvMuxMetadata, nullptr, nullptr, nullptr, napi_default, nullptr},
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_value Add(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);
    napi_valuetype valuetype1;
    napi_typeof(env, args[1], &valuetype1);
    double value0;
    napi_get_value_double(env, args[0], &value0);
    double value1;
    napi_get_value_double(env, args[1], &value1);
    napi_value sum;
    napi_create_double(env, value0 + value1, &sum);
    return sum;
}

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&demoModule);
}