/**
 * Copyright (c) 2025-2026, Michael Yang 杨福海 (fuhai999@gmail.com).
 * <p>
 * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/lgpl-3.0.txt
 * <p>
 * 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.
 */
package com.easyagents.flow.core.code.impl;

import com.alibaba.fastjson.JSONObject;
import com.easyagents.flow.core.chain.Chain;
import com.easyagents.flow.core.chain.ChainState;
import com.easyagents.flow.core.chain.NodeState;
import com.easyagents.flow.core.code.CodeRuntimeEngine;
import com.easyagents.flow.core.node.CodeNode;
import com.easyagents.flow.core.util.graalvm.JsInteropUtils;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
import org.graalvm.polyglot.Value;

import java.util.Collections;
import java.util.Map;

/**
 * 基于 GraalVM 的 JavaScript 代码节点执行器。
 *
 * <p>优先保留历史 {@code _result} 输出约定；当脚本未写入
 * {@code _result} 且声明了 {@code main} 函数时，自动传入节点参数并使用
 * {@code main} 返回的对象作为节点输出。</p>
 */
public class JavascriptRuntimeEngine implements CodeRuntimeEngine {

    // 使用 Context.Builder 构建上下文，线程安全
    private static final Context.Builder CONTEXT_BUILDER = Context.newBuilder("js")
            .option("engine.WarnInterpreterOnly", "false")
            .allowHostAccess(HostAccess.ALL)       // 允许访问 Java 对象的方法和字段
            .allowHostClassLookup(className -> false) // 禁止动态加载任意 Java 类
            .option("js.ecmascript-version", "2021");  // 使用较新的 ECMAScript 版本


    /**
     * 执行 JavaScript 代码并返回节点输出。
     *
     * @param code 用户代码
     * @param node 当前代码节点
     * @param chain 当前工作流
     * @return 代码节点输出
     * @throws RuntimeException JavaScript 执行失败或 main 返回值不是对象时抛出
     */
    @Override
    public Map<String, Object> execute(String code, CodeNode node, Chain chain) {
        try (Context context = CONTEXT_BUILDER.build()) {
            Value bindings = context.getBindings("js");
            ChainState chainState =
                    chain.getExecutionState();
            NodeState nodeState =
                    chain.getNodeState(node.getId());

            Map<String, Object> all =
                    chainState.getMemory();
            all.forEach((key, value) -> {
                if (!key.contains(".")) {
                    bindings.putMember(key, JsInteropUtils.wrapJavaValueForJS(context, value));
                }
            });

            // 注入参数
            Map<String, Object> parameterValues =
                    chainState.resolveParameters(node);
            if (parameterValues != null) {
                for (Map.Entry<String, Object> entry : parameterValues.entrySet()) {
                    bindings.putMember(entry.getKey(), JsInteropUtils.wrapJavaValueForJS(context, entry.getValue()));
                }
            }

            // 在 JS 中创建 _result 对象
            context.eval("js", "var _result = {};");

            // 注入 _chain 和 _context
            bindings.putMember("_chain", chain);
            bindings.putMember("_state", nodeState);

            // 执行用户脚本
            context.eval("js", code);

            return resolveResult(context, bindings, parameterValues);

        } catch (Exception e) {
            throw new RuntimeException("Polyglot JS 脚本执行失败: " + e.getMessage(), e);
        }
    }

    /**
     * 解析脚本输出，兼容历史 _result 写法并自动调用 main 函数。
     *
     * @param context JavaScript 执行上下文
     * @param bindings JavaScript 全局绑定
     * @param parameterValues 当前节点已解析的输入参数
     * @return 代码节点输出
     * @throws IllegalArgumentException main 返回值不是对象时抛出
     */
    private Map<String, Object> resolveResult(Context context,
                                              Value bindings,
                                              Map<String, Object> parameterValues) {
        JSONObject legacyResult = GraalvmToFastJSONUtils.toJSONObject(
                bindings.getMember("_result"));
        if (!legacyResult.isEmpty()) {
            return legacyResult;
        }

        Value mainFunction = bindings.getMember("main");
        if (mainFunction == null || !mainFunction.canExecute()) {
            return legacyResult;
        }

        Map<String, Object> mainInput = parameterValues == null
                ? Collections.emptyMap()
                : parameterValues;
        Value mainResult = mainFunction.execute(
                JsInteropUtils.wrapJavaValueForJS(context, mainInput));
        Object convertedResult =
                GraalvmToFastJSONUtils.toFastJsonValue(mainResult);
        if (!(convertedResult instanceof JSONObject)) {
            throw new IllegalArgumentException(
                    "JavaScript main 函数必须返回对象，例如：return { result: value }");
        }
        return (JSONObject) convertedResult;
    }

}
