/*
 * 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 "bridge/declarative_frontend/jsview/js_accessibility.h"

#include "bridge/declarative_frontend/engine/functions/js_accessibility_function.h"
#include "bridge/declarative_frontend/engine/jsi/nativeModule/arkts_native_frame_node_bridge.h"
#include "bridge/declarative_frontend/jsview/js_view_abstract.h"
#include "bridge/declarative_frontend/jsview/models/view_abstract_model_impl.h"
#include "core/accessibility/static/accessibility_static_utils.h"
#include "core/components_ng/base/view_abstract_model_ng.h"
#include "frameworks/base/log/ace_scoring_log.h"

namespace OHOS::Ace::Framework {
namespace {
const std::vector<AccessibilitySamePageMode> PAGE_MODE_TYPE = { AccessibilitySamePageMode::SEMI_SILENT,
    AccessibilitySamePageMode::FULL_SILENT };
const std::string DEFAULT_STATE_DESCRIPTION = "";
}

void JSViewAbstract::JsAccessibilityGroup(const JSCallbackInfo& info)
{
    bool isGroup = false;
    if (info[0]->IsBoolean()) {
        isGroup = info[0]->ToBoolean();
    }
    ViewAbstractModel::GetInstance()->SetAccessibilityGroup(isGroup);

    if ((info.Length() <= 1) || !info[1]->IsObject()) {
        return;
    }
    auto obj = JSRef<JSObject>::Cast(info[1]);

    auto preferAccessibilityTextObj = obj->GetProperty("AccessibilityProferred");
    auto preferAccessibilityText =
        preferAccessibilityTextObj->IsBoolean() ? preferAccessibilityTextObj->ToBoolean() : false;
    
    auto stateControllerTypeObj = obj->GetProperty("stateControllerRoleType");
    auto stateControllerTypeNumber = stateControllerTypeObj->IsNumber() ?
        stateControllerTypeObj->ToNumber<int32_t>() : static_cast<int32_t>(AccessibilityRoleType::ROLE_NONE);
    auto stateControllerIdObj = obj->GetProperty("stateControllerId");
    auto stateControllerId = stateControllerIdObj->IsString() ?
        stateControllerIdObj->ToString() : "";
    auto actionControllerTypeObj = obj->GetProperty("actionControllerRoleType");
    auto actionControllerTypeNumber = actionControllerTypeObj->IsNumber() ?
        actionControllerTypeObj->ToNumber<int32_t>() : static_cast<int32_t>(AccessibilityRoleType::ROLE_NONE);
    auto actionControllerIdObj = obj->GetProperty("actionControllerId");
    auto actionControllerId = actionControllerIdObj->IsString() ? actionControllerIdObj->ToString() : "";

    NG::AccessibilityGroupOptions groupOptions = {
        .accessibilityTextPreferred = preferAccessibilityText,
        .stateControllerByType = static_cast<AccessibilityRoleType>(stateControllerTypeNumber),
        .stateControllerByInspector = stateControllerId,
        .actionControllerByType = static_cast<AccessibilityRoleType>(actionControllerTypeNumber),
        .actionControllerByInspector = actionControllerId,
    };
    ViewAbstractModel::GetInstance()->SetAccessibilityTextPreferred(preferAccessibilityText);
    ViewAbstractModel::GetInstance()->SetAccessibilityGroupOptions(groupOptions);
}

void JSViewAbstract::JsAccessibilityText(const JSCallbackInfo& info)
{
    const JSRef<JSVal>& jsValue = info[0];
    std::string text;
    if (!ParseJsString(jsValue, text)) {
        return;
    }
    ViewAbstractModel::GetInstance()->SetAccessibilityText(text);
}

void JSViewAbstract::JsAccessibilityTextHint(const std::string& text)
{
    ViewAbstractModel::GetInstance()->SetAccessibilityTextHint(text);
}

void JSViewAbstract::JsAccessibilityNextFocusId(const JSCallbackInfo& info)
{
    const JSRef<JSVal>& jsValue = info[0];
    std::string nextFocusId;
    if (!ParseJsString(jsValue, nextFocusId)) {
        return;
    }
    ViewAbstractModel::GetInstance()->SetAccessibilityNextFocusId(nextFocusId);

    if (info.Length() > 1 && info[1]->IsObject()) {
        auto obj = JSRef<JSObject>::Cast(info[1]);
        NG::AccessibilityNextFocusParams params;
        auto descendantModeVal = obj->GetProperty("isConsiderDescendants");
        if (descendantModeVal->IsBoolean()) {
            params.nextFocusInspectorKey = nextFocusId;
            params.descendantMode = descendantModeVal->ToBoolean();
        }
        ViewAbstractModel::GetInstance()->SetAccessibilityNextFocusParams(params);
    }
}

void JSViewAbstract::JsAccessibilityDescription(const JSCallbackInfo& info)
{
    const JSRef<JSVal>& jsValue = info[0];
    std::string description;
    if (!ParseJsString(jsValue, description)) {
        return;
    }
    std::pair<bool, std::string> autoEventPair(false, "");
    std::pair<bool, std::string> descriptionPair(false, "");
    ParseAccessibilityDescriptionJson(description, autoEventPair, descriptionPair);
    if (descriptionPair.first) {
        ViewAbstractModel::GetInstance()->SetAccessibilityDescription(descriptionPair.second);
    } else {
        ViewAbstractModel::GetInstance()->SetAccessibilityDescription(description);
    }
    if (autoEventPair.first) {
        ViewAbstractModel::GetInstance()->SetAutoEventParam(autoEventPair.second);
    }
}

void JSViewAbstract::JsAccessibilityStateDescription(const JSCallbackInfo& info)
{
    const JSRef<JSVal>& jsValue = info[0];
    std::string stateDescription;
    if (!ParseJsString(jsValue, stateDescription)) {
        ViewAbstractModel::GetInstance()->SetAccessibilityStateDescription(DEFAULT_STATE_DESCRIPTION);
        return;
    }
    ViewAbstractModel::GetInstance()->SetAccessibilityStateDescription(stateDescription);
}

void JSViewAbstract::ParseAccessibilityDescriptionJson(const std::string& description,
    std::pair<bool, std::string>& autoEventPair, std::pair<bool, std::string>& descriptionPair)
{
    if (description.empty()) {
        return;
    }
    if (!StartWith(description, "{") || !EndWith(description, "}")) {
        return;
    }
    auto jsonObj = JsonUtil::ParseJsonString(description);
    if (!jsonObj || !jsonObj->IsValid() || !jsonObj->IsObject()) {
        return;
    }
    if (jsonObj->Contains("$autoEventParam")) {
        auto param = jsonObj->GetValue("$autoEventParam");
        if (param) {
            autoEventPair = std::make_pair(true, param->ToString());
        }
    }
    if (jsonObj->Contains("$accessibilityDescription")) {
        descriptionPair = std::make_pair(true, jsonObj->GetString("$accessibilityDescription"));
    } else if (jsonObj->Contains("$autoEventParam")) {
        descriptionPair = std::make_pair(true, "");
    }
}

void JSViewAbstract::JsAccessibilityImportance(const std::string& importance)
{
    ViewAbstractModel::GetInstance()->SetAccessibilityImportance(importance);
}

void JSViewAbstract::JsAccessibilityLevel(const std::string& level)
{
    ViewAbstractModel::GetInstance()->SetAccessibilityImportance(level);
}

void JSViewAbstract::JsAccessibilityVirtualNode(const JSCallbackInfo& info)
{
    // parse builder
    if (!info[0]->IsObject()) {
        return;
    }
    JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
    auto builder = obj->GetProperty("builder");
    if (builder->IsFunction()) {
        auto builderFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSFunc>::Cast(builder));
        CHECK_NULL_VOID(builderFunc);
        auto buildFunc = [execCtx = info.GetExecutionContext(), func = std::move(builderFunc)]() {
            JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
            ACE_SCORING_EVENT("AccessibilityVirtualNode");
            func->Execute();
        };
        NG::ViewAbstractModelNG::GetInstance()->SetAccessibilityVirtualNode(std::move(buildFunc));
    }
}

void JSViewAbstract::JsAccessibilitySelected(const JSCallbackInfo& info)
{
    bool selected = false;
    bool resetValue = false;
    JSRef<JSVal> arg = info[0];
    if (arg->IsUndefined()) {
        resetValue = true;
    } else if (arg->IsBoolean()) {
        selected = arg->ToBoolean();
    } else {
        return;
    }

    ViewAbstractModel::GetInstance()->SetAccessibilitySelected(selected, resetValue);
}

void JSViewAbstract::JsAccessibilityChecked(const JSCallbackInfo& info)
{
    bool checked = false;
    bool resetValue = false;
    JSRef<JSVal> arg = info[0];
    if (arg->IsUndefined()) {
        resetValue = true;
    } else if (arg->IsBoolean()) {
        checked = arg->ToBoolean();
    } else {
        return;
    }

    ViewAbstractModel::GetInstance()->SetAccessibilityChecked(checked, resetValue);
}

void JSViewAbstract::JsAccessibilityScrollTriggerable(const JSCallbackInfo& info)
{
    bool scrollTriggerable = false;
    bool resetValue = false;
    JSRef<JSVal> arg = info[0];
    if (arg->IsUndefined()) {
        resetValue = true;
    } else if (arg->IsBoolean()) {
        scrollTriggerable = arg->ToBoolean();
    } else {
        return;
    }

    ViewAbstractModel::GetInstance()->SetAccessibilityScrollTriggerable(scrollTriggerable, resetValue);
}

void JSViewAbstract::JsAccessibilityRole(const JSCallbackInfo& info)
{
    bool resetValue = false;
    std::string role;
    if (info.Length() < 1 || !info[0]->IsNumber()) {
        ViewAbstractModel::GetInstance()->SetAccessibilityRole(role, true);
        return;
    }
    auto index = info[0]->ToNumber<int32_t>();
    AccessibilityRoleType text = static_cast<AccessibilityRoleType>(index);
    role = AccessibilityUtils::GetRoleByType(text);
    if (role.empty()) {
        resetValue = true;
    }
    ViewAbstractModel::GetInstance()->SetAccessibilityRole(role, resetValue);
}

void JSViewAbstract::JsOnAccessibilityFocus(const JSCallbackInfo& info)
{
    if (info[0]->IsUndefined() || !info[0]->IsFunction()) {
        ViewAbstractModel::GetInstance()->ResetOnAccessibilityFocus();
        return;
    }
    RefPtr<JsFunction> jsFoucusFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSFunc>::Cast(info[0]));
    WeakPtr<NG::FrameNode> frameNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
    auto onAccessibilityFoucus = [execCtx = info.GetExecutionContext(), func = std::move(jsFoucusFunc),
                                     node = frameNode](bool isFocus) {
        JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
        ACE_SCORING_EVENT("onAccessibilityFoucus");
        PipelineContext::SetCallBackNode(node);
        JSRef<JSVal> newJSVal = JSRef<JSVal>::Make(ToJSValue(isFocus));
        func->ExecuteJS(1, &newJSVal);
    };
    ViewAbstractModel::GetInstance()->SetOnAccessibilityFocus(std::move(onAccessibilityFoucus));
}

void JSViewAbstract::JsAccessibilityDefaultFocus(const JSCallbackInfo& info)
{
    JSRef<JSVal> arg = info[0];
    if (arg->IsBoolean()) {
        auto isFocus = arg->ToBoolean();
        ViewAbstractModel::GetInstance()->SetAccessibilityDefaultFocus(isFocus);
        return;
    }
    ViewAbstractModel::GetInstance()->SetAccessibilityDefaultFocus(false);
}

void JSViewAbstract::JsAccessibilityUseSamePage(const JSCallbackInfo& info)
{
    JSRef<JSVal> arg = info[0];
    if (arg->IsNumber()) {
        auto pageModeType = arg->ToNumber<int32_t>();
        if (pageModeType >= 0 && pageModeType < static_cast<int32_t>(PAGE_MODE_TYPE.size())) {
            auto pageMode = static_cast<bool>(PAGE_MODE_TYPE[pageModeType]) ? "FULL_SILENT" : "SEMI_SILENT";
            ViewAbstractModel::GetInstance()->SetAccessibilityUseSamePage(pageMode);
            return;
        }
        ViewAbstractModel::GetInstance()->SetAccessibilityUseSamePage("");
        return;
    }
    ViewAbstractModel::GetInstance()->SetAccessibilityUseSamePage("");
}

void JSViewAbstract::JsAccessibilityFocusDrawLevel(const JSCallbackInfo& info)
{
    int32_t drawLevel = 0;
    JSRef<JSVal> arg = info[0];
    do {
        if (!arg->IsNumber()) {
            break;
        }
        if (arg->ToNumber<int32_t>() > 1) {
            break;
        }
        drawLevel = arg->ToNumber<int32_t>();
    } while (false);
    ViewAbstractModel::GetInstance()->SetAccessibilityFocusDrawLevel(drawLevel);
}

void JSViewAbstract::JsOnAccessibilityActionIntercept(const JSCallbackInfo& info)
{
    if (info[0]->IsUndefined() || !info[0]->IsFunction()) {
        ViewAbstractModel::GetInstance()->SetOnAccessibilityActionIntercept(nullptr);
        return;
    }
    auto jsInterceptFunc = AceType::MakeRefPtr<JsAccessibilityActionInterceptFunction>(JSRef<JSFunc>::Cast(info[0]));
    WeakPtr<NG::FrameNode> frameNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
    auto onAccessibilityActionIntercept = [execCtx = info.GetExecutionContext(), func = std::move(jsInterceptFunc),
        node = frameNode](AccessibilityInterfaceAction action) -> AccessibilityActionInterceptResult {
        JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, AccessibilityActionInterceptResult::ACTION_CONTINUE);
        ACE_SCORING_EVENT("onAccessibilityActionIntercept");
        PipelineContext::SetCallBackNode(node);
        return func->Execute(action);
    };
    ViewAbstractModel::GetInstance()->SetOnAccessibilityActionIntercept(std::move(onAccessibilityActionIntercept));
}

void JSViewAbstract::JsOnAccessibilityHoverTransparent(const JSCallbackInfo& args)
{
    if (args[0]->IsUndefined() || !args[0]->IsFunction()) {
        ViewAbstractModel::GetInstance()->SetOnAccessibilityHoverTransparent(nullptr);
        return;
    }
    auto jsOnHoverTransparentFunc =
        AceType::MakeRefPtr<JsAccessibilityHoverTransparentFunction>(JSRef<JSFunc>::Cast(args[0]));
    WeakPtr<NG::FrameNode> frameNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
    auto onHoverTransparentFunc = [execCtx = args.GetExecutionContext(),
        func = jsOnHoverTransparentFunc, node = frameNode](
        TouchEventInfo& info) {
        JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
        ACE_SCORING_EVENT("onHoverTransparent");
        PipelineContext::SetCallBackNode(node);
        func->Execute(info);
    };
    ViewAbstractModel::GetInstance()->SetOnAccessibilityHoverTransparent(std::move(onHoverTransparentFunc));
}

void JSViewAbstract::JsAccessibilityActionOptions(const JSCallbackInfo& args)
{
    NG::AccessibilityActionOptions options;
    if (args.Length() == 0 || args[0]->IsUndefined()) {
        ViewAbstractModel::GetInstance()->ResetAccessibilityActionOptions();
        return;
    }
    if (args.Length() > 0 && args[0]->IsObject()) {
        auto obj = JSRef<JSObject>::Cast(args[0]);
        auto scrollStepVal = obj->GetProperty("scrollStep");
        if (scrollStepVal->IsNumber()) {
            int32_t stepCount = scrollStepVal->ToNumber<int32_t>();
            options.scrollStep = (stepCount >= 1) ? stepCount : 1;
        }
        ViewAbstractModel::GetInstance()->SetAccessibilityActionOptions(options);
    } else {
        ViewAbstractModel::GetInstance()->ResetAccessibilityActionOptions();
    }
}

void JSViewAbstract::JsAccessibilityCustomActions(const JSCallbackInfo& args)
{
    if (args.Length() == 0 || args[0]->IsUndefined()) {
        ViewAbstractModel::GetInstance()->ResetAccessibilityCustomActions();
        return;
    }
    
    if (args.Length() > 0 && args[0]->IsArray()) {
        auto array = JSRef<JSArray>::Cast(args[0]);
        uint32_t length = array->Length();
        std::vector<NG::AccessibilityCustomAction> actions;
        
        for (uint32_t i = 0; i < length; i++) {
            auto item = array->GetValueAt(i);
            if (!item->IsObject()) {
                continue;
            }
            
            auto obj = JSRef<JSObject>::Cast(item);
            NG::AccessibilityCustomAction action;
            
            auto nameVal = obj->GetProperty("name");
            if (!ParseJsString(nameVal, action.actionName)) {
                action.actionName = "";
            }
            
            auto callbackVal = obj->GetProperty("onAction");
            if (callbackVal->IsFunction()) {
                auto jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSFunc>::Cast(callbackVal));
                WeakPtr<NG::FrameNode> frameNode =
                    AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
                action.customActionCallback =
                    [execCtx = args.GetExecutionContext(), func = jsFunc, node = frameNode]() {
                    JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
                    ACE_SCORING_EVENT("customActionCallback");
                    PipelineContext::SetCallBackNode(node);
                    func->Execute();
                };
            }
            
            if (!action.actionName.empty() && action.customActionCallback) {
                actions.push_back(action);
            }
        }
        
        ViewAbstractModel::GetInstance()->SetAccessibilityCustomActions(actions);
    } else {
        ViewAbstractModel::GetInstance()->ResetAccessibilityCustomActions();
    }
}
}