package com.gzzm.lobster.runtime;

import com.gzzm.lobster.identity.UserContext;
import com.gzzm.lobster.identity.UserContextHolder;

import java.util.concurrent.Callable;

/**
 * RunScope —— 跨线程 run 执行作用域 / Cross-thread run scope helper.
 *
 * <p>zmeg_new 的 {@code @Inject} DAO 绑定到注入时的线程；当 {@link AgentRuntime} 的
 * 主循环不在 servlet 请求线程跑时（例如搬到独立 executor），必须：
 * <ol>
 *   <li>在 worker 线程 <b>进入</b> 时显式 {@link UserContextHolder#set(UserContext)}
 *       —— 让同线程后续的 DAO 调用能拿到当前用户上下文</li>
 *   <li>DAO 通过 {@code Tools.getBean(XxxDao.class)} 现拿，不要复用父线程 @Inject 实例
 *       （见 memory: <i>thunwind DAOs are thread-bound</i>）</li>
 *   <li>worker 线程 <b>退出</b> 时 {@link UserContextHolder#clear()} —— Tomcat 线程复用
 *       下不清理会让下一个 run 读到错用户</li>
 * </ol>
 *
 * <p>当前实现仅负责 ①③：DAO 现拿由调用方自行遵守。以后若要加更多 ThreadLocal
 *（如 tenant、trace id），在 {@link #runWith} 的 try/finally 里挂载即可。
 *
 * <p>使用示例：
 * <pre>
 * RunScope.runWith(user, () -&gt; {
 *     // 这里 UserContextHolder.get() == user, DAO Tools.getBean(...) 正确解析
 *     return agentRuntime.run(request, emitter);
 * });
 * </pre>
 */
public final class RunScope {

    private RunScope() {}

    /**
     * 以指定 UserContext 执行一段代码 / Run a block under a given UserContext.
     *
     * <p>幂等：若当前线程已有 UserContext 且与 {@code user} 相同，不重复 set；
     * 退出时恢复原值（不是无脑 clear）—— 这样嵌套调用也安全。
     */
    public static <T> T runWith(UserContext user, Callable<T> task) throws Exception {
        UserContext prev = UserContextHolder.get();
        boolean changed = false;
        if (user != null && user != prev) {
            UserContextHolder.set(user);
            changed = true;
        }
        try {
            return task.call();
        } finally {
            if (changed) {
                if (prev == null) UserContextHolder.clear();
                else UserContextHolder.set(prev);
            }
        }
    }

    /** 无返回值版本 / Runnable variant. */
    public static void runWith(UserContext user, Runnable task) {
        try {
            runWith(user, () -> { task.run(); return null; });
        } catch (RuntimeException re) {
            throw re;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
