/*
 * Copyright (c) 2024 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 <gtest/gtest.h>

#ifdef WRONG_PRIVATE
#include "test/unittest/capi/stubs/friend_class_accessor.h"
#endif

#include "modifier_test_base.h"
#include "modifiers_test_utils.h"
#include "generated/test_fixtures.h"
#include "generated/type_helpers.h"

#include "core/interfaces/native/implementation/text_controller_peer_impl.h"
#include "core/interfaces/native/utility/converter.h"
#include "core/interfaces/native/utility/reverse_converter.h"
#include "core/interfaces/native/utility/callback_helper.h"
#include "generated/type_helpers.h"

#include "core/components/text/text_theme.h"
#include "core/components_ng/pattern/text/text_event_hub.h"
#include "core/components_ng/pattern/text/text_model_ng.h"
#include "core/components_ng/pattern/text/text_pattern.h"
#include "test/unittest/capi/utils/custom_node_builder_test_helper.h"

using namespace testing;
using namespace testing::ext;

namespace OHOS::Ace::NG {
namespace {
const auto ATTRIBUTE_BIND_SELECTION_MENU_NAME = "bindSelectionMenu";
const auto ATTRIBUTE_BIND_SELECTION_MENU_DEFAULT_VALUE = "[]";
const std::string TEST_CONTENT_ONE = "ContentTestOne";
const std::string TEST_CONTENT_TWO = "ContentTestTwo";

struct SelectionMenuOptions {
    std::optional<MenuOnAppearCallback> onAppear;
    std::optional<VoidCallback> onDisappear;
    std::optional<Ark_MenuType> menuType;
};
} // namespace

namespace Converter {
void AssignArkValue(Ark_SelectionMenuOptions& dst, const SelectionMenuOptions& src, ConvContext *ctx = nullptr)
{
    dst.onAppear = Converter::ArkValue<Opt_MenuOnAppearCallback>(src.onAppear);
    dst.onDisappear = Converter::ArkValue<Opt_VoidCallback>(src.onDisappear);
    dst.menuType = Converter::ArkValue<Opt_MenuType>(src.menuType);
}
} // namespace Converter

class TextModifierTest2 : public ModifierTestBase<GENERATED_ArkUITextModifier,
    &GENERATED_ArkUINodeModifiers::getTextModifier, GENERATED_ARKUI_TEXT> {
public:
    static void SetUpTestCase()
    {
        ModifierTestBase::SetUpTestCase();
        SetupTheme<TextTheme>();
    }

    std::vector<MenuItemParam> GetMenuItemParams()
    {
        std::vector<MenuItemParam> params;
        MenuOptionsParam menuOptionItemOne;
        menuOptionItemOne.content = TEST_CONTENT_ONE;
        menuOptionItemOne.action = [](const std::string&) {};
        MenuOptionsParam menuOptionItemTwo;
        menuOptionItemTwo.content = TEST_CONTENT_TWO;
        menuOptionItemTwo.action = [](const std::string&) {};
        MenuItemParam param1;
        param1.menuOptionsParam = menuOptionItemOne;
        MenuItemParam param2;
        param2.menuOptionsParam = menuOptionItemTwo;
        params.push_back(param1);
        params.push_back(param2);
        return params;
    }
};

/*
 * @tc.name: bindSelectionMenuTestDefaultValues
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(TextModifierTest2, bindSelectionMenuTestDefaultValues, TestSize.Level1)
{
    std::unique_ptr<JsonValue> jsonValue = GetJsonValue(node_);
    std::optional<std::string> resultStr;
    resultStr = GetAttrValue<std::string>(jsonValue, ATTRIBUTE_BIND_SELECTION_MENU_NAME);
    EXPECT_THAT(resultStr, Eq(ATTRIBUTE_BIND_SELECTION_MENU_DEFAULT_VALUE)) <<
        "Default value for attribute 'BindSelectionMenu'";
}

using OneTestTextSpanTypeStep = std::tuple<Ark_TextSpanType, Ark_TextResponseType>;
static const std::vector<OneTestTextSpanTypeStep> testPlan = {
    {ARK_TEXT_SPAN_TYPE_TEXT, ARK_TEXT_RESPONSE_TYPE_SELECT},
    {ARK_TEXT_SPAN_TYPE_MIXED, ARK_TEXT_RESPONSE_TYPE_SELECT},
    {ARK_TEXT_SPAN_TYPE_IMAGE, ARK_TEXT_RESPONSE_TYPE_SELECT},
    {ARK_TEXT_SPAN_TYPE_TEXT, ARK_TEXT_RESPONSE_TYPE_LONG_PRESS},
    {ARK_TEXT_SPAN_TYPE_MIXED, ARK_TEXT_RESPONSE_TYPE_LONG_PRESS},
    {ARK_TEXT_SPAN_TYPE_IMAGE, ARK_TEXT_RESPONSE_TYPE_LONG_PRESS},
    {ARK_TEXT_SPAN_TYPE_TEXT, ARK_TEXT_RESPONSE_TYPE_RIGHT_CLICK},
    {ARK_TEXT_SPAN_TYPE_MIXED, ARK_TEXT_RESPONSE_TYPE_RIGHT_CLICK},
    {ARK_TEXT_SPAN_TYPE_IMAGE, ARK_TEXT_RESPONSE_TYPE_RIGHT_CLICK},
};

std::optional<SelectOverlayInfo> GetSelectionMenuParams_Patched(FrameNode* frameNode, Ark_TextSpanType arkSpanType,
                                                                Ark_TextResponseType arkResponseType)
{
    auto pattern = frameNode->GetPattern<TextPattern>();
    CHECK_NULL_RETURN(pattern, std::nullopt);
#ifdef WRONG_MODEL
    pattern->SetSelectedType(Converter::OptConvert<TextSpanType>(arkSpanType).value_or(TextSpanType::NONE));
#endif
    pattern->SetTextResponseType(
        Converter::OptConvert<TextResponseType>(arkResponseType).value_or(TextResponseType::NONE));
    SelectOverlayInfo selectInfo;
    pattern->CopySelectionMenuParams(selectInfo);
    return selectInfo;
}

/*
 * @tc.name: setBindSelectionMenuTestValidValues
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(TextModifierTest2, DISABLED_setBindSelectionMenuTestValidValues, TestSize.Level1)
{
    ASSERT_NE(modifier_->setBindSelectionMenu, nullptr);
    auto frameNode = reinterpret_cast<FrameNode*>(node_);
    ASSERT_NE(frameNode, nullptr);

    int callsCount = 0;
    CustomNodeBuilderTestHelper<TextModifierTest2> builderHelper(this, frameNode);
    const auto builder = Converter::ArkValue<Opt_CustomNodeBuilder>(builderHelper.GetBuilder());

    std::unique_ptr<JsonValue> fullJson;
    std::optional<std::string> resultValue;
    SelectionMenuOptions selectionMenuOptions1 = {.onAppear = std::nullopt, .onDisappear = std::nullopt,
        .menuType = Ark_MenuType::ARK_MENU_TYPE_SELECTION_MENU};
    auto options1 = Converter::ArkValue<Opt_SelectionMenuOptions>(selectionMenuOptions1);

    SelectionMenuOptions selectionMenuOptions2 = {.onAppear = std::nullopt, .onDisappear = std::nullopt,
        .menuType = Ark_MenuType::ARK_MENU_TYPE_PREVIEW_MENU};
    auto options2 = Converter::ArkValue<Opt_SelectionMenuOptions>(selectionMenuOptions2);

    std::optional<SelectOverlayInfo> selectInfo;

    for (auto [spanType, responseType]: testPlan) {
        auto optSpanType = Converter::ArkValue<Opt_TextSpanType>(spanType);
        auto optResponseType = Converter::ArkValue<Opt_TextResponseType>(responseType);
        modifier_->setBindSelectionMenu(node_, &optSpanType, &builder, &optResponseType, &options1);
        selectInfo = GetSelectionMenuParams_Patched(frameNode, spanType, responseType);
        ASSERT_TRUE(selectInfo.has_value());
        ASSERT_NE(selectInfo->menuInfo.menuBuilder, nullptr);
        selectInfo->menuInfo.menuBuilder();
        EXPECT_EQ(builderHelper.GetCallsCountAsync(), ++callsCount);

        modifier_->setBindSelectionMenu(node_, &optSpanType, &builder, &optResponseType, &options2);
        selectInfo = GetSelectionMenuParams_Patched(frameNode, spanType, responseType);
        ASSERT_TRUE(selectInfo.has_value());
        ASSERT_NE(selectInfo->menuInfo.menuBuilder, nullptr);
        selectInfo->menuInfo.menuBuilder();
        EXPECT_EQ(builderHelper.GetCallsCountAsync(), ++callsCount);

        fullJson = GetJsonValue(node_);
        resultValue = GetAttrValue<std::string>(fullJson, ATTRIBUTE_BIND_SELECTION_MENU_NAME);
    }

    std::string expectedValue =
        "[{\"spanType\":0,\"responseType\":0,\"menuType\":0},"
        "{\"spanType\":0,\"responseType\":1,\"menuType\":0},"
        "{\"spanType\":0,\"responseType\":2,\"menuType\":0},"
        "{\"spanType\":1,\"responseType\":0,\"menuType\":0},"
        "{\"spanType\":1,\"responseType\":1,\"menuType\":0},"
        "{\"spanType\":1,\"responseType\":2,\"menuType\":0},"
        "{\"spanType\":2,\"responseType\":0,\"menuType\":0},"
        "{\"spanType\":2,\"responseType\":1,\"menuType\":0},"
        "{\"spanType\":2,\"responseType\":2,\"menuType\":0}]";
    EXPECT_THAT(resultValue, Eq(expectedValue)) << "Passed value is: " << expectedValue;
}

/*
 * @tc.name: setBindSelectionMenuTestMenuTypeValidValues
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(TextModifierTest2, DISABLED_setBindSelectionMenuTestMenuTypeValidValues, TestSize.Level1)
{
    ASSERT_NE(modifier_->setBindSelectionMenu, nullptr);
    auto frameNode = reinterpret_cast<FrameNode*>(node_);
    ASSERT_NE(frameNode, nullptr);

    using OneTestMenuTypeStep = std::tuple<Ark_MenuType, int32_t>;
    const std::vector<OneTestMenuTypeStep> testPlan = {
        {Ark_MenuType::ARK_MENU_TYPE_PREVIEW_MENU, 0},
        {Ark_MenuType::ARK_MENU_TYPE_SELECTION_MENU, 1},
    };

    CustomNodeBuilderTestHelper<TextModifierTest2> builderHelper(this, frameNode);
    const auto builder = Converter::ArkValue<Opt_CustomNodeBuilder>(builderHelper.GetBuilder());
    std::unique_ptr<JsonValue> fullJson;
    std::optional<std::string> resultValue;
    auto spanType = Converter::ArkValue<Opt_TextSpanType>(ARK_TEXT_SPAN_TYPE_TEXT);
    auto responseType = Converter::ArkValue<Opt_TextResponseType>(ARK_TEXT_RESPONSE_TYPE_SELECT);
    SelectionMenuOptions selectionMenuOptions = {};
    Opt_SelectionMenuOptions options;

    for (auto [arkMenuType, aceMenuType]: testPlan) {
        selectionMenuOptions.menuType = arkMenuType;
        options = Converter::ArkValue<Opt_SelectionMenuOptions>(selectionMenuOptions);
        modifier_->setBindSelectionMenu(node_, &spanType, &builder, &responseType, &options);
        fullJson = GetJsonValue(node_);
        resultValue = GetAttrValue<std::string>(fullJson, ATTRIBUTE_BIND_SELECTION_MENU_NAME);
    }

    std::string expectedValue =
        "[{\"spanType\":0,\"responseType\":2,\"menuType\":0},"
        "{\"spanType\":0,\"responseType\":2,\"menuType\":1}]";
    EXPECT_THAT(resultValue, Eq(expectedValue)) << "Passed value is: " << expectedValue;
}

/**
 * @tc.name: setEditMenuOptionsTest
 * @tc.desc: setEditMenuOptions test
 * @tc.type: FUNC
 */
HWTEST_F(TextModifierTest2, DISABLED_setEditMenuOptionsTest, TestSize.Level1)
{
#ifdef WRONG_PRIVATE
    ASSERT_NE(modifier_->setEditMenuOptions, nullptr);
    auto frameNode = reinterpret_cast<FrameNode*>(node_);
    auto pattern = frameNode->GetPattern<TextPattern>();
    ASSERT_NE(pattern, nullptr);

    int32_t testID = 323;
    struct CheckEvent {
        int32_t resourceId;
    };
    static std::optional<CheckEvent> checkEvent = std::nullopt;
    auto testOnCreateMenuCallback = [](Ark_VMContext context, const Ark_Int32 resourceId,
        const Array_TextMenuItem menuItems, const Callback_Array_TextMenuItem_Void continuation) {
        checkEvent = CheckEvent{
            .resourceId = Converter::Convert<int32_t>(resourceId),
        };
        CallbackHelper(continuation).Invoke(menuItems);
    };
    auto arkCreateCallback = Converter::ArkCallback<Opt_OnCreateMenuCallback>(testOnCreateMenuCallback, testID);

    auto testOnMenuItemClickCallback = [](Ark_VMContext context, const Ark_Int32 resourceId,
        const Ark_TextMenuItem menuItem, const Ark_TextRange range, const Callback_Boolean_Void continuation) {
        auto item = Converter::OptConvert<MenuOptionsParam>(menuItem);
        ASSERT_TRUE(item.has_value());
        ASSERT_TRUE(item->content.has_value());
        CallbackHelper(continuation).Invoke(Converter::ArkValue<Ark_Boolean>(*item->content == TEST_CONTENT_ONE));
    };
    auto arkClickCallback = Converter::ArkCallback<Opt_OnMenuItemClickCallback>(testOnMenuItemClickCallback, testID);

    Ark_EditMenuOptions options {
        .onCreateMenu = arkCreateCallback,
        .onMenuItemClick = arkClickCallback
    };
    auto inputValue = Converter::ArkValue<Opt_EditMenuOptions>(options);

    SelectOverlayInfo selectOverlayInfo;
    auto params = GetMenuItemParams();
    FriendClassAccessor::OnUpdateOnCreateMenuCallback(selectOverlayInfo, pattern);
    EXPECT_TRUE(selectOverlayInfo.onCreateCallback.onCreateMenuCallback == nullptr);
    modifier_->setEditMenuOptions(node_, &inputValue);
    FriendClassAccessor::OnUpdateOnCreateMenuCallback(selectOverlayInfo, pattern);
    ASSERT_NE(selectOverlayInfo.onCreateCallback.onCreateMenuCallback, nullptr);
    selectOverlayInfo.onCreateCallback.onCreateMenuCallback(params);
    ASSERT_TRUE(checkEvent.has_value());
    EXPECT_EQ(checkEvent->resourceId, testID);

    ASSERT_NE(selectOverlayInfo.onCreateCallback.onMenuItemClick, nullptr);
    EXPECT_TRUE(selectOverlayInfo.onCreateCallback.onMenuItemClick(params[0]));
    EXPECT_FALSE(selectOverlayInfo.onCreateCallback.onMenuItemClick(params[1]));
#endif
}
}