/**
 * 会话输出缓冲服务
 *
 * 管理终端会话的输出缓冲和刷新机制
 */
import axios from "axios";
import { requestRuntimeAccessTokenRefreshForServer } from "../services/auto-register.js";
import { resolveSchedulerBaseUrlFrom } from "../config.js";
import { logger } from "../utils/logger.js";
import { getRuntimeAccessToken } from "./runtime-binding.js";
function runtimeAuthHeaders(options, callbackBaseUrl = resolveRuntimeCallbackBaseUrl(options)) {
    const token = options?.runtimeAccessToken ||
        getRuntimeAccessToken(undefined, callbackBaseUrl) ||
        getRuntimeAccessToken();
    return token ? { "X-Runtime-Token": token } : undefined;
}
/**
 * 解析 Runtime 回调的调度中心地址。
 * 主流程：优先使用启动请求携带的 scheduler 地址；否则按环境变量、单自动注册目标、默认值依次回退。
 */
function resolveRuntimeCallbackBaseUrl(options) {
    return resolveSchedulerBaseUrlFrom(options?.schedulerBaseUrl).url;
}
/** 输出刷新防抖时间：合并启动期高频小块输出，降低 HTTP/WebSocket 压力。 */
const OUTPUT_FLUSH_DEBOUNCE_MS = 100;
function getAxiosStatusCode(error) {
    if (typeof error !== "object" || error === null)
        return undefined;
    const response = error.response;
    if (typeof response !== "object" || response === null)
        return undefined;
    const status = response.status;
    return typeof status === "number" ? status : undefined;
}
/** 防止并发回调同时触发多次 token 刷新。 */
let tokenRefreshPromise = null;
async function tryRefreshRuntimeToken(serverBaseUrl) {
    if (tokenRefreshPromise)
        return tokenRefreshPromise;
    tokenRefreshPromise = (async () => {
        try {
            const result = await requestRuntimeAccessTokenRefreshForServer(serverBaseUrl);
            if (result.success && result.runtimeAccessToken) {
                logger.info("[session-output] Runtime token refreshed after 401");
                return true;
            }
            logger.warn("[session-output] Runtime token refresh failed", { error: result.error });
            return false;
        }
        catch (error) {
            logger.warn("[session-output] Runtime token refresh threw", { error });
            return false;
        }
    })();
    try {
        return await tokenRefreshPromise;
    }
    finally {
        tokenRefreshPromise = null;
    }
}
/** 单个会话待发送输出上限，避免调度器不可达时内存无限增长。 */
const MAX_PENDING_OUTPUT_CHARS = 64 * 1024;
/** 单次回调最大发送量，避免一次 POST 携带过大的终端噪声。 */
const MAX_OUTPUT_FLUSH_CHARS = 32 * 1024;
/** 活跃会话存储 */
export const sessions = new Map();
/**
 * 发送状态更新到调度器
 *
 * @param agentId - Agent ID
 * @param sessionId - 会话 ID
 * @param status - 状态
 * @param actionInfo - 可选的动作详情
 */
export async function sendStatus(agentId, sessionId, status, actionInfo, usage, authOptions) {
    const payload = { agentId, sessionId, status };
    // 添加细粒度动作信息
    if (actionInfo) {
        if (actionInfo.actionType)
            payload.actionType = actionInfo.actionType;
        if (actionInfo.actionLabel)
            payload.actionLabel = actionInfo.actionLabel;
        if (actionInfo.actionDetail)
            payload.actionDetail = actionInfo.actionDetail;
        if (actionInfo.actionResult)
            payload.actionResult = actionInfo.actionResult;
        if (actionInfo.actionId)
            payload.actionId = actionInfo.actionId;
    }
    // 添加 token 用量信息
    if (usage) {
        payload.inputTokens = usage.inputTokens;
        payload.outputTokens = usage.outputTokens;
    }
    const callbackBaseUrl = resolveRuntimeCallbackBaseUrl(authOptions);
    const headers = runtimeAuthHeaders(authOptions, callbackBaseUrl);
    if (!headers) {
        logger.warn("Skip runtime status callback because runtime token is missing", { agentId, sessionId, status, schedulerBaseUrl: callbackBaseUrl });
        return undefined;
    }
    try {
        return await axios.post(`${callbackBaseUrl}/api/runtime/callback/status`, payload, { headers });
    }
    catch (error) {
        if (getAxiosStatusCode(error) === 401) {
            const refreshed = await tryRefreshRuntimeToken(callbackBaseUrl);
            if (refreshed) {
                const retryHeaders = runtimeAuthHeaders(authOptions, callbackBaseUrl);
                if (retryHeaders) {
                    try {
                        return await axios.post(`${callbackBaseUrl}/api/runtime/callback/status`, payload, { headers: retryHeaders });
                    }
                    catch (retryError) {
                        logger.warn("Runtime status callback retry failed after token refresh", {
                            agentId, sessionId, status, error: retryError,
                        });
                        return undefined;
                    }
                }
            }
        }
        logger.warn("Runtime status callback failed", {
            agentId,
            sessionId,
            status,
            schedulerBaseUrl: callbackBaseUrl,
            error,
        });
        return undefined;
    }
}
/**
 * 发送输出到调度器
 *
 * @param agentId - Agent ID
 * @param output - 输出内容
 * @param sessionId - 会话 ID
 * @param seq - 序列号
 */
export async function sendOutput(agentId, output, sessionId, seq, metadata, authOptions) {
    const callbackBaseUrl = resolveRuntimeCallbackBaseUrl(authOptions);
    try {
        const headers = runtimeAuthHeaders(authOptions, callbackBaseUrl);
        if (!headers) {
            logger.warn("Skip runtime output callback because runtime token is missing", {
                agentId,
                sessionId,
                schedulerBaseUrl: callbackBaseUrl,
            });
            return;
        }
        await axios.post(`${callbackBaseUrl}/api/runtime/callback/output`, { agentId, output, sessionId, seq, ...metadata }, { headers });
    }
    catch (error) {
        if (getAxiosStatusCode(error) === 401) {
            const refreshed = await tryRefreshRuntimeToken(callbackBaseUrl);
            if (refreshed) {
                const retryHeaders = runtimeAuthHeaders(authOptions, callbackBaseUrl);
                if (retryHeaders) {
                    try {
                        await axios.post(`${callbackBaseUrl}/api/runtime/callback/output`, { agentId, output, sessionId, seq, ...metadata }, { headers: retryHeaders });
                        return;
                    }
                    catch {
                        // retry also failed
                    }
                }
            }
        }
        logger.warn("Runtime output callback failed", {
            agentId,
            sessionId,
            error,
        });
    }
}
/**
 * 发送 AskUserQuestion 事件到调度器。
 *
 * @param agentId - Agent ID
 * @param sessionId - SDK 会话 ID
 * @param questions - 结构化问题列表
 */
export async function sendQuestionRequest(agentId, sessionId, questions, authOptions) {
    const callbackBaseUrl = resolveRuntimeCallbackBaseUrl(authOptions);
    const payload = { agentId, sessionId, questions };
    try {
        const headers = runtimeAuthHeaders(authOptions, callbackBaseUrl);
        if (!headers) {
            return;
        }
        await axios.post(`${callbackBaseUrl}/api/runtime/callback/question`, payload, { headers });
    }
    catch (error) {
        if (getAxiosStatusCode(error) === 401) {
            const refreshed = await tryRefreshRuntimeToken(callbackBaseUrl);
            if (refreshed) {
                const retryHeaders = runtimeAuthHeaders(authOptions, callbackBaseUrl);
                if (retryHeaders) {
                    try {
                        await axios.post(`${callbackBaseUrl}/api/runtime/callback/question`, payload, { headers: retryHeaders });
                        return;
                    }
                    catch {
                        // retry also failed
                    }
                }
            }
        }
        // ignore transient callback failures
    }
}
/**
 * 发送工作区文件变更事件到调度器，由调度器通过 WebSocket 通知前端刷新预览。
 */
export async function sendFileChanged(payload) {
    const callbackBaseUrl = resolveRuntimeCallbackBaseUrl();
    try {
        const headers = runtimeAuthHeaders(undefined, callbackBaseUrl);
        if (!headers) {
            return;
        }
        await axios.post(`${callbackBaseUrl}/api/runtime/callback/file-changed`, payload, { headers });
    }
    catch (error) {
        if (getAxiosStatusCode(error) === 401) {
            const refreshed = await tryRefreshRuntimeToken(callbackBaseUrl);
            if (refreshed) {
                const retryHeaders = runtimeAuthHeaders(undefined, callbackBaseUrl);
                if (retryHeaders) {
                    try {
                        await axios.post(`${callbackBaseUrl}/api/runtime/callback/file-changed`, payload, { headers: retryHeaders });
                        return;
                    }
                    catch {
                        // retry also failed
                    }
                }
            }
        }
        // ignore transient callback failures
    }
}
/**
 * 追加会话输出到有界缓冲区。
 *
 * 主流程：拼接新输出 -> 超过上限时丢弃最旧终端噪声并保留截断提示 -> 等待 flush pump 批量发送。
 */
export function appendSessionOutput(sessionId, data) {
    const session = sessions.get(sessionId);
    if (!session || !data) {
        return;
    }
    const combinedOutput = `${session.outputBuffer}${data}`;
    if (combinedOutput.length <= MAX_PENDING_OUTPUT_CHARS) {
        session.outputBuffer = combinedOutput;
        return;
    }
    const omittedChars = combinedOutput.length - MAX_PENDING_OUTPUT_CHARS;
    const marker = `\r\n[AgentsWorkStudio: omitted ${omittedChars} chars of old terminal output]\r\n`;
    const retainedLength = Math.max(0, MAX_PENDING_OUTPUT_CHARS - marker.length);
    session.outputBuffer = `${marker}${combinedOutput.slice(-retainedLength)}`;
}
/**
 * 调度输出刷新
 *
 * @param sessionId - 会话 ID
 */
export function scheduleOutputFlush(sessionId) {
    const session = sessions.get(sessionId);
    if (!session) {
        return;
    }
    if (session.flushTimer) {
        return;
    }
    session.flushTimer = setTimeout(() => {
        session.flushTimer = null;
        flushSessionOutput(sessionId).catch(() => undefined);
    }, OUTPUT_FLUSH_DEBOUNCE_MS);
}
/**
 * 刷新会话输出
 *
 * @param sessionId - 会话 ID
 */
export async function flushSessionOutput(sessionId) {
    const session = sessions.get(sessionId);
    if (!session) {
        return;
    }
    if (session.flushInFlight) {
        return;
    }
    if (!session.outputBuffer) {
        return;
    }
    const output = session.outputBuffer.slice(0, MAX_OUTPUT_FLUSH_CHARS);
    session.outputBuffer = session.outputBuffer.slice(MAX_OUTPUT_FLUSH_CHARS);
    const running = (async () => {
        const seq = session.seq + 1;
        session.seq = seq;
        await sendOutput(session.agentId, output, sessionId, seq);
        const latest = sessions.get(sessionId);
        if (latest?.outputBuffer) {
            scheduleOutputFlush(sessionId);
        }
    })();
    session.flushInFlight = running;
    try {
        await running;
    }
    finally {
        const latest = sessions.get(sessionId);
        if (latest) {
            latest.flushInFlight = null;
        }
    }
}
